0.1 + 0.2 // 0.30000000000000004
0.1 + 0.3 // 0.4
0.1 + 0.4 // 0.5
0.1 + 0.2 为什么不等于 0.3
我们知道,js 中所有的值在计算机底层都是以二进制来进行存储的。
js 使用 Number 类型表示数字(整数和浮点数),遵循 IEEE-754 标准,通过 64 位二进制来表示一个数字。
我们打开 IEEE-754标准数字到二进制转换工具,输入 0.1,点击 Not Rounded,可以看到 0.1 在计算机底层存储的 64 为二进制字符。
// 符号位 + 指数位 + 有效数字部分(不包含前面的1.1)
0.1 // '0011111110111001100110011001100110011001100110011001100110011010'
0.2 // '0011111111001001100110011001100110011001100110011001100110011010'
而在 Js 中,我们平时写的数字,浏览器都认为是十进制的,那浏览器肯定有一个操作,把我们写的 10 进制值转换为二进制。
如果我们来实现一个十进制转二进制呢,也就是 n.toString(2)
二进制转十进制进制
整数部分,除2取余,逆序排列
不断除2,拿余数倒序拼接得二进制,拿 12 举例,如下图

// 整数(兼容了负数) 十进制转二进制
let decimal2Binary = function (decimal) {
let isNegative = decimal < 0; // 是否是负数
decimal = Math.abs(decimal); // 都变成正数计算
let binary = [decimal % 2]; // 存二进制字符
let integer = Math.floor(decimal / 2);
while(integer) {
binary.unshift(integer % 2); // 取余数
integer = Math.floor(integer / 2); // 向下取整 0.5 -> 0 直到 为 0
}
return isNegative ? `-${ binary.join('') }` : binary.join('');
}
decimal2Binary(12) // 1100
decimal2Binary(-12) // -1100
小数部分,乘2取整,顺序排列
不断乘以2,取整数部分,余数继续乘以2,继续取整数部分,直到取整后余数为0。

我们得到的二进制数是 0.0011001100110011..,这样无尽的的循环,但是计算进底层最多能存储 64 位,所以会对十进制 0.1 转成的二进制进行截取,这样就没法保证精度了。
结论:为什么不等
浮点数在计算机底层存储的时候,十进制的 0.1 转化为存储的二进制值,可能被舍掉一部分「因为最多只有 64 位」,所以本身和原来的十进制就不一样了,这是所有编程语言都存在的问题,因为浮点数在后端语言中,也是按二进制进行存储的。 而 0.1 转二进制就被截取了一部分, 所以计算机底层进行的运算 0.1 + 0.2 本身就是不准确的,最后转成浏览器能识别的十进制,这样也可能是一个不准确的很长的值,例如可能是这样的 0.300000000000000040000... 但是浏览器也会存在长度的限制(小数点后17位),会截掉一部分,而最后面全是 0 的省略掉。
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.3 // 0.4 -> 其实是 0.4000000000000000000000001222222xxx 截取17位小数时候发现后面全是0 故返回 0.4
0.3 + 0.6 // 0.8999999999999999 后面也是截取掉的
所以导致浮点数计算不精确的原因有两个
- 十进制的浮点数转二进制时候,面临的计算机只截取64位二进制值的问题。
- 二进制浮点数运算结果转十进制时,面临的浏览器只展示小数点后17位问题。
怎么解决浮点数计算不精确的问题(这里示范加法)
1.乘以一定系数,避免浮点数运算,变成整数,运算结果再除以系数。
// 根据小数点后位数 获取合适系数
const getCoefficient = function(num) {
// 数字 -> 字符串 -> 小数点位数
num = num + '';
let [,char = ''] = num.split('.');
return Math.pow(10, char.length);
}
// 加法
const plus = function plus(num1, num2) {
num1 = +num1;
num2 = +num2;
if (isNaN(num1) || isNaN(num2)) return NaN;
// 最大系数
let coefficient = Math.max(getCoefficient(num1), getCoefficient(num2));
return (num1 * coefficient + num2 * coefficient) / coefficient;
}