In [1]: 0.1+0.2
Out[1]: 0.30000000000000004首先要说的是,这并不是编程语言的bug,而是十进制转换二进制后无限循环小数的精度问题,其他编程语言也有这种情况,有人专门建了一个网站来收录:http://0.30000000000000004.com/ 。
把小数装入计算机,总共分几步?
<ol>
<li>
转换成二进制
</li>
<li>
用科学计数法表示
</li>
<li>
表示成IEEE 754格式
</li>
</ol>浮点数的二进制表示
<p>
十进制小数转化为二进制小数采用<strong>“乘2取整,顺序排列”</strong>的方法。
</p>
<p>
0.1 的表示是什么?
</p>
<blockquote>
<p>
0.1 * 2 = 0.2 整数部分取 0<br /> 0.2 * 2 = 0.4 整数部分取 0<br /> 0.4 * 2 = 0.8 整数部分取 0<br /> 0.8 * 2 = 1.6 整数部分取 1<br /> 0.6 * 2 = 1.2 整数部分取 1<br /> 0.2 * 2 = 0.4 整数部分取 0<br /> …
</p>
</blockquote>
<p>
所以0.1 的二进制表示是 0.0001100110011……(无限循环小数)。
</p>
<p>
如果你仔细观察下会发现:
</p>
<p>
<strong>0.1 到 0.9 的 9 个小数中,只有 0.5 可以用二进制精确的表示。</strong>
</p>
<p>
这就引出了一个问题,计算机是无法精确存储一些小数的。
</p>浮点数的二进制存储
<p>
<a href="https://en.wikipedia.org/wiki/IEEE_floating_point">IEEE 754</a>将双精度浮点数的64 bit规定为 3 部分:
</p>
<ul>
<li>
第一位符号位
</li>
<li>
11位存储指数部分
</li>
<li>
剩下52位存储尾数部分
</li>
</ul>
<p>
<img class="alignnone" src="https://i0.wp.com/ww2.sinaimg.cn/large/9cd77f2ejw1f6vabbwldbj20go03dgly.jpg?resize=600%2C121" data-recalc-dims="1" />
</p>
<p>
</p>
<p>
因此我们用IEEE 754规范来计算一下 0.1 + 0.2 吧!
</p>十进制0.1
<p>
=> 二进制0.00011001100110011…(循环0011)<br /> => 尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0<br /> => 最终存储为:0(符号位) 00000000100(11位指数) 10011001100110011…11001(52位尾数)
</p>十进制0.2
<p>
=> 二进制0.0011001100110011…(循环0011)<br /> => 尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0<br /> => 最终存储为:0(符号位) 00000000011(11位指数) 10011001100110011…11001(52位尾数)
</p>
<p>
两者相加:
</p>
<p>
0.00011001100110011001100110011001100110011001100110011001 +
</p>
<p>
0.00110011001100110011001100110011001100110011001100110011 =
</p>
<p>
0.01001100110011001100110011001100110011001100110011001100
</p>
<p>
转换成10进制之后得到:0.30000000000000004。
</p>BTW
<p>
如果开发一套货币系统,那货币的金额一定要是 “ 1 分钱” 的整数倍,而不是有 “ 0.01 元” 这样的数据,所有的数据都要用整型表示,显示的时候再加上小数点,否则便会出现麻烦。
</p>