.000005 <= .000001 * 5
计算结果为false。
Matlab指定double
为IEEE-754 binary64,但C标准并没有指定,尽管许多实现使用它。通常,浮点数使用二进制和十进制。在十进制中,所提出的关系成立,并且实际上两个表达式在格式的界限处相等。(当x
太大以至于将其转换为double
会产生无穷大时,等式不成立。)在其他进制中,类似于本答案中所示的反例可以被找到。1在本答案的剩余部分中,假设使用IEEE-754 binary64,在数字的格式和操作的行为方面都使用该标准。
我们应该了解这样一个表达式如何计算:.000…000x <= .000…0001 * x
:
- 首先,源文本中的每个数字都被转换为
double
。在此转换期间,数字被四舍五入为可表示的值。通常通过四舍五入到最接近的可表示值来完成这种舍入,如果遇到平局,则向最低位(位)为偶数的值靠拢。
- 然后执行乘法,并且结果就好像实数结果被四舍五入为可表示值一样。同样,常规使用最近舍入方法。
- 然后评估比较
<=
。这没有误差;它当且仅当右操作数大于或等于左操作数时产生true。
我假设x
是非负数。对于负x
,请参见第一个补充说明。
首先,考虑使用最近舍入。
将
.000001
转换为
double
得到的结果是 0.000000999999999999999954748111825886258685613938723690807819366455078125。可以通过良好的 C 实现和
printf(".99f\n", .000001);
来证明这一点。(由于 C 标准在编译时既未完全指定将十进制数字转换为浮点数,也未完全指定
printf
将浮点数转换为十进制数,所以在不适用正确的四舍五入的实现中可能存在差异。)如我们所见,这比 .000001 还要小。通过打印
.1
、
.01
、
.001
等,直到找到一个刚好被舍去的数字,然后测试各种
x
直到找到一个使得
.00000x
刚好被进位的数字即可发现这一点。将
.000005
转换为
double
的结果是 0.0000050000000000000004090152695701565477293115691281855106353759765625。接下来,对于小整数的
x
,
x
在
double
中是完全可表示的,因此将该操作数转换为
double
不会产生舍入误差。我们几乎满足了
.00000x <= .000001 * x
,因为左侧包含一个进位,而右侧包含一个舍去。但是,乘法也可能有一个进位,这将使我们的准备工作无效。再次测试几个
x
可能会找到一个不会发生这种情况的示例——如果乘法不是精确的,那么它是否会进行进位或舍位基本上取决于
x
的某个位,因此几次试验就足以找到一个可用的示例。
回到原始比例,.001 而不是 .000001,我们可以说对于所有小于 2
53 的
x
,
.00x <= .001 * x
成立。这是因为所有这样的整数
x
都可以在
double
中完全表示,并且将
.001
转换为
double
是四舍五入的,从而产生 0.001000000000000000020816681711721685132943093776702880859375。因此,即使左侧
.00x
舍去,右侧也包含一个舍入,只有通过乘法中的舍去才能补偿,因为在将
x
转换为
double
时没有舍入。由于每个舍入只能移动到下一个可表示的值,不能跳过任何值,因此乘法中的舍去不能使右侧低于左侧。所以对于所有小于 2
53 的
x
,
.00x <= .001 * x
都成立。
除此之外,将
x
转换为
double
可能会导致舍入误差,并且容易找到反例(在2的53次方以上的第四个奇数中):
9007199254741.001 <= .001 * 9007199254741001
,将
9007199254741.001
转换为
double
产生
9007199254741.001953125
,将
9007199254741001
转换为
double
产生
9007199254741000
,而右侧评估为
9007199254741
。
如果考虑其他舍入模式:
- 向下舍入或向0舍入时,右侧最多会遭受三次舍入误差下降的影响,因此我们可以预期有许多情况下该关系失败。
- 向上舍入时,右侧必须至少经历一次舍入误差上升,因为在二进制浮点数中
.000…0001
永远不是精确可表示的,而向上舍入规则从不允许右侧抛弃这个误差,左侧只能有一个舍入误差,它永远不会超过右侧的误差,因此该关系必须始终成立。
附录
x
可以为负吗?问题的语法表明不行,因为对于x
= -3,.00x
将变为.00-3
,这不是一个正确的数字。如果我们允许它为-.003
,那么当x
的大小超出将其转换为double
时产生负无穷大,但不太大以至于将.00x
转换为double
会产生负无穷大时,就会出现.00x <= .001 * x
失败的情况。在这种情况下,我们将有限值与负无穷比较,比较结果为false。在double
格式的界限内,值仍保持有限,比较将遭受上述问题的影响,但也会受到舍入规则的某些修改。
请注意,如果.000…0001
足够小,则将其转换为double
可能会产生零(除向上舍入外的任何标准舍入模式),而x
可能足够大,以至于它产生∞,在这种情况下,将它们相乘会产生NaN(或陷阱),并且该关系不成立,因为NaN与数字没有关系。
脚注
1在基数是10的倍数的情况下,可能存在一些不寻常的交互作用,本答案未探讨这些情况。