“0.00x <= 0.001 * x”是否始终适用于双倍?

Does "0.00x <= 0.001 * x" always hold for double?

示例:

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

.000005 <= .000001 * 5 计算结果为假。

Matlab specifies double to be IEEE-754 binary64,但 C 标准没有,尽管许多实现都使用它。通常,基数 2 和 10 用于浮点数。在基数 10 中,构成关系成立,实际上这两个表达式在格式的边界内是相等的。 (当 x 太大以至于将其转换为 double 会产生无穷大时,等式不成立。)在其他基础上,可以找到类似于此答案中所示的反例。1 对于此答案的其余部分,假定 IEEE-754 binary64,包括数字格式和操作行为。

我们应该理解 .000…000x <= .000…0001 * x 这样的表达式是如何求值的:

  • 首先,将源文本中的每个数字转换为double。在此转换过程中,数字四舍五入为可表示的值。这种舍入通常是通过舍入到最接近的可表示值来完成的,与偶数位(位)的值有关。
  • 然后执行乘法,结果就好像实数结果四舍五入到一个可表示的值。同样,舍入到最接近的值是常见的。
  • 然后评估比较<=。这没有错误;当且仅当右操作数大于或等于左操作数时,它才产生 true。

我假设 x 是非负数。负数x见第一个附录

首先,考虑使用舍入法。

.000001 转换为 double 得到 0.000000999999999999999954748111825886258685613938723690807819366455078125。这可以在 printf(".99f\n", .000001); 的良好 C 实现中看到。 (由于 C 标准既没有完全指定在编译时将十进制数字转换为浮点数,也没有通过 printf 将浮点数转换为十进制数,因此在实现中可能存在不使用正确舍入到-最近。)正如我们所见,这小于 .000001。这很容易通过打印 .1.01.001 等找到,直到我们找到恰好向下舍入的数字。然后我们测试各种 x 直到我们找到 .00000x 恰好四舍五入的一个。将 .000005 转换为 double 得到 0.0000050000000000000004090152695701565477293115691281855106353759765625。接下来,对于小整数 xx 可以在 double 中精确表示,因此将该操作数转换为 double 没有舍入误差。我们几乎满意 .00000x <= .000001 * x,因为左侧包含向上舍入,而右侧包含向下舍入。但是,乘法也可能有舍入,这会破坏我们的准备工作。同样,测试几个 x 可能会找到一个不发生这种情况的例子——如果乘法不精确,那么它是否恰好向上或向下舍入基本上取决于 [=12= 中某处的单个位], 所以几次试验就足以找到一个有效的例子。

回到原来的规模,.001 而不是 .000001,我们可以说 .00x <= .001 * x 对所有小于 253x 成立。这是因为所有此类整数 x 都可以在 double 中精确表示,并且 .001double 的转换向上舍入,生成 0.001000000000000000020816681711721685132943093776702880859375。因此,即使左侧 .00x 向下舍入,右侧也包含向上舍入,只能通过乘法向下舍入来补偿,因为 [=12] 的转换中没有舍入=] 到 double。由于每次舍入只能移动到下一个可表示值,而不能跳过任何值,因此乘法中的舍入不能使右侧低于左侧。所以 .00x <= .001 * x 对所有 x 小于 253.

成立

在此之上,将x转换为double可能会出现舍入误差,搜索很容易找到反例(在上面第4个奇数253): 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 会产生 −∞ 但不是太大以至于转换 .00xdouble 产生 −∞。在这种情况下,我们将有限值与 −∞ 进行比较,比较结果为 false。在 double 格式的范围内,值仍然是有限的,比较会遇到上面讨论的问题,但要对舍入规则进行一些修改。

请注意,如果 .000…0001 足够小,将其转换为 double 可能会产生零(在任何标准舍入模式中,而不是朝向 +∞),并且 x 可能足够大到它产生 ∞,在这种情况下将它们相乘产生 NaN(或陷阱),并且该关系不成立,因为 NaN 与数字无关。

脚注

1 在十的倍数的碱基中,可能存在一些本答案未探讨的不寻常的相互作用。

这不完全是你问的,因为 0.00x 不是数学运算。但也许考虑以下相关问题是有意义的:

Is it true that x / 100 == 0.01 * x when x is a IEEE754 double-precision number?

即使忽略 NaN 的明显情况,这种等式也不成立。即:

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

(我使用了 Haskell/ghci 提示,但你可以用任何执行 IEEE754 二进制浮点运算的语言复制它,几乎所有语言都在那里。)

注意最后一位的结果有何不同。

不用说,通过舍入和有限精度,几乎没有 "obvious" 等式适用于浮点数。从某种意义上说,这是设计使然;浮点数是一种用有限存储量表示 "real numbers" 的方法,足够有用,但不应用于代数运算:例如 +* 等的关联性, 是一个典型的例子,它不适用于浮点数,但你会从任何 "mathematical" 数字概念中得到它。