浮点数(包括 C 中的 float 和 double )由两部分表示,两者都有固定数量的位来保存它们的值:
- 称为尾数的二进制分数(二进制点左侧没有位,并且二进制点右侧没有零)。(这与数字的十进制表示进行比较。小数点左侧的数字可以与二进制点左侧的位进行比较,小数点右侧的小数位可以与小数二进制位进行比较二进制点的右边)。
- 一个指数,它告诉 2 乘以该尾数的幂。(将此与科学记数法 0.1e5 的指数相比较,即 5 表示尾数乘以 10 的幂)
在十进制中,我们不能用固定的小数位数来表示分数 1/3。例如,0.333333 并不完全等于 1/3,因为 3 需要无限重复。
在二进制中,我们不能用固定数量的小数位来表示小数 1/10。在这种情况下,二进制数 0.00011001100110011 并不完全等于 1/10,因为 0011 需要无限重复。因此,当 1/10 转换为浮点数时,这部分会被截断以适应可用位。
在二进制中,任何分母(底部)可被 10 整除的分数都是无限重复的。这意味着很多浮点值是不精确的。
添加时,它们是不精确的。如果将它们中的许多加在一起,则不精确性可能会消除或加强,具体取决于当我们将无限重复的二进制分数转换为具有固定位数的二进制分数时被截断的位中的值。
大数字、包含大量数字的分数或添加非常不同的数字时,您也会变得不精确。例如,10 亿加 0.0000009 无法用可用位数表示,因此小数会四舍五入。
你可以看到它变得复杂。在任何特定情况下,您都可以提出浮点表示,评估由于切掉的位和乘法或除法时的舍入导致的错误。到那时,如果您遇到麻烦,您就可以确切地看到为什么它是错误的。
简化示例 - 不精确的表示
这是一个忽略指数并且尾数未归一化的示例,这意味着不会删除左侧的零。(0.0001100 = 1/10 和 0.0011001 = 1/20 在 7 位后被斩波)请注意,在实际情况下,问题发生在右边更多的数字:
0.0001100 = 1/10
0.0001100
0.0001100
0.0001100 0.0011001 = 2/10 (1/5)
0.0001100 0.0011001
0.0001100 0.0011001
--------- ---------
00 <- sum of right 2 columns 11 <- sum of right column
11000 <- sum of next column 00 <- sum of next two columns
110 <- sum of next column 11 <- sum of next column
000 <- sum of other columns 11 <- sum of next column
------- 000 <- sum of other columns
0.1001000 <- sum ---------
0.1001011 <- sum
对于不适合我示例的 7 位的 0.12345678901234567890 之类的分数,我们可能会遇到同样的问题。
该怎么办
首先,请记住浮点数可能不准确。加法或减法,甚至更多,乘法或除法应该会产生不精确的结果。
其次,在比较两个浮点(或双精度)值时,最好将差异与一些“epsilon”进行比较。因此,如果,天堂禁止,您将美元计算存储在浮点变量中,它会看起来像这样。我们不关心任何低于半美分的东西:
如果 (fabsf(f1 - f2) >= 0.005f) ...
这意味着这些数字彼此接近,并且为了您的目的,足够接近。(@EricPostpischil 指出没有“足够接近”的一般定义。它与您的计算希望完成的事情有关。)
与某个小值进行比较会处理一些浮点运算发生后可能位于低小数位中的所有松散位。
请注意,如果您将其与常量进行比较,它看起来很相似:
if (fabsf(f1 - 1.0f) >= 0.000001f) ...
或者您可以进行两次比较来检查相同范围的差异:
if (f1 < 0.999999f || f1 > 1.000001f) ...
我应该再次指出,每个问题都有自己有趣的小数位数。
例如,如果 Google 告诉您地球上两个位置的距离以千米为单位,您可能会关注最近的米,因此您会说 0.001(千分之一公里)内的任何两个位置在功能上是相同的。将差异与 0.0005 进行比较。或者您可能只关心最近的街区,因此将差异与 0.03(300 米)进行比较。因此,将差异与 0.015 进行比较。
当您的测量工具非常准确时,同样的情况也适用。如果你用尺子测量,不要期望结果精确到 1/100 英寸。