/** * @author zp */ /** * 多平台一致精确计算库,比decimal更轻量更快 * [Math.round、Math.min、Math.max,Math.floor、Math.ceil,这些系统方法一般情况下是可以放心使用的] */ const ExactMath = Object.create(null); module.exports = ExactMath; // 计算精度 // sin、cos、tan方法的误差小数点后16位 const ACCURACY_SIN_ERROR = 1e-16; const ACCURACY_COS_ERROR = 1e-16; const ACCURACY_TAN_ERROR = 1e-16; // 角度弧度常量 const DEG = 57.29577951308232; const RAD = 0.017453292519943295; // 系统常量 ExactMath.PI = 3.141592653589793; ExactMath.E = 2.718281828459045; ExactMath.LN2 = 0.6931471805599453; ExactMath.LN10 = 2.302585092994046; ExactMath.LOG2E = 1.4426950408889634; ExactMath.LOG10E = 0.4342944819032518; ExactMath.SQRT1_2 = 0.7071067811865476; ExactMath.SQRT2 = 1.4142135623730951; /** * 链式调用 * @example * const value = ExactMath.value(10).add(20.123).mul(2).sqrt().value; */ let chain = null; ExactMath.value = function (value) { if (!chain) { chain = { value: 0, valueOf() { return this.value; }, toString() { return String(this.value); } } for (const key in ExactMath) { if (key !== 'value' && typeof ExactMath[key] === 'function') { chain[key] = function (...args) { this.value = ExactMath[key].call(ExactMath, this.value, ...args); return this; } } } } chain.value = value; return chain; } /****************************************************基础****************************************************/ /** * 获得小数位数 * @param {Number} num 浮点数 * @returns {Number} */ ExactMath.getDecimalPlace = function (num) { if (num && num !== Math.floor(num)) { for (let n = 1, m = 10, temp = 0; n < 20; n += 1, m *= 10) { temp = num * m; if (temp == Math.floor(temp)) return n; } return 20; } else { return 0; } } /** * 保留n为小数,并四舍五入 * @example * (2.335).toFixed(2) * ExactMath.toFixed(2.335, 2) * @param {Number} num 浮点数 * @param {Number} n 整数 * @returns {Number} */ ExactMath.toFixed = function (num, n = 0) { if (n == 0) { return Math.round(num); } else { const m = Math.pow(10, n); return Math.round(num * (m * 10) / 10) / m; } } ExactMath.abs = function (x) { return Math.abs(x); } ExactMath.round = function (x) { return Math.round(x); } ExactMath.ceil = function (x) { return Math.ceil(x) } ExactMath.floor = function (x) { return Math.floor(x) } ExactMath.min = function (...args) { return Math.min(...args); } ExactMath.max = function (...args) { return Math.max(...args); } /** * 小数相加 * @param {Number} num1 浮点数 * @param {Number} num2 浮点数 * @returns {Number} */ ExactMath.add = function (...args) { if (args.length === 2) { const num1 = args[0]; const num2 = args[1]; const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); return (this.toFixed(num1 * m) + this.toFixed(num2 * m)) / m; } else { return args.reduce((a, b) => this.add(a, b)) } }; /** * 小数相减 * @param {Number} num1 浮点数 * @param {Number} num2 浮点数 * @returns {Number} */ ExactMath.sub = function (...args) { if (args.length === 2) { const num1 = args[0]; const num2 = args[1]; const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); return (this.toFixed(num1 * m) - this.toFixed(num2 * m)) / m; } else { return args.reduce((a, b) => this.sub(a, b)) } }; /** * 小数相乘 * @param {Number} num1 浮点数 * @param {Number} num2 浮点数 * @returns {Number} */ ExactMath.mul = function (...args) { if (args.length === 2) { let num1 = args[0]; let num2 = args[1]; // 方案1: // 直接相乘,但是相乘两数小数点过多会导致中间值[(n1 * m1) * (n2 * m2)]过大 // const n1 = this.getDecimalPlace(num1); // const n2 = this.getDecimalPlace(num2); // const m1 = Math.pow(10, n1); // const m2 = Math.pow(10, n2); // return (n1 * m1) * (n2 * m2) / (m1 * m2); // 方案2: // 用除法实现乘法,不会存在过大中间值 let n1 = this.getDecimalPlace(num1); let n2 = this.getDecimalPlace(num2); let m = Math.pow(10, n2); num2 = m / this.toFixed(num2 * m); m = Math.pow(10, Math.max(n1, this.getDecimalPlace(num2))); m = this.toFixed(num1 * m) / this.toFixed(num2 * m); let n = Math.min(this.getDecimalPlace(m), n1 + n2); return this.toFixed(m, n); } else { return args.reduce((a, b) => this.mul(a, b)) } }; /** * 小数相除法 * @param {Number} num1 浮点数 * @param {Number} num2 浮点数 * @returns {Number} */ ExactMath.div = function (...args) { if (args.length === 2) { const num1 = args[0]; const num2 = args[1]; const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); return this.toFixed(num1 * m) / this.toFixed(num2 * m); } else { return args.reduce((a, b) => this.div(a, b)) } }; /** * 取余 * @param {Number} num1 浮点数 * @param {Number} num2 浮点数 * @returns {Number} */ ExactMath.rem = function (...args) { if (args.length === 2) { const num1 = args[0]; const num2 = args[1]; const m = Math.pow(10, Math.max(this.getDecimalPlace(num1), this.getDecimalPlace(num2))); return this.toFixed(num1 * m) % this.toFixed(num2 * m) / m; } else { return args.reduce((a, b) => this.rem(a, b)) } }; /** * n次方,仅支持整数次方(正负都可以) * @param {Number} num 浮点数 * @param {Number} n 整数 */ ExactMath.pow = function (num, n) { if (num == 0 && n == 0) { return 1; } if (num == 0 && n > 0) { return 0 } if (num == 0 && n < 0) { return Infinity; } // num为负数,n为负小数,返回NaN if (num < 0 && n < 0 && Math.round(n) != n) { return NaN; } if (Math.round(n) != n) { throw new Error('n must be an integer'); } let result = 1; if (n > 0) { for (let index = 0; index < n; index++) { result = this.mul(result, num); } } else if (n < 0) { for (let index = 0, len = Math.abs(n); index < len; index++) { result = this.div(result, num); } } return result; }; /** * 开方运算【牛顿迭代法】 * * @param {Number} n * @returns */ ExactMath.sqrt = function (n) { if (n < 0) return NaN; if (n === 0) return 0; if (n === 1) return 1; let last = 0; let res = 1; let c = 50; while (res != last && --c >= 0) { last = res; res = this.div(this.add(res, this.div(n, res)), 2) } return res; // float InvSqrt(float x) // { // float xhalf = 0.5f * x; // int i = * (int *) & x; // get bits for floating VALUE // i = 0x5f375a86 - (i >> 1); // gives initial guess y0 // x = * (float *) & i; // convert bits BACK to float // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy // x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy // return 1 / x; // } }; /****************************************************随机****************************************************/ function getSeed(seed) { if (isNaN(seed)) { seed = Math.floor(Math.random() * 233280); } else { seed = Math.floor(seed % 233280); } return seed; } let randomSeed = getSeed(); /** * 设置随机种子 */ ExactMath.setSeed = function (seed) { randomSeed = getSeed(seed); }; /** * 随机 */ ExactMath.random = function () { randomSeed = (randomSeed * 9301 + 49297) % 233280; return randomSeed / 233280.0; }; /** * 根据随机种子随机 * @param {number} seed */ ExactMath.randomBySeed = function (seed) { seed = getSeed(seed); seed = (seed * 9301 + 49297) % 233280; return seed / 233280.0; }; /****************************************************角度弧度转换****************************************************/ /** * 弧度数转角度数 * @param {Number} radians 浮点数 * @returns {Numbe} 浮点数 */ ExactMath.radiansToDegrees = function (radians) { return this.div(radians, RAD); }; /** * 角度数转弧度数 * @param {Number} degrees 浮点数 * @returns {Numbe} 浮点数 */ ExactMath.degreesToRadians = function (degrees) { return this.div(degrees, DEG); }; /** * 将角度值转换到[0, 360)范围内 * @param {Number} angle 浮点数 * @returns {Number} 整数 */ ExactMath.get0To360Angle = function (angle) { if (angle === 0) { return 0; } else if (angle < 0) { return this.add(this.rem(angle, 360), 360); } else { return this.rem(angle, 360); } }; /****************************************************三角函数****************************************************/ /** * 查表 */ ExactMath._sin = {}; ExactMath._cos = {}; ExactMath._tan = {}; /** * 3个三角函数,根据需求自行添加 * 为了效率,应该尽量使用查表法 * 表内查不到的,目前使用系统方法的结果并取前4位小数 */ ExactMath.sin = function (x) { if (this._sin.hasOwnProperty(x)) { return this._sin[x]; } // if (x == 0) { // return 0; // } else if (x == 90) { // return 1; // } // let n = x, sum = 0, i = 1; // do { // i++; // sum = this.add(sum, n); // // n = -n * x * x / (2 * i - 1) / (2 * i - 2); // n = this.div(this.mul(-1, n, x, x), this.sub(this.mul(2, i), 1), this.sub(this.mul(2, i), 2)); // } while (Math.abs(n) >= ACCURACY_SIN_ERROR); // return sum; return this.toFixed(Math.sin(x), 4); }; ExactMath.cos = function (x) { if (this._cos.hasOwnProperty(x)) { return this._cos[x]; } return this.toFixed(Math.cos(x), 4); }; ExactMath.tan = function (x) { if (this._tan.hasOwnProperty(x)) { return this._tan[x]; } return this.toFixed(Math.tan(x), 4); };