理解浮点数

说到理解浮点数, 最著名的一个问题莫过于 0.1 + 0.2 = ?
答案似乎显而易见, 运行python交互式终端, 键入算式按下回车, 结果如下

等等,这似乎并不是我们期待的答案! 要理解这个奇怪的现象,我们就必须从什么是浮点数说起 ...

浮点数的存储

以double类型的浮点数为例,数据在内存中由如下三个部分组成:

  • 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
  • 指数位E:中间的 11 位存储指数(exponent),用来表示次方数
  • 尾数位M:最后的 52 位是尾数(mantissa),首位的始终为1, 在存储时省略, 超出的部分自动进一舍零

如 4.5 转换成二进制就是 100.1,科学计数法表示是 1.001*2^2,舍去1后 M = 001。E是一个无符号整数,因为长度是11位,取值范围是 0~2047。但是科学计数法中的指数是可以为负数的,所以再减去一个中间数 1023,[0,1022]表示为负,[1024,2047] 表示为正。如4.5 的指数E = 1025,尾数M为 001。

浮点数值可由以下公式表示:

换句话说,任何一个浮点数的数值其实是由若干个 2^x 叠加表示的. 那么岂不是很多数值都没有办法用浮点数准确表示?

事实的确是这样, 以 0.1 为例, 0.1 转成二进制表示为 0.0001100110011001100(1100循环),1.100110011001100x2^-4,所以 E=-4+1023=1019;M 舍去首位的1,得到 100110011...。最终就是:

转化成十进制后为 0.100000000000000005551115123126,因此就出现了浮点误差。

事实上, 能用浮点数准确表示的数值只占实数中很小的一部分, 如下图

所以在进行高精度运算时(如涉及金额, 或者需要保留小数点后很多位的计算), 请使用如 java.math.BigDecimal 这样专门为精度计算所设计的数据类型, 在这样的数据类型中使用了十进制(BigInteger)+ 小数点位置(scale) 来表示小数, 如 100.001 = 100001 * 0.1^3, 在这种表示方式下,当然就不会出现精度问题了

参考资料:

Show Comments