为什么 0.1 + 0.2 不等于 0.3 ?
十进制小数在高级程序设计语言中属于浮点类型数据,既然十进制小数存储到内存中有误差,所以计算机中的浮点型数值的计算是不精确的,这种问题 对于所有支持浮点数运算的编程语言都是存在的。
先看两个简单但诡异的代码:
1 | 0.1 + 0.2 > 0.3 // true |
0.1
加 0.2
为什么就不等于 0.3
昵?要回答这个问题,得先了解计算机内部是如何表示数的。
内存中数的存储
我们知道,计算机中任何类型的数据,其本质是以二进制的形式存储的。
[1] –> 计算机内部如何表示整数
这里以十进制数 13
来展示 “按位计数法” 如何表示整数:
十进制值 | 进制 | 按位格式 | 描述 |
---|---|---|---|
13 | 10 | 13 | 1x10^1 + 3x10^0 = 10 + 3 |
13 | 2 | 1101 | 1x2^3 + 1x2^2 + 0x2^1 + 1x2^0 = 8 + 4 + 0 + 1 |
[2] –> 计算机内部如何表示小数
再来看,十进制小数 0.625
怎么用 “按位计数法” 表示:
十进制值 | 进制 | 按位格式 | 描述 |
---|---|---|---|
0.625 | 10 | 0.65 | 6x10^-1 + 2x10^-2 + 5x10^-3 = 0.6 + 0.02 + 0.005 |
0.65 | 2 | 0.101 | 1x2^-1 + 0 x2^-2 + 1x2^-3 = 1/2 + 0 + 1/8 |
[3] –> 如何用二进制表示 0.1
最终回归到本篇开头的样例,先来看如何用二进制表示 0.1
???
我们知道,十进制整数转二进制方法:除 2 取余,逆序排列;十进制小数转二进制方法:乘 2 取整,顺序排列。
十进制 0.1
转换成二进制,乘 2
取整过程:
1 | 0.1 * 2 = 0.2 # 0 |
从上面可以看出,0.1
的二进制格式是:0.000110011....
。这是一个二进制无限循环小数,但计算机内存有限,我们不能用储存所有的小数位数。
也就是说 >>>> 将十进制小数转化为其它进制小数时,存在无限问题。 >>>> 那么在精度与内存间如何取舍呢?
答案是:在某个精度点直接舍弃。
当然,代价就是,0.1
在计算机内部根本就不是精确的 0.1
,而是一个有舍入误差的 0.1
。当代码被编译或解释后,0.1
已经被四舍五入成一个与之很接近的计算机内部数字,以至于 计算还没开始,一个很小的舍入错误就已经产生了,并且这种误差会在后续的计算过程中不断累计。
这也就是 0.1 + 0.2
不等于 0.3
的根本原因。
有误差的两个数,其计算的结果,很可能 与我们期望的不一样了。
注意前面的这句话中的 “很可能” 这三个字?为啥是很可能昵?
0.1 + 0.1 为什么等于 0.2 ?
答案是:两个有舍入误差的值在求和时,相互抵消了,但这种“负负得正,相互抵消”不一定是可靠的,当这两个数字是用不同长度数位来表示的浮点数时,舍入误差可能不会相互抵消。
又如,对于 0.1 + 0.3
,结果其实并不是 0.4
,但 0.4
是最接近真实结果的数,比其它任何浮点数都更接近。许多语言也就直接显示结果为 0.4
了,而不展示一个浮点数的真实结果了。
另外要注意,二进制能精确地表示位数有限且分母是 2 的倍数的小数。比如 0.5
(0.25、0.625…),0.5
在计算机内部就没有舍入误差。所以 0.5 + 0.5 === 1
。
胡乱舍入,能满足所有的计算需求吗
我们看两个现实的场景:
- 对于一个修建铁路的工程师而言,10 米宽,还是 10.0001 米宽并没有什么不同。铁路工程师就不需要这么高 0.x 这样的精度;
- 对于芯片设计师,0.0001 米就会是一个巨大不同,他也永远不用处理超过 0.1 米距离。
不同行业,要求的精度不是线性的,我们允许(对结果无关紧要的)误差存在。 10.0001
与 10.001
在铁路工程师看来都是合格的。
虽然允许误差存在,但在使用浮点数进行计算或逻辑处理时,不注意,就可能出问题。记住,永远不要直接比较两个浮点的大小。
为什么 0.1 + 0.2 不等于 0.3 ?
https://www.orangeshare.cn/2016/07/29/wei-shi-me-0-1-0-2-bu-deng-yu-0-3/
install_url
to use ShareThis. Please set it in _config.yml
.