写在前面
因为经常收到一些新手程序员的邀请,并且问题重复率很高。经过一番思考,我决定在空闲时间写一些相关的文章,以后遇到相关的问题可以直接发文章链接,从而高效的解决问题,另外希望也起到一些科普的作用。这系列的文章中我刻意的没有深入到问题的细节中,而是以一个“大概差不多”的标准来写作,让解答尽量短、新手向以及不枯燥。但这样必然会导致内容的精细度下降,比如说不会涉及到极端情况,或者有一些数据不够精确等等,毕竟不可兼得。同时也不希望有人因此来杠,当然,欢迎友好的讨论、补充和纠错。
这篇文章中整合了关于小数型(浮点数)精度相关的日经问题。
通用知识
在大部分编程语言中,小数型的存储都遵循于IEEE754二进制浮点数算术标准[1],通常我们称它们为浮点数。常用的浮点数类型有float
与double
,前者占用4字节空间,后者占用8字节,当然后者的表示范围和精度都更高。(在比较简单的语言中,可能只提供一种浮点型。)
是的,当然有表示范围。毕竟就这么几个字节,能存多少位数嘛。整数范围本文不谈,小数范围,毕竟数学上小数是无限多的,比如0~1
这个范围内,有0.1
还有0.01
还有0.001
……还有0.00000000000001
。总之大约来说,float
大约能装到小数点后五六七位差不多,double
能到十五六七位。
“为什么我的运算结果有微小的偏差?为什么两个数字比较的结果反直觉?”
先说第一个问题,通常是在运算之后,发现结果比预期差了一点点。比如下例中0.1+0.2=0.30000000000000004
。
C:Usersmoe001>python
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1+0.2
0.30000000000000004
从结果来看,比预期的多了0.00000000000000004
,该问题源于两个部分。
1.进制引起的循环小数截断
就拿0.1
来举例,十进制下干干净净,它就是0.1
。但计算机内部都是二进制,在二进制下,它是一个循环小数——0.000 1100 1100 ... 1100
,后面1100
部分无限循环。没什么奇怪的,想象如果有之所谓的π进制,那我们现在所有的整数转换过去,不都是无限小数。
而浮点型存储这样的数,当然只能精确到某一位,把后面的截断掉,这导致我们输入的0.1
,实际上计算机内部只能是一个近似值,准确来说,是0.1000000000000000055511151231257827021181583404541015625
。[2]
可以看到近似值在一定位数(有效精度)之后,出现了一些很乱的数字,这些数字通常情况下没什么影响,毕竟我们知道这东西有效位数,后面的截断不要,只保留十七位小数,0.10000000000000000
着实还是0.1
。
2.运算引起的误差放大
上一节最后提到了截断掉“乱码区”,看上去是个不错的办法。但这些数字进行计算的时候,“乱码区”却是同样参与计算的,这留下了隐患。详细分析最初的例子:
0.1
的近似值是0.1000000000000000055511151231257827021181583404541015625
0.2
的近似值是0.200000000000000011102230246251565404236316680908203125
- 其之和的精确值
0.3000000000000000166533453693773481063544750213623046875
- 如同
0.1
没法精确表示,这个数字虽然很长,但是也没他的精确值 - 取其近似值为
0.3000000000000000444089209850062616169452667236328125
最终……保留十七位之后的结果就成了0.30000000000000004
。事情就是这么个事情,为什么说是误差放大呢,是因为两个“乱码区”相加之后,使得运算结果离0.3
更远了,不然也许能取到一个更接近0.3
的近似值。
然后说到第二个问题,懂了上面所说的,其实很简单,一笔带过。
一个类似的例子:
C:Usersmoe001>python
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1+0.2==0.3
False
上节提到0.1+0.2
的结果为0.3000000000000000444089209850062616169452667236328125
,而0.3
的近似值,是0.299999999999999988897769753748434595763683319091796875
,因其更接近0.3
。最终,这两个数自然是不相等的。所以,浮点型不要去判断相等或者不相等,通常的做法是判断两个数的差值是否小于某个阈值,比如说0.001
,是的话就认为相等。
参考
- ^IEEE 754 https://en.wikipedia.org/wiki/IEEE_754
- ^如果深究的话,这个数来自1100110011001100110011001100110011001100110011001101(二进制)除以2的55次方,是的,浮点型内部就是这样存放数字的。