# JS浮点数计算精度问题-简易解法

130 阅读2分钟

解决方案

解决原理: 记录数字的小数点位数,移除小数点整理成整数来计算,再把小数点加上
暴露了五个方法:加plus, 减minus, 乘times,  除divide,  四舍五入round

image.png 弊端 :没解决大数计算问题 image.png 当然,也可以考虑其他解决方案: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),超出的部分自动进一舍零