js中0.1+0.2的问题(作者:老司机)
一、前提:
0.1或0.2等一些小数,转换为二进制格式时,是一个无限循环的小数,因此最终得到的结果是一个近似值(保留52位小数),在计算过程中,有可能会发生位数的变化,在进行舍取的过程中,导致精度丢失
js的浮点数在计算机底层的存储格式为:符号位(1) + 阶码(11)+ 尾数(52)
符号位:0表示正数,1表示负数
阶码:指数+1023(最后转为二进制格式,若不足11位,则在前面补0)
1023称为偏移量:2 ^ (位数-1) - 1,此处js浮点数的阶码位数为11,所以:2 ^ 10 - 1 = 1023
以0.1为例:
- 科学记数法格式(乘二取整,顺序排序):1.1 00110011 00110011 00110011 00110011 00110011 00110011 010 x 2 ^ -4
- 二进制格式:0 01111111011 1001100110011001100110011001100110011001100110011010【省略了整数1,作为一个隐藏位】
二、实现 0.1+0.2===0.3
function binaryStrToNum(str) {
// 1、获取符号位
let sign = str.charAt(0);
// 2、获取阶码(1~11【包含11】)
let exponent = str.slice(1, 12);
// 3、获取小数位
let m = str.slice(12);
let sum = 0;
// 注意:在尾数中,计算是从第一位开始的,一共有52位,其实还包含一个隐藏位(上面已经提了),这个隐藏位的计算结果始终是1(计算方式:1 * 2 ^ 0),这个也必须累加到计算结果中
for (let i=0; i<m.length; i++) {
let c = m.charAt(i);
if (c == "1") {
sum += Math.pow(2, -(i + 1));
}
}
// 4、计算结果,公式为:-1 ^ 符号 * 2 ^ (阶码-1023) * sum
return Math.pow(-1, +sign) * Math.pow(2, parseInt(exponent, 2) - 1023) * (1 + sum);
}
// 《0.1》
// const r1 = binaryStrToNum("0011111110111001100110011001100110011001100110011001100110011010");
// console.log(r1);
// 《0.2》
// 1.1 00110011 00110011 00110011 00110011 00110011 00110011 010 x 2 ^ -3 = 0 01111111100 1001100110011001100110011001100110011001100110011010
// const r2 = binaryStrToNum("0011111111001001100110011001100110011001100110011001100110011010");
// console.log(r2);
/**
*《0.1 + 0.2》
* 先对其进行计算(保留52位,第53位为0时,舍弃;为1时,进1):
* 0.00011 00110011 00110011 00110011 00110011 00110011 00110011 010 + 0.00110 01100110 01100110 01100110 01100110 01100110 01100110 10
* = 0.01001 10011001 10011001 10011001 10011001 10011001 10011001 110
* = 1.001 10011001 10011001 10011001 10011001 10011001 10011001 110 x 2 ^ -2【在此次计算中,尾数变成了53位,因此要进行舍取】
* = 1.001 10011001 10011001 10011001 10011001 10011001 10011010 0 x 2 ^ -2【取小数52位】
* 转为二进制格式 =>:0 01111111101 0011001100110011001100110011001100110011001100110100
*/
const r3 = binaryStrToNum("0011111111010011001100110011001100110011001100110011001100110100");
console.log(r3); // 0.30000000000000004
console.log(r3 == (0.1 + 0.2)); // true