0、1 + 0.68 不等于 1.68
我们先来看一行简单的代码:
a = 1 + 0.68
b = 1.68
is_equal = (a == b)
print("{}=={}is{}".format(a, b, is_equal))
逻辑上,上面的代码的输出结果应该为:
1.68 == 1.68 is True
然而,实际上真正的输出结果是:
1.6800000000000002 == 1.68 is False
你是否在实际的开发过程中遇到类似的问题,并因为付出了大量的DEBUG时间,最终才发现这么一个违反直觉的现象。
我们可以信手就举几个违反直觉的、看似相等,实则不想等的例子:
0.1 + 0.1 + 0.1 == 0.3 # False
0.99 * 3 == 2.97 # False
事实上,这个问题是所有编程语言都会遇到的问题,它都不能算是一个BUG,而算是一个非常正常的现象。
那么问题来了:我们怎么才能比较两个浮点数是否想等呢?
我们将在本文的最后回答这个问题。
1、浮点数的精度问题
我们都知道,计算机是以二进制的形式保存数据的。无论是整数还是浮点数,都不例外。举个例子,十进制浮点数字0.125的二进制表达式为:
0.001
# 0.125 = 0*2 + 0/2 + 0/4 + 1/8
# 0. 0 0 1
如果你看不懂上面的转换,你应该先了解一下十进制浮点数与二进制浮点数之间的转换,再来阅读本文。本文是在假设你已经了解这个规则的基础上编写的。
我们知道,当我们用十进制小数表达1/3的时候,它是一个无穷循环的小数。
# 1/3 的十进制小数表达:
0.33333333333333333333333333...
我们无法穷举所有的3,所以没有办法通过穷举再准确地表达1/3,而只是通过尽量多的穷举,去更接近的表达(近似表达)。
同样的,计算在表达一个数值(二进制)的时候,也是通过只能通过穷举一定的位数,来达到表达一个浮点数的效果。当计算机表述一个64位的浮点数时,一般是用10位来表达指数,用54位来表达精度。所以,当一个小数的位数超过54的时候,计算机就无法精准的表达,而只能取其近似值。
举个例子,十进制数0.1的二进制表达式为:
0.0001100110011001100110011001100110011001100110011...
如果是64位的浮点数,那么计算机只能取小数点后54位的精度。
我们先来看一段代码:
a = 0.1
print(a)
print("{:.20f}".format(a))
其它结果为:
0.1
0.10000000000000000555
第一个打印的结果,看似正确,事实上只是一个表象;这是计算机为我们做了尽量接近原数值的推算和展示(这个超过本文的讨论范围,不深入讨论)。
当我们把精度取到20位的时候(第二行结果),我们就会出现计算机在存储0.1的这数值的时候,其实并不是准确的存储了0.1,而是存储了一个近似值。正如我们前面所提到的,用有限位的二进值小数,并没有办法精确的表达十进制小数0.1。
2、如何比较两个浮点数是否相等
很遗憾,我们的答案是:不能。
这是一个非常重要的计算机常识。
如果你尝试通过==来判定两个浮点数是否相等,你有可能就陷入了第一节所讨论的陷阱。所以,除非你非常明确自己的意图,否则你千万不要这么做。
但是判定两个浮点数是否相等,却是一个非常普通而常见的操作,我们应该怎么做呢?
答案是:取近似值。
当我们直接用==判定两个64位浮点数(注意我们这里提到了位数)是否相等时,事实上是对比两个浮点数在54位的精度上(与小数点后54不是同一个概念)的近似值是否相等。
同理,当你判定两个浮点数是否相等时,你应该取一个适当的精度。而这个精度应该根据你实际功能的需要决定。
回到我们第0节所提到的1.68的问题,当我们取小数点后15位以内时,这两个数值就可以判定为近似相等。
a = 1.68
b = 1 + 0.68
print("a={:.20}".format(a))
print("b={:.20}".format(b))
eps = 0.00001
if abs(a - b) < eps:
print("a == b")
上面代码的打印结果为:
a=1.6799999999999999378
b=1.6800000000000001599
a == b
浮点数计算一个很专业和复杂的问题,我这里只是抛砖引玉,简单地介绍,如果你感兴趣,可以专门去了解一下。这里有几个链接仅各位参考一下:https://docs.python.org/3/tutorial/floatingpoint.htmldocs.python.orgBinary numbers – floating point conversionblog.penjee.com