双精度浮点数中,“0.00x <= 0.001 * x”是否总是成立?

3

例子:

0.008 == 0.001 * 8
0.009  < 0.001 * 9
0.035 == 0.001 * 35
0.036  < 0.001 * 36

我也尝试过以下模式:

0.0x   <= 0.01   * x
0.000x <= 0.0001 * x

我已在MATLAB和C中测试了数千个。

我的问题:

  1. 如果它始终成立,为什么?如果不是,有无反例?

  2. 如果它始终成立,下面的模式是否正确?

    0.0000000000000x <= 0.0000000000001 * x ; for arbitrary zeros


显然,对于任意的零值,这是错误的,因为浮点类型没有无限精度。放置足够多的零和0.000....01*x将导致0。 - phuclv
你改变了舍入模式吗? - Rick James
2个回答

3

.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。接下来,对于小整数的 xxdouble 中是完全可表示的,因此将该操作数转换为 double 不会产生舍入误差。我们几乎满足了 .00000x <= .000001 * x,因为左侧包含一个进位,而右侧包含一个舍去。但是,乘法也可能有一个进位,这将使我们的准备工作无效。再次测试几个 x 可能会找到一个不会发生这种情况的示例——如果乘法不是精确的,那么它是否会进行进位或舍位基本上取决于 x 的某个位,因此几次试验就足以找到一个可用的示例。
回到原始比例,.001 而不是 .000001,我们可以说对于所有小于 253x.00x <= .001 * x 成立。这是因为所有这样的整数 x 都可以在 double 中完全表示,并且将 .001 转换为 double 是四舍五入的,从而产生 0.001000000000000000020816681711721685132943093776702880859375。因此,即使左侧 .00x 舍去,右侧也包含一个舍入,只有通过乘法中的舍去才能补偿,因为在将 x 转换为 double 时没有舍入。由于每个舍入只能移动到下一个可表示的值,不能跳过任何值,因此乘法中的舍去不能使右侧低于左侧。所以对于所有小于 253x.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的倍数的情况下,可能存在一些不寻常的交互作用,本答案未探讨这些情况。


0

这不完全是您所要求的,因为0.00x不是数学运算。但也许考虑以下相关问题会很有趣:

x是IEEE754双精度数字时,x / 100 == 0.01 * x是否成立?

即使忽略NaN的明显情况,这个等式也不成立。如下所示:

Prelude> 0.9033208460939007 / 100
9.033208460939007e-3
Prelude> 0.9033208460939007 * 0.01
9.033208460939008e-3

我使用了Haskell/ghci提示符,但你可以在几乎所有支持IEEE754二进制浮点运算的语言中复制此操作。

请注意,结果在最后一位上是不同的。

毫无疑问,由于舍入和有限精度,几乎没有任何“显而易见”的浮点相等性成立。从某种意义上说,这是有意设计的;浮点数是一种用有限的存储空间表示“实数”的方法,足够有用,但不应用于代数运算:例如,+*等的结合律就是一个经典案例,它对于浮点数来说根本不成立,但你会期望它适用于任何“数学”概念。


OP并不是在问是否相等,他们知道它不相等。他们在问它是否总是相等或更小,还是有时可能更大。 - GSerg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接