我希望这仅在尾数的最后一位为时才成立0
。否则,为了减去它们(因为它们的指数相差 1),x
首先会损失一些精度,结果最终要么向上舍入,要么向下舍入。
但一个快速实验表明,对于任何随机数(包括带有 aa 尾随位的数) ,它似乎总是成立(假设x
和是有限的) 。2x
1
import random
import struct
from collections import Counter
def float_to_bits(f: float) -> int:
"""
Convert a double-precision floating-point number to a 64-bit integer.
"""
# Pack the float into 8 bytes, then unpack as an unsigned 64-bit integer
return struct.unpack(">Q", struct.pack(">d", f))[0]
def check_floating_point_precision(num_trials: int) -> float:
true_count = 0
false_count = 0
bit_counts = Counter()
for _ in range(num_trials):
x = random.uniform(0, 1)
if 2 * x - x == x:
true_count += 1
else:
false_count += 1
bits = float_to_bits(x)
# Extract the last three bits of the mantissa
last_three_bits = bits & 0b111
bit_counts[last_three_bits] += 1
return (bit_counts, true_count / num_trials)
num_trials = 1_000_000
(bit_counts, proportion_true) = check_floating_point_precision(num_trials)
print(f"The proportion of times 2x - x == x holds true: {proportion_true:.6f}")
print("Distribution of last three bits (mod 8):")
for bits_value in range(8):
print(f"{bits_value:03b}: {bit_counts[bits_value]} occurrences")
The proportion of times 2x - x == x holds true: 1.000000
Distribution of last three bits (mod 8):
000: 312738 occurrences
001: 62542 occurrences
010: 125035 occurrences
011: 62219 occurrences
100: 187848 occurrences
101: 62054 occurrences
110: 125129 occurrences
111: 62435 occurrences
当你手工进行算术运算时,你就会明白为什么这总是成立。是的,在最低有效位中你有时会这样做
xyz10 - wxyz1
。但你也会减去最高有效位,所以在底部有空间容纳完整的精度。这个属性被称为Sterbenz 引理。(该链接提供了更长、更正式的证明。)如果我们只能以浮点格式进行算术运算,即使在算术过程中对内部值进行运算,那么,是的,
2*x - x
并不总是会产生x
。例如,对于四位有效数字,我们可以有:x
2*x
2*x - x
= 1.001•2 1 − 0.100•2 1(右移操作数并丢失一些位)
= 0.101•2 1 = 1.010•2 0(10)。
但是,浮点实现的工作方式并非如此。为了获得正确的结果,它们要么在内部使用更多数字,要么使用更复杂的算法,或者两者兼而有之。IEEE-754 规定,基本运算的结果是通过计算精确的实数算术结果(没有错误或舍入或数字限制)然后使用该运算有效的舍入规则将该实数舍入为目标格式可表示的最接近值而得到的值。(最常见的是,该舍入规则是舍入到最接近的值,打破平局,选择有效数字中位数较低的值。)
这一要求的结果是,如果数学结果可以按照该格式表示,则计算结果必须是该数学结果。当数学结果可以表示时,正确实施的基本运算中绝不会有任何舍入误差。
让 x 和 y 成为浮点数
x
和所表示的实数值2*x
。乘以 2 是精确的:尾数保持不变,只是指数增加 1。所以 y = 2x。然后减去。浮点减法并不总是会导致实数差,因为那可能无法表示为浮点数。但它被指定为给出最接近的可以表示为浮点数的值(或在平局的情况下给出最接近的值)。在这里,真正的差异是 yx = 2x-x = x,并且它可以表示为浮点数,因为那是x
我们开始时的。在您的另一种情况下
4*x - 3*x
,乘以 4 再次是精确的(指数只是增加了 2)。但乘以 3 通常不是。当尾数为奇数时,乘以 3 将需要新尾数中多一位。那一位丢失了,您将得到一个浮点数,其值要么略小于要么略大于 的 3 倍x
。因此,当您减去时,两个值中的一个可能已经“不准确”。减法也可能导致“不准确”的整体结果,或者减法也可能不准确并且恰好抵消了之前的不准确性,因此您恰好得到x
。但问题是3*x
,而不是减法。当3*x
也是精确的时,就像在这种2*x - x
情况下一样,所有操作都是精确的,并且整体结果恰好是x
。减法如何进行计算,使结果成为最接近浮点表示值?我认为通常在计算过程中会有一些额外的位或技巧。但我不认为标准定义了实现应如何计算更正的结果,它只关心它们“是否”这样做。