这个测试违反了浮点编程的基本规则:不要进行等式比较。
由于浮点数的小数部分只有有限数量的位,因此会出现许多问题。这些问题通常被称为"舍入误差",尽管在大多数情况下它们并不是错误,而是格式限制。
例如,由于我们在编程时使用十进制字符串来表示数字...大多数我们能够写下的数字如果有小数部分,在浮点格式中就没有相应的表示。其二进制小数部分会重复。
这基本上排除了精确比较浮点数,除了整数值之间具有的比较,具有讽刺意味的是,您需要实现一种模糊比较,如 abs(a - b) < epsilon
。
事实上,您的2/9
是一个特殊情况,它既没有一个十进制字符串的有限表示,也没有一个二进制字符串的有限表示!1
为了成功地将2/9
与常量相等地比较,程序、解释器和库需要更高的完美度要求,这是不能保证的。
例如,您需要输入比您实际需要的更多的2
,解释器必须在知道比格式更多精度的情况下舍入常量的低位位。机器在执行操作时确实有一些额外的知识,但在将常量从源代码转换为内部形式时,解释器可能没有这种知识。此外,运行时舍入受到各种选项的影响,像PHP这样的语言甚至可能没有明确指定不可表示常量从源代码到内部形式是如何进行舍入的。
实际上情况比这更糟,因为十进制字符串中每个0.2/10n分量也没有确切的二进制等价物。因此,非常有可能完美而忠实地转换0.22222222222222实际上并不等于对实际的2/9进行最佳努力表示的结果。在任何特定(有限)位数中,你无法将最接近2/9的确切二进制分数表示为有限的十进制字符串。
(我们一定要有一个标准答案,关于不使用浮点数进行相等比较。)
1.
每个机器分数都是x/2n形式的有理数。现在,这些常数是十进制的,每个十进制常数都是x/(2n*5m)形式的有理数。5m是奇数,所以它们中没有一个具有2n因子。只有当m == 0时,在分数的二进制和十进制扩展中才有一个有限的表示。例如,1.25
是精确的,因为它是5/(22*50),但0.1
不是,因为它是1/(20*51)。对于有理数2/9
,既没有2n因子,也没有5m因子。