浮点数的运算
一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断。
首先是对阶,所谓对阶,就是把阶码调整为相同。
- 比如 0.1 是 1.1001100110011…… * 2^-4,阶码是 -4,
- 而 0.2 就是 1.10011001100110…* 2^-3,阶码是 -3;
- 两个阶码不同,所以先调整为相同的阶码再进行计算,调整原则是小阶对大阶,也就是 0.1 的 -4 调整为 -3,对应变成 0.11001100110011…… * 2^-3
接下来是尾数计算:
- 0.1100110011001100110011001100110011001100110011001101 +1.1001100110011001100110011001100110011001100110011010—————————————————————————————— 10.0110011001100110011001100110011001100110011001100111
- 我们得到结果为:10.0110011001100110011001100110011001100110011001100111 * 2^-3
将这个结果处理一下,即结果规格化:
- 变成 1.0011001100110011001100110011001100110011001100110011(1) * 2^-2,括号里的 1 意思是说计算后这个 1 超出了范围,所以要被舍弃了。
再然后是舍入,四舍五入对应到二进制中,就是 0 舍 1 入。
- 因为我们要把括号里的 1 丢了,所以这里会进一,结果变成1.0011001100110011001100110011001100110011001100110100 * 2^-2
本来还有一个溢出判断,因为这里不涉及,就不讲了。
所以最终的结果存成 64 位就是0 01111111101 0011001100110011001100110011001100110011001100110100,
将它转换为10进制数就得到 0.30000000000000004440892098500626。
因为两次存储时的精度丢失加上一次运算时的精度丢失,最终导致了 0.1 + 0.2 !== 0.3
其他
// 十进制转二进制
parseFloat(0.1).toString(2);
=> "0.0001100110011001100110011001100110011001100110011001101"
// 二进制转十进制
parseInt(1100100,2)
=> 100
// 以指定的精度返回该数值对象的字符串表示
(0.1 + 0.2).toPrecision(21)
=> "0.300000000000000044409"
(0.3).toPrecision(21)
=> "0.299999999999999988898"