解决方案
解决原理: 记录数字的小数点位数,移除小数点整理成整数来计算,再把小数点加上
暴露了五个方法:加plus, 减minus, 乘times, 除divide, 四舍五入round
弊端 :没解决大数计算问题
当然,也可以考虑其他解决方案:Math.js
但是太大了,引用成本比较高
源码
js
/**
- Return digits length of a number
- @param {*number} num Input number */ function digitLength(num) { // Get digit length of e const eSplit = num.toString().split(/[eE]/); const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0)); return len > 0 ? len : 0; }
/**
- 把小数转成整数,支持科学计数法。如果是小数则放大成整数
- @param {*number} num 输入数 */ function float2Fixed(num) { if (num.toString().indexOf('e') === -1) { return Number(num.toString().replace('.', '')); } const dLen = digitLength(num); return dLen > 0 ? num * Math.pow(10, dLen) : num; }
/**
- 检测数字是否越界,如果越界给出提示
- @param {*number} num 输入数
*/
function checkBoundary(num) {
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
console.warn(
${num} is beyond boundary when transfer to integer, the results may not be accurate); } }
/**
- 精确乘法 */ function times(num1, num2, ...others) { if (others.length > 0) { return times(times(num1, num2), others[0], ...others.slice(1)); } const num1Changed = float2Fixed(num1); const num2Changed = float2Fixed(num2); const baseNum = digitLength(num1) + digitLength(num2); const leftValue = num1Changed * num2Changed;
checkBoundary(leftValue);
return leftValue / Math.pow(10, baseNum); }
/**
- 精确加法 */ function plus(num1, num2, ...others) { if (others.length > 0) { return plus(plus(num1, num2), others[0], ...others.slice(1)); } const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; }
/**
- 精确减法 */ function minus(num1, num2, ...others) { if (others.length > 0) { return minus(minus(num1, num2), others[0], ...others.slice(1)); } const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; }
/**
- 精确除法 */ function divide(num1, num2, ...others) { if (others.length > 0) { return divide(divide(num1, num2), others[0], ...others.slice(1)); } const num1Changed = float2Fixed(num1); const num2Changed = float2Fixed(num2); checkBoundary(num1Changed); checkBoundary(num2Changed); return times((num1Changed / num2Changed), Math.pow(10, digitLength(num2) - digitLength(num1))); }
/**
- 四舍五入 */ function round(num, ratio) { const base = Math.pow(10, ratio); return divide(Math.round(times(num, base)), base); }
export { plus, minus, times, divide, round }; export default { plus, minus, times, divide, round };
### 附:问题产生原因
JavaScript中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用64位固定长度来表示,也就是标准的 double 双精度浮点数。
这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。
64位比特又可分为三个部分:\
符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数\
指数位E:中间的 11 位存储指数(exponent),用来表示次方数\
尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零