Math.ts 12 KB

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