我遇到了
math.cos()
(Python 3.11.0)的一些奇怪行为:
>>> import math
>>> math.cos(math.pi) # expected to get -1
-1.0
>>> math.cos(math.pi/2) # expected to get 0
6.123233995736766e-17
我怀疑浮点数学可能会在其中发挥作用,但我不确定如何。如果是这样,我假设 Python 只是检查参数是否等于
math.pi/2
开始。
我找到了Jon Skeet 的这个答案,他说:
基本上,当您的输入不能表示为精确的二进制值时,您不应该期望二进制浮点运算完全正确 - pi/2 不能,因为它是非理性的。
但如果这是真的,那么
math.cos(math.pi)
也不应该起作用,因为它也使用了 math.pi
近似值。我的问题是:为什么只有在使用math.pi/2
时才会出现这个问题?
math.pi
与 π 中的任何错误(总是有一些)在一种情况下影响很小math.cos(math.pi)
并且在 math.cos(math.pi/2)
中非常重要。
曲线平坦
当
math.cos(x)
非常接近1.0时,曲线非常平坦:斜率“接近”零。大约 4700 万 浮点数 x
接近 π 的值在数学上具有 cos(x)
大于 -1.0,但它们的值比下一个可编码值 -0.99999999999999988897... 更接近 -1.0
曲线的斜率为1
x
接近 π 和 math.cos(x/2)
接近 0.0,余弦曲线有一个 |斜率 | “接近”一个。下一个较小的和下一个较大的可编码x
都有不同的cos(x/2)
.
结论
当|结果|
sin(x)/cos(x)
接近 1.0,many 附近 x
值将报告 1.0.
即使某些
x
值非常接近 π,这也是正确的。
对于 π 附近的
x
(如 math.pi
)和 y = |cos(x)|
,我们需要大约两倍的 y
精度才能看到 x
中的不精确性。
math.cos(math.pi)
的结果会有类似程度的不准确,但是-1.0 + 6e-17
不能用浮点精度表示,所以你只能得到-1.0
。这可以这样证明:
>>> -1.0000_0000_0000_001
# -1.000000000000001
>>> -1.0000_0000_0000_0001
# -1.0
两个原因:
我想你知道这一点,但值得重复:你没有取 π/2 的余弦。 我知道你试过了,但你没有。你取了 1.5707963267948965579989817342720925807952880859375 的余弦值,它非常接近 π/2,但不是 exactly π/2。而这个数的余弦,正如
math.cos
告诉你的那样,是一个接近但不完全等于0的小数。如果你能更准确地表示π/2,你可以获得更接近0的结果,但在现在在某些情况下,您不能比这更准确地表示 π/2:IEEE-754 浮点数具有有限的精度,而 1.570…375 是您能做的最好的。
在计算机编程中(就像在一般生活中一样),当你做错事时,有时你可以侥幸逃脱。当某些东西不能保证工作时,这并不意味着它一定不会工作。当你取 cos(π) 时,你得到了“幸运”,你得到了 1.0 的答案,即使在那种情况下你实际上取了 cos(3.141592653589793115997963468544185161590576171875)。 (如果你玩得更精确,你会发现 cos(3.141…875) 实际上大约是 -0.999999999999999999999999999999925,但这个数字也不可表示,四舍五入为 1.0。)
事实上,如果你深入挖掘,这些结果并不是随机的。其他答案解释了为什么,从数学上讲,即使您不能准确地表示 π 或 π/2,当取其中一个的余弦时,您可以获得比另一个更接近的结果。