为什么「0.1+0.2!=0.3」,而「0.1+0.3==0.4」

我们都知道潮汐现象,上学的时候老师多半简单解释一句「月球引力所致」就算了,而我们也都觉得自己明白了,但是凡事就怕琢磨:如果涨潮仅仅是月球对地球万有引力的作用结果的话,那么每天同一个地点,应该仅仅在距离月球最近引力最强的时候有一次涨潮才对,但是住在海边的人都知道,同一个地点,每天会有两次涨潮,为什么?

我抛出这个问题并不是我转行搞物理学了,而是我发现很多司空见惯的问题,如果深究的话,你就会发现很多人根本就没搞懂。浮点数运算就是这样一个问题,每个人都知道浮点数运算有精度损失,但是为什么「0.1+0.2!=0.3」,而「0.1+0.3==0.4」:

float

除了含含糊糊的精度损失,你能给出更有营养的解释么?让我们看看到底是为什么!

首先,让我们举一个整数的例子,比如:

十进制「13」:1*(10 ^1) + 3(10^0) = 10 + 3 = 13 二进制「1101」:1*(2^3) + 1*(2^2) + 0*(2^1) + 1*(2^0) = 8 + 4 + 0 + 1 = 13

接着,让我们再举一个小数的例子,比如:

十进制「0.625」:6*(10^-1) + 2*(10^-2) + 5*(10^-3) = 0.625 二进制「0.101」:1*(2^-1) + 0*(2^-2) + 1*(2^-3) = 5/8 = 0.625

最重要的一点是你要明白计算机是如何表示小数的:比如二进制的「0.1111111」,无非就是十进制的「1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128」,不过细心的你可能已经发现问题了,计算机这种处理小数的方式存在精度损失的,比如一个十进制的「0.1」,换算成分数的话就是十进制的「1/10」,对比前面的结果,你会发现计算机没办法精确表示它,只能近似等于二进制的「0.00011」,也就是十进制的「1/16 + 1/32 = 3/32」,当然二进制小数点后可以多取几位,可惜结果是只能无限趋近,但永远不可能等于。

下面看看为什么「0.1 + 0.2 != 0.3」,而「0.1 + 0.3 == 0.4」。既然存在精度损失,那么「0.1 + 0.2 != 0.3」也说得过去,我们推算一下为什么「0.1 + 0.3 == 0.4」:

十进制的「0.1」近似等于二进制「0.00011」 十进制的「0.3」近似等于二进制「0.01001」 十进制的「0.4」近似等于二进制「0.01100」

于是,十进制的「0.1 + 0.3」也就是二进制的「0.00011 + 0.01001」:

  0.00011
+ 0.01001
---------
  0.01100

不多不少,答案正好是 0.4!也就是说,虽然有精度损失,但是刚刚好碰巧抵消了彼此的误差。希望大家阅读完本文之后,能够彻底搞清楚浮点数运算的相关问题,如果还有不清楚的地方,推荐阅读:IEEE 754 和 THE FLOATING-POINT GUIDE。

文章来源:

Author:老王
link:https://blog.huoding.com/2019/08/23/769