“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 中测试了数千次。
我的问题:
如果它总是成立,为什么?如果不是,有反例吗?
如果总是成立,下面的模式都正确吗?
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。接下来,对于小整数 x
,x
可以在 double
中精确表示,因此将该操作数转换为 double
没有舍入误差。我们几乎满意 .00000x <= .000001 * x
,因为左侧包含向上舍入,而右侧包含向下舍入。但是,乘法也可能有舍入,这会破坏我们的准备工作。同样,测试几个 x
可能会找到一个不发生这种情况的例子——如果乘法不精确,那么它是否恰好向上或向下舍入基本上取决于 [=12= 中某处的单个位], 所以几次试验就足以找到一个有效的例子。
回到原来的规模,.001 而不是 .000001,我们可以说 .00x <= .001 * x
对所有小于 253 的 x
成立。这是因为所有此类整数 x
都可以在 double
中精确表示,并且 .001
到 double
的转换向上舍入,生成 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
会产生 −∞ 但不是太大以至于转换 .00x
到 double
产生 −∞。在这种情况下,我们将有限值与 −∞ 进行比较,比较结果为 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" 数字概念中得到它。
示例:
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 中测试了数千次。
我的问题:
如果它总是成立,为什么?如果不是,有反例吗?
如果总是成立,下面的模式都正确吗?
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。接下来,对于小整数 x
,x
可以在 double
中精确表示,因此将该操作数转换为 double
没有舍入误差。我们几乎满意 .00000x <= .000001 * x
,因为左侧包含向上舍入,而右侧包含向下舍入。但是,乘法也可能有舍入,这会破坏我们的准备工作。同样,测试几个 x
可能会找到一个不发生这种情况的例子——如果乘法不精确,那么它是否恰好向上或向下舍入基本上取决于 [=12= 中某处的单个位], 所以几次试验就足以找到一个有效的例子。
回到原来的规模,.001 而不是 .000001,我们可以说 .00x <= .001 * x
对所有小于 253 的 x
成立。这是因为所有此类整数 x
都可以在 double
中精确表示,并且 .001
到 double
的转换向上舍入,生成 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
会产生 −∞ 但不是太大以至于转换 .00x
到 double
产生 −∞。在这种情况下,我们将有限值与 −∞ 进行比较,比较结果为 false。在 double
格式的范围内,值仍然是有限的,比较会遇到上面讨论的问题,但要对舍入规则进行一些修改。
请注意,如果 .000…0001
足够小,将其转换为 double
可能会产生零(在任何标准舍入模式中,而不是朝向 +∞),并且 x
可能足够大到它产生 ∞,在这种情况下将它们相乘产生 NaN(或陷阱),并且该关系不成立,因为 NaN 与数字无关。
脚注
1 在十的倍数的碱基中,可能存在一些本答案未探讨的不寻常的相互作用。
这不完全是你问的,因为 0.00x
不是数学运算。但也许考虑以下相关问题是有意义的:
Is it true that
x / 100 == 0.01 * x
whenx
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" 数字概念中得到它。