我正在使用基本模数(
%%
)运算符,并且只是努力理解以下行为。我的理解是 %%
返回除 2 个数字后的余数。所以例如:
5 %% 2
# [1] 1
6 %% 2
# [1] 0
因为 5 除以 2 余数为 1,6 除以 2 余数为 0。它也适用于小数,例如:
3.2 %% 0.1
# [1] 0
我还意识到在某些情况下可能会出现浮点问题,例如:
12345.2 %% 0.1
# [1] 4.263256e-14
我认为这是因为 12345.2 的浮点实际上是 12345.2000000000000042632... 没有问题。
我感到困惑的地方在这里:
3.1 %% 0.1
# [1] 0.1
上面为什么有0.1余数?我假设结果为 0(或者如果存在浮点问题则非常接近于零)。我是不是错过了什么?
我预计任何具有 1 个或更少小数点精度的值都会给出 0,但有很多结果似乎与此不一致(请注意,我对输出进行四舍五入只是为了避免像上面那样的“e-14”结果):
data.frame(num = seq.int(3, 5, 0.1)) |>
dplyr::mutate(result = round(num %% 0.1, 4))
# num result
# 1 3.0 0.1
# 2 3.1 0.1
# 3 3.2 0.0
# 4 3.3 0.1
# 5 3.4 0.1
# 6 3.5 0.1
# 7 3.6 0.1
# 8 3.7 0.1
# 9 3.8 0.1
# 10 3.9 0.1
# 11 4.0 0.1
# 12 4.1 0.1
# 13 4.2 0.1
# 14 4.3 0.1
# 15 4.4 0.0
# 16 4.5 0.1
# 17 4.6 0.1
# 18 4.7 0.1
# 19 4.8 0.1
# 20 4.9 0.0
# 21 5.0 0.1
如果我将所有值(数字和模运算符)乘以 10,我会得到我期望的行为:
data.frame(num = seq.int(30, 50, 1)) |>
dplyr::mutate(result = round(num %% 1, 4))
# num result
# 1 30 0
# 2 31 0
# 3 32 0
# 4 33 0
# 5 34 0
# 6 35 0
# 7 36 0
# 8 37 0
# 9 38 0
# 10 39 0
# 11 40 0
# 12 41 0
# 13 42 0
# 14 43 0
# 15 44 0
# 16 45 0
# 17 46 0
# 18 47 0
# 19 48 0
# 20 49 0
# 21 50 0
值得注意的是,我所看到的在 Python 中也是一致的。这显示了相同的行为:
import math
[math.fmod(x/10.0, 0.1) for x in range(30, 50, 1)]
我有什么遗漏的吗?
感谢评论中的人们,我现在可以看到这是如何由浮点(我将缩短为 FP)精度引起的,所以我想添加一个答案,以防它对其他人有用。我最初认为这不可能是 FP 问题,因为返回的结果是 0.1 而不是预期的 0,这比我习惯看到的 FP 错误有更大的差异(除非错误已成倍增加)不知何故,这里的情况并非如此)。
为了解释为什么会发生这种情况,以
0.9 %% 0.1
为例,这是一个输出不是预期的 0 的示例。
0.9 %% 0.1
# [1] 0.1
如果我们仔细观察这两个数字,我们可以看到 FP 精度的作用:
print(0.9, digits = 22)
# [1] 0.9000000000000000222045
print(0.1, digits = 22)
# [1] 0.1000000000000000055511
注意数字排列得很好,所以我们可以看到 0.9 有一个额外的
222045e-22
,而 0.1 有一个额外的 55511e-22
,这要归功于 FP。
我们预计 0.1 会干净地乘以 9 倍,得到 0.9(因此
%%
函数的预期结果为 0)。如果 9 乘以 0.1 的 FP 表示比 0.9 的 FP 稍微小,那么余数将非常接近 0(可能在 1e-15
范围内),并且 R 将返回 0。但是 9 乘以 0.1比 0.9 稍微大,我们可以通过乘以 e-22
部分来看到:
55511 * 9
# 499599
请注意,这比 0.9 获得的 222045 FP“额外位”要大。因此,当 R“尝试”将 0.1 乘以 9 次时,它并不能完全适合 0.9。因此,它必须假设在超过 0.9 之前可以乘以 0.1 的最多次数是 8(特别是 0.9 %/% 0.1
给出 8),这留下了
teeny-tiny比 0.1 少一点。 与任何 FP 问题一样,我讨厌说“哦,那好吧”。不幸的是(至少在我看来)我们必须将其视为计算机执行数学计算方式的限制。只要我们使用浮点运算,我们就需要接受这样的问题将会出现的事实。在我看到 FP 问题之前,我认为我能够在看到它们时发现它们。这件事让我很感动,我相信这不会是最后一次发生。