为什么 sqrt(x*x + y*y) != math.hypot(x, y) 在 Python 3.8 中?
Why is sqrt(x*x + y*y) != math.hypot(x, y) in Python 3.8?
我正在尝试 运行 在闪亮的新 Python 3.8 上进行一些测试,并注意到 math.hypot
存在问题。来自文档:
For a two dimensional point (x, y)
, this is equivalent to computing
the hypotenuse of a right triangle using the Pythagorean theorem,
sqrt(x*x + y*y)
.
但是,这些在 3.8 中并不等效:
>>> from math import hypot, sqrt
>>> x, y = 95, 168
>>> sqrt(x*x + y*y), hypot(x, y), sqrt(x*x + y*y) == hypot(x, y)
(193.0, 193.00000000000003, False)
>>> sqrt(x*x + y*y).is_integer(), hypot(x, y).is_integer()
(True, False)
在 3.7 中,两种方式产生完全相同的结果("193.0"
,它被认为是一个整数)。
函数hypot
提供另一个数学表达式的近似值√(x2 + y 2), 就像浮点表达式 sqrt(x*x + y*y)
是同一个数学表达式的近似值。
推荐使用函数hypot
,因为它解决了浮点计算sqrt(x*x + y*y)
中存在的非常明显的缺陷,这些缺陷具有非常大或非常小的值。例如,如果 x
仅比最大有限浮点值的平方根大一点,则 sqrt(x*x + y*y)
总是产生 +inf
,因为 x*x
产生 +inf
.
比较:
>>> x, y = 95E200, 168E200
>>> sqrt(x*x + y*y), hypot(x, y)
(inf, 1.93e+202)
>>> z, t = 95E-200, 168E-200
>>> sqrt(z*z + t*t), hypot(z, t)
(0.0, 1.93e-198)
对于这两对(分别是非常大和非常小的)输入,hypot
做得很好,而 sqrt(x*x + y*y)
是灾难性的错误。
当原始版本 sqrt(x*x + y*y)
工作得相当好时(当值 x
和 y
既不是很大也不是很小时),它可能或多或少准确比函数 hypot
取决于 x
和 y
的值。可以预期它们都会产生与数学结果相差几个 ULP 的结果。但由于它们是通过不同方法获得的不同近似值,它们可能会有所不同(在最坏的情况下相差两倍“几个 ULP”)。
hypot(x, y)
的一个典型实现是,如果需要,首先交换 x
和 y
,使 x
具有最大幅度,然后计算 x * sqrt(1 + (y/x)*(y/x))
.这解决了 x*x
溢出的问题。作为副作用,这意味着 即使没有溢出 ,结果也与 sqrt(x*x + y*y)
.
略有不同
请注意,当您将 sqrt(x*x + y*y)
应用于小整数(就像您在测试中所做的那样)时,sqrt(x*x + y*y)
更精确是正常的:当 x
和 y
是小整数时, x*x
和 y*y
以及它们的总和可以精确地计算为浮点值。如果此和是整数的平方,则浮点函数 sqrt
只能计算此整数。简而言之,在这种情况下,尽管是浮点计算,但计算从头到尾都是准确的。相比之下,上面典型的 hypot
实现从计算 x/y
(在你的测试中,95.0/168.0
)开始,这个结果通常不能精确地表示为浮点值。第一步已经产生了一个近似值,这个近似值可能会导致最终结果错误(因为它在您的测试中)!
hypot
没有标准算法:只希望计算数学表达式 √(x2 + y[=68= 的良好近似值]2) 同时避免了上溢和下溢问题。 This article 显示了不同的实现,并指出我提到的流行实现牺牲了精度以避免上溢和下溢(但文章还提供了 hypot
的浮点实现,即 比 sqrt(x*x + y*y)
更准确,即使在 sqrt(x*x + y*y)
有效的地方也是如此。
我正在尝试 运行 在闪亮的新 Python 3.8 上进行一些测试,并注意到 math.hypot
存在问题。来自文档:
For a two dimensional point
(x, y)
, this is equivalent to computing the hypotenuse of a right triangle using the Pythagorean theorem,sqrt(x*x + y*y)
.
但是,这些在 3.8 中并不等效:
>>> from math import hypot, sqrt
>>> x, y = 95, 168
>>> sqrt(x*x + y*y), hypot(x, y), sqrt(x*x + y*y) == hypot(x, y)
(193.0, 193.00000000000003, False)
>>> sqrt(x*x + y*y).is_integer(), hypot(x, y).is_integer()
(True, False)
在 3.7 中,两种方式产生完全相同的结果("193.0"
,它被认为是一个整数)。
函数hypot
提供另一个数学表达式的近似值√(x2 + y 2), 就像浮点表达式 sqrt(x*x + y*y)
是同一个数学表达式的近似值。
推荐使用函数hypot
,因为它解决了浮点计算sqrt(x*x + y*y)
中存在的非常明显的缺陷,这些缺陷具有非常大或非常小的值。例如,如果 x
仅比最大有限浮点值的平方根大一点,则 sqrt(x*x + y*y)
总是产生 +inf
,因为 x*x
产生 +inf
.
比较:
>>> x, y = 95E200, 168E200
>>> sqrt(x*x + y*y), hypot(x, y)
(inf, 1.93e+202)
>>> z, t = 95E-200, 168E-200
>>> sqrt(z*z + t*t), hypot(z, t)
(0.0, 1.93e-198)
对于这两对(分别是非常大和非常小的)输入,hypot
做得很好,而 sqrt(x*x + y*y)
是灾难性的错误。
当原始版本 sqrt(x*x + y*y)
工作得相当好时(当值 x
和 y
既不是很大也不是很小时),它可能或多或少准确比函数 hypot
取决于 x
和 y
的值。可以预期它们都会产生与数学结果相差几个 ULP 的结果。但由于它们是通过不同方法获得的不同近似值,它们可能会有所不同(在最坏的情况下相差两倍“几个 ULP”)。
hypot(x, y)
的一个典型实现是,如果需要,首先交换 x
和 y
,使 x
具有最大幅度,然后计算 x * sqrt(1 + (y/x)*(y/x))
.这解决了 x*x
溢出的问题。作为副作用,这意味着 即使没有溢出 ,结果也与 sqrt(x*x + y*y)
.
请注意,当您将 sqrt(x*x + y*y)
应用于小整数(就像您在测试中所做的那样)时,sqrt(x*x + y*y)
更精确是正常的:当 x
和 y
是小整数时, x*x
和 y*y
以及它们的总和可以精确地计算为浮点值。如果此和是整数的平方,则浮点函数 sqrt
只能计算此整数。简而言之,在这种情况下,尽管是浮点计算,但计算从头到尾都是准确的。相比之下,上面典型的 hypot
实现从计算 x/y
(在你的测试中,95.0/168.0
)开始,这个结果通常不能精确地表示为浮点值。第一步已经产生了一个近似值,这个近似值可能会导致最终结果错误(因为它在您的测试中)!
hypot
没有标准算法:只希望计算数学表达式 √(x2 + y[=68= 的良好近似值]2) 同时避免了上溢和下溢问题。 This article 显示了不同的实现,并指出我提到的流行实现牺牲了精度以避免上溢和下溢(但文章还提供了 hypot
的浮点实现,即 比 sqrt(x*x + y*y)
更准确,即使在 sqrt(x*x + y*y)
有效的地方也是如此。