玩命加载中 . . .

JS 之精度丢失


浮点数的运算

一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断。

  • 首先是对阶,所谓对阶,就是把阶码调整为相同。

    • 比如 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"

文章作者: hcyety
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hcyety !
评论
  目录