0.30000000000000004

In [1]: 0.1+0.2
Out[1]: 0.30000000000000004

首先要说的是,这并不是编程语言的bug,而是十进制转换二进制后无限循环小数的精度问题,其他编程语言也有这种情况,有人专门建了一个网站来收录:http://0.30000000000000004.com/

把小数装入计算机,总共分几步?

  1. 转换成二进制
  2. 用科学计数法表示
  3. 表示成IEEE 754格式

浮点数的二进制表示

十进制小数转化为二进制小数采用“乘2取整,顺序排列”的方法。

0.1 的表示是什么?

0.1 * 2 = 0.2 整数部分取 0
0.2 * 2 = 0.4 整数部分取 0
0.4 * 2 = 0.8 整数部分取 0
0.8 * 2 = 1.6 整数部分取 1
0.6 * 2 = 1.2 整数部分取 1
0.2 * 2 = 0.4 整数部分取 0

所以0.1 的二进制表示是 0.0001100110011……(无限循环小数)。

如果你仔细观察下会发现:

0.1 到 0.9 的 9 个小数中,只有 0.5 可以用二进制精确的表示。

这就引出了一个问题,计算机是无法精确存储一些小数的。

浮点数的二进制存储

IEEE 754将双精度浮点数的64 bit规定为 3 部分:

  • 第一位符号位
  • 11位存储指数部分
  • 剩下52位存储尾数部分

 

因此我们用IEEE 754规范来计算一下 0.1 + 0.2 吧!

十进制0.1

=> 二进制0.00011001100110011…(循环0011)
=> 尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0
=> 最终存储为:0(符号位) 00000000100(11位指数) 10011001100110011…11001(52位尾数)

十进制0.2

=> 二进制0.0011001100110011…(循环0011)
=> 尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0
=> 最终存储为:0(符号位) 00000000011(11位指数) 10011001100110011…11001(52位尾数)

两者相加:

0.00011001100110011001100110011001100110011001100110011001 +

0.00110011001100110011001100110011001100110011001100110011 =

0.01001100110011001100110011001100110011001100110011001100

转换成10进制之后得到:0.30000000000000004。

BTW

如果开发一套货币系统,那货币的金额一定要是 “ 1 分钱” 的整数倍,而不是有 “ 0.01 元” 这样的数据,所有的数据都要用整型表示,显示的时候再加上小数点,否则便会出现麻烦。

Zhao Li /
Published under (CC) BY-NC-SA in categories 杂谈  tagged with IEEE754