ExactMath.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /**
  2. * @author zp
  3. */
  4. /**
  5. * 多平台一致精确计算库,比decimal更轻量更快
  6. * [Math.round、Math.min、Math.max,Math.floor、Math.ceil,这些系统方法一般情况下是可以放心使用的]
  7. */
  8. const ExactMath = Object.create(null);
  9. module.exports = ExactMath;
  10. // 计算精度
  11. // sin、cos、tan方法的误差小数点后16位
  12. const ACCURACY_SIN_ERROR = 1e-16;
  13. const ACCURACY_COS_ERROR = 1e-16;
  14. const ACCURACY_TAN_ERROR = 1e-16;
  15. // 角度弧度常量
  16. const DEG = 57.29577951308232;
  17. const RAD = 0.017453292519943295;
  18. // 系统常量
  19. ExactMath.PI = 3.141592653589793;
  20. ExactMath.E = 2.718281828459045;
  21. ExactMath.LN2 = 0.6931471805599453;
  22. ExactMath.LN10 = 2.302585092994046;
  23. ExactMath.LOG2E = 1.4426950408889634;
  24. ExactMath.LOG10E = 0.4342944819032518;
  25. ExactMath.SQRT1_2 = 0.7071067811865476;
  26. ExactMath.SQRT2 = 1.4142135623730951;
  27. /**
  28. * 链式调用
  29. * @example
  30. * const value = ExactMath.value(10).add(20.123).mul(2).sqrt().value;
  31. */
  32. let chain = null;
  33. ExactMath.value = function (value) {
  34. if (!chain) {
  35. chain = {
  36. value: 0,
  37. valueOf() { return this.value; },
  38. toString() { return String(this.value); }
  39. }
  40. for (const key in ExactMath) {
  41. if (key !== 'value' && typeof ExactMath[key] === 'function') {
  42. chain[key] = function (...args) {
  43. this.value = ExactMath[key].call(ExactMath, this.value, ...args);
  44. return this;
  45. }
  46. }
  47. }
  48. }
  49. chain.value = value;
  50. return chain;
  51. }
  52. /****************************************************基础****************************************************/
  53. /**
  54. * 获得小数位数
  55. * @param {Number} num 浮点数
  56. * @returns {Number}
  57. */
  58. ExactMath.getDecimalPlace = function (num) {
  59. if (num && num !== Math.floor(num)) {
  60. for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) {
  61. temp = num * m;
  62. if (temp == Math.floor(temp)) return n;
  63. }
  64. return 20;
  65. } else {
  66. return 0;
  67. }
  68. }
  69. /**
  70. * 保留n为小数,并四舍五入
  71. * @example
  72. * (2.335).toFixed(2)
  73. * ExactMath.toFixed(2.335, 2)
  74. * @param {Number} num 浮点数
  75. * @param {Number} n 整数
  76. * @returns {Number}
  77. */
  78. ExactMath.toFixed = function (num, n = 0) {
  79. if (n == 0) {
  80. return Math.round(num);
  81. } else {
  82. const m = Math.pow(10, n);
  83. return Math.round(num * (m * 10) / 10) / m;
  84. }
  85. }
  86. ExactMath.abs = function (x) {
  87. return Math.abs(x);
  88. }
  89. ExactMath.round = function (x) {
  90. return Math.round(x);
  91. }
  92. ExactMath.ceil = function (x) {
  93. return Math.ceil(x)
  94. }
  95. ExactMath.floor = function (x) {
  96. return Math.floor(x)
  97. }
  98. ExactMath.min = function (...args) {
  99. return Math.min(...args);
  100. }
  101. ExactMath.max = function (...args) {
  102. return Math.max(...args);
  103. }
  104. /**
  105. * 小数相加
  106. * @param {Number} num1 浮点数
  107. * @param {Number} num2 浮点数
  108. * @returns {Number}
  109. */
  110. ExactMath.add = function (...args) {
  111. if (args.length === 2) {
  112. const num1 = args[0];
  113. const num2 = args[1];
  114. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  115. return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m;
  116. } else {
  117. return args.reduce((a, b) => this.add(a, b))
  118. }
  119. };
  120. /**
  121. * 小数相减
  122. * @param {Number} num1 浮点数
  123. * @param {Number} num2 浮点数
  124. * @returns {Number}
  125. */
  126. ExactMath.sub = function (...args) {
  127. if (args.length === 2) {
  128. const num1 = args[0];
  129. const num2 = args[1];
  130. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  131. return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m;
  132. } else {
  133. return args.reduce((a, b) => this.sub(a, b))
  134. }
  135. };
  136. /**
  137. * 小数相乘
  138. * @param {Number} num1 浮点数
  139. * @param {Number} num2 浮点数
  140. * @returns {Number}
  141. */
  142. ExactMath.mul = function (...args) {
  143. if (args.length === 2) {
  144. let num1 = args[0];
  145. let num2 = args[1];
  146. // 方案1:
  147. // 直接相乘,但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大
  148. // const n1 = this.getDecimalPlace(num1);
  149. // const n2 = this.getDecimalPlace(num2);
  150. // const m1 = Math.pow(10, n1);
  151. // const m2 = Math.pow(10, n2);
  152. // return (n1 * m1) * (n2 * m2) / (m1 * m2);
  153. // 方案2:
  154. // 用除法实现乘法,不会存在过大中间值
  155. let n1 = this.getDecimalPlace(num1);
  156. let n2 = this.getDecimalPlace(num2);
  157. let m = Math.pow(10, n2);
  158. num2 = m / this.toFixed(num2 * m);
  159. m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2)));
  160. m = this.toFixed(num1 * m) / this.toFixed(num2 * m);
  161. let n = Math.min(this.getDecimalPlace(m), n1 + n2);
  162. return this.toFixed(m, n);
  163. } else {
  164. return args.reduce((a, b) => this.mul(a, b))
  165. }
  166. };
  167. /**
  168. * 小数相除法
  169. * @param {Number} num1 浮点数
  170. * @param {Number} num2 浮点数
  171. * @returns {Number}
  172. */
  173. ExactMath.div = function (...args) {
  174. if (args.length === 2) {
  175. const num1 = args[0];
  176. const num2 = args[1];
  177. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  178. return this.toFixed(num1 * m) / this.toFixed(num2 * m);
  179. } else {
  180. return args.reduce((a, b) => this.div(a, b))
  181. }
  182. };
  183. /**
  184. * 取余
  185. * @param {Number} num1 浮点数
  186. * @param {Number} num2 浮点数
  187. * @returns {Number}
  188. */
  189. ExactMath.rem = function (...args) {
  190. if (args.length === 2) {
  191. const num1 = args[0];
  192. const num2 = args[1];
  193. const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2)));
  194. return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m;
  195. } else {
  196. return args.reduce((a, b) => this.rem(a, b))
  197. }
  198. };
  199. /**
  200. * n次方,仅支持整数次方(正负都可以)
  201. * @param {Number} num 浮点数
  202. * @param {Number} n 整数
  203. */
  204. ExactMath.pow = function (num, n) {
  205. if (num == 0 && n == 0) {
  206. return 1;
  207. }
  208. if (num == 0 && n > 0) {
  209. return 0
  210. }
  211. if (num == 0 && n < 0) {
  212. return Infinity;
  213. }
  214. // num为负数,n为负小数,返回NaN
  215. if (num < 0 && n < 0 && Math.round(n) != n) {
  216. return NaN;
  217. }
  218. if (Math.round(n) != n) {
  219. throw new Error('n must be an integer');
  220. }
  221. let result = 1;
  222. if (n > 0) {
  223. for (let index = 0; index < n; index++) {
  224. result = this.mul(result, num);
  225. }
  226. } else if (n < 0) {
  227. for (let index = 0, len = Math.abs(n); index < len; index++) {
  228. result = this.div(result, num);
  229. }
  230. }
  231. return result;
  232. };
  233. /**
  234. * 开方运算【牛顿迭代法】
  235. *
  236. * @param {Number} n
  237. * @returns
  238. */
  239. ExactMath.sqrt = function (n) {
  240. if (n < 0) return NaN;
  241. if (n === 0) return 0;
  242. if (n === 1) return 1;
  243. let last = 0;
  244. let res = 1;
  245. let c = 50;
  246. while (res != last && --c >= 0) {
  247. last = res;
  248. res = this.div(this.add(res, this.div(n, res)), 2)
  249. }
  250. return res;
  251. // float InvSqrt(float x)
  252. // {
  253. // float xhalf = 0.5f * x;
  254. // int i = * (int *) & x; // get bits for floating VALUE
  255. // i = 0x5f375a86 - (i >> 1); // gives initial guess y0
  256. // x = * (float *) & i; // convert bits BACK to float
  257. // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
  258. // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
  259. // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
  260. // return 1 / x;
  261. // }
  262. };
  263. /****************************************************随机****************************************************/
  264. function getSeed(seed) {
  265. if (isNaN(seed)) {
  266. seed = Math.floor(Math.random() * 233280);
  267. } else {
  268. seed = Math.floor(seed % 233280);
  269. }
  270. return seed;
  271. }
  272. let randomSeed = getSeed();
  273. /**
  274. * 设置随机种子
  275. */
  276. ExactMath.setSeed = function (seed) {
  277. randomSeed = getSeed(seed);
  278. };
  279. /**
  280. * 随机
  281. */
  282. ExactMath.random = function () {
  283. randomSeed = (randomSeed * 9301 + 49297) % 233280;
  284. return randomSeed / 233280.0;
  285. };
  286. /**
  287. * 根据随机种子随机
  288. * @param {number} seed
  289. */
  290. ExactMath.randomBySeed = function (seed) {
  291. seed = getSeed(seed);
  292. seed = (seed * 9301 + 49297) % 233280;
  293. return seed / 233280.0;
  294. };
  295. /****************************************************角度弧度转换****************************************************/
  296. /**
  297. * 弧度数转角度数
  298. * @param {Number} radians 浮点数
  299. * @returns {Numbe} 浮点数
  300. */
  301. ExactMath.radiansToDegrees = function (radians) {
  302. return this.div(radians, RAD);
  303. };
  304. /**
  305. * 角度数转弧度数
  306. * @param {Number} degrees 浮点数
  307. * @returns {Numbe} 浮点数
  308. */
  309. ExactMath.degreesToRadians = function (degrees) {
  310. return this.div(degrees, DEG);
  311. };
  312. /**
  313. * 将角度值转换到[0, 360)范围内
  314. * @param {Number} angle 浮点数
  315. * @returns {Number} 整数
  316. */
  317. ExactMath.get0To360Angle = function (angle) {
  318. if (angle === 0) {
  319. return 0;
  320. } else if (angle < 0) {
  321. return this.add(this.rem(angle, 360), 360);
  322. } else {
  323. return this.rem(angle, 360);
  324. }
  325. };
  326. /****************************************************三角函数****************************************************/
  327. /**
  328. * 查表
  329. */
  330. ExactMath._sin = {};
  331. ExactMath._cos = {};
  332. ExactMath._tan = {};
  333. /**
  334. * 3个三角函数,根据需求自行添加
  335. * 为了效率,应该尽量使用查表法
  336. * 表内查不到的,目前使用系统方法的结果并取前4位小数
  337. */
  338. ExactMath.sin = function (x) {
  339. if (this._sin.hasOwnProperty(x)) {
  340. return this._sin[x];
  341. }
  342. // if (x == 0) {
  343. // return 0;
  344. // } else if (x == 90) {
  345. // return 1;
  346. // }
  347. // let n = x, sum = 0, i = 1;
  348. // do {
  349. // i++;
  350. // sum = this.add(sum, n);
  351. // // n = -n * x * x / (2 * i - 1) / (2 * i - 2);
  352. // n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2));
  353. // } while (Math.abs(n) >= ACCURACY_SIN_ERROR);
  354. // return sum;
  355. return this.toFixed(Math.sin(x), 4);
  356. };
  357. ExactMath.cos = function (x) {
  358. if (this._cos.hasOwnProperty(x)) {
  359. return this._cos[x];
  360. }
  361. return this.toFixed(Math.cos(x), 4);
  362. };
  363. ExactMath.tan = function (x) {
  364. if (this._tan.hasOwnProperty(x)) {
  365. return this._tan[x];
  366. }
  367. return this.toFixed(Math.tan(x), 4);
  368. };