In [1]: 0.1+0.2
Out[1]: 0.30000000000000004
首先要说的是,这并不是编程语言的bug,而是十进制转换二进制后无限循环小数的精度问题,其他编程语言也有这种情况,有人专门建了一个网站来收录:http://0.30000000000000004.com/ 。
把小数装入计算机,总共分几步?
- 转换成二进制
- 用科学计数法表示
- 表示成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 元” 这样的数据,所有的数据都要用整型表示,显示的时候再加上小数点,否则便会出现麻烦。