已知周长如何求直径(都知道0.1+0.2 = 0.30000000000000004,那要怎么让它等于0.3)
前言
小学数学老师教过我们 ,0.1 + 0.2 = 0.3 ,但是为什么在我们在浏览器的控制台中输出却是0.30000000000000004?
除了加法有这个奇怪的现象 ,带小数点的减法和乘除计算也会得出意料之外的结果
console.log(0.3 - 0.1) // 0.19999999999999998 console.log(0.1 * 0.2) // 0.020000000000000004 console.log(0.3 / 0.1) // 2.9999999999999996原因
我们都知道计算机时是通过二进制来进行计算的 ,即 0 和 1
就拿 0.1 + 0.2 来说 ,0.1表示为0.0001100110011001... ,而0.2表示为0.0011001100110011...
而在二进制中 1 + 1 = 10 ,所以 0.1 + 0.2 = 0.0100110011001100...
转成10进制就近似表示为 0.30000000000000004
结论
简单来说就是 ,浮点数转成二进制时丢失了精度 ,因此在二进制计算完再转回十进制时可能会和理论结果不同
对于浮点数的四则运算,许多编程语言都会有理论值和实际值不同的问题 。例如Java中也会出现类似的问题 ,但是Java中可以使用java.math.BigDecimal类来避免这种情况
可是JS是弱类型的语言 ,作者Brendan Eich自述10天内开发出JS语言,一开始设计的时候就没有对浮点数计算有个处理的好方法
那么在日常开发的前端项目中我们可以怎么解决嘞?
解决方案
简单实现
使用toFixed()<不推荐>
可以控制小数点后几位 ,如果为空的话会用0补充 ,返回一个字符串
> 0.123.toFixed(2) // 0.12缺点:
在不同浏览器中得出的值可能不相同,且部分数字得不到预计的结果 ,并不是执行严格的四舍五入 // 在chrome控制台中 > 1.014.toFixed(2) // 1.01 > 1.215.toFixed(2) // 1.22 > 1.105.toFixed(2) // 1.10 > 1.115.toFixed(2) // 1.11乘以一个10的幂次方
把需要计算的数字乘以10的n次方 ,让数值都变为整数 ,计算完后再除以10的n次方 ,这样就不会出现浮点数精度丢失问题
> (0.1 * 10 + 0.2 *10) / 10 // 0.3我们可以将它封装成一个函数
mathFloat = function (float, digit) { const math = Math.pow(10, digit); return parseInt(float * math, 10) / math; } mathFloat(0.1 + 0.2, 3) // 0.3缺点:
JS中的存储都是通过8字节的double浮点类型表示的 ,因此它并不能准确记录所有数字 ,它存在一个数值范围Number.MAX_SAFE_INTEGER为 9007199254740991 ,而Number.MIN_SAFE_INTEGER为 -9007199254740991 ,超出这个范围的话JS是无法表示的
虽然范围有限制 ,但是数值一般都够用较为完整的实现
加法
function mathPlus(arg1, arg2) { let r1, r2, m; try { r1 = arg1.toString().split(".")[1].length; // 获取小数点后字符长度 } catch (error) { r1 = 0; // 为整数状态,r1赋0 } try { r2 = arg2.toString().split(".")[1].length; } catch (error) { r2 = 0; } m = Math.pow(10, Math.max(r1, r2)); // 确保所有参数都为整数 return (arg1 * m + arg2 * m) / m; } > mathPlus(0.1, 0.2); // 0.3 > mathPlus(1, 2); // 3减法
function mathSubtract(arg1, arg2) { let r1, r2, m; try { r1 = arg1.toString().split(".")[1].length; } catch (error) { r1 = 0; } try { r2 = arg2.toString().split(".")[1].length; } catch (error) { r2 = 0; } m = Math.pow(10, Math.max(r1, r2)); return ((arg1 * m - arg2 * m) / m); } > mathSubtract(0.3, 0.1); // 0.2 > mathSubtract(3, 1); // 2乘法
function mathMultiply(arg1, arg2) { let m = 0; let s1 = arg1.toString(); let s2 = arg2.toString(); try { m += s1.split(.)[1].length; // 小数相乘 ,小数点后个数相加 } catch (e) {} try { m += s2.split(.)[1].length; } catch (e) {} return ( (Number(s1.replace(., )) * Number(s2.replace(., ))) / Math.pow(10, m) ); } > mathMultiply(0.1, 0.2); // 0.02 > mathMultiply(1, 2); // 2除法
function mathDivide(arg1, arg2) { let m1 = 0; let m2 = 0; let n1 = 0; let n2 = 0; try { m1 = arg1.toString().split(.)[1].length; } catch (e) {} try { m2 = arg2.toString().split(.)[1].length; } catch (e) {} n1 = Number(arg1.toString().replace(., )); n2 = Number(arg2.toString().replace(., )); /** * 将除法转换成乘法 * 乘以它们的小数点后个数差 */ return mathMultiply(n1 / n2, Math.pow(10, m2 - m1)); } // > 0.2 / 0.03 => 6.666666666666667 > mathDivide(0.2, 0.03); // 6.666666666666665 > mathDivide(0.3, 0.1); // 3 > mathDivide(3, 1); // 3引入第三方库
站在前人的肩膀上 ,可以前进的更快 。下面这些成熟的库封装了很多实用的函数,虽然部分函数可能永远不会用到
Math.js
介绍:功能强大 ,内置大量函数 ,体积较大
Github地址:https://github.com/josdejong/mathjs
star: 12.2k+decimal.js
介绍:支持三角函数等,并支持非整数幂
Github地址:https://github.com/MikeMcl/decimal.js
star: 4.8k+big.js
介绍:体积6k ,提供了CDN
Github地址:https://github.com/MikeMcl/big.js
star: 3.9k+number-precision
介绍:体积很小 ,只有1k左右
Github地址:https://github.com/nefe/number-precision
star: 3.4k+创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!