小学数学老师教过我们,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语言,一开始设计的时候就没有对浮点数计算有个处理的好方法
那么在日常开发的前端项目中我们可以怎么解决嘞?
可以控制小数点后几位,如果为空的话会用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的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
缺点:
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
站在前人的肩膀上,可以前进的更快。下面这些成熟的库封装了很多实用的函数,虽然部分函数可能永远不会用到
介绍:功能强大,内置大量函数,体积较大
Github地址:https://github.com/josdejong/mathjs
star: 12.2k+
介绍:支持三角函数等,并支持非整数幂
Github地址:https://github.com/MikeMcl/decimal.js
star: 4.8k+
介绍:体积6k,提供了CDN
Github地址:https://github.com/MikeMcl/big.js
star: 3.9k+
介绍:体积很小,只有1k左右
Github地址:https://github.com/nefe/number-precision
star: 3.4k+
0.1 + 0.2 是否等于 0.3 作为一道经典的面试题,已经广外熟知,说起原因,大家能回答出这是浮点数精度问题导致,也能辩证的看待这并非是 ECMAScript 这门语言的问题,今天就是具体看一下背后的原因。
因为toFixed可能会出现bug,比如value为: 310.275,保留2位小数,为310.27;或者是value为: 139.605 ,保留2位小数,为: 139.60
ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。它表示 1 与大于 1 的最小浮点数之间的差。Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了
JS 在存放整数的时候是有一个安全范围的,一旦数字超过这个范围便会损失精度。我们不能拿精度损失的数字进行运行,因为运算结果一样是会损失精度的。所以,我们要用字符串来表示数据!(不会丢失精度)
由于计算机的底层是由二进制实现的,有些运算的数字无法全部显示出来。就像一些无理数不能完全显示出来一样,如圆周率 3.1415926...,0.3333... 等。JavaScript遵循IEEE754规范
最近在做项目的时候,涉及到商品价格的计算,经常会出现计算出现精度问题。刚开始草草了事,直接用toFixed就解决了问题,并没有好好的思考一下这个问题
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!