6

我正在尝试在闪亮的新 Python 3.8 上运行一些测试,并注意到math.hypot. 从文档:

对于二维点(x, y),这相当于使用勾股定理计算直角三角形的斜边, 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"被认为是整数)。

4

1 回答 1

14

该函数hypot提供了数学表达式 √(x 2 + 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)是灾难性的错误。


当 naïve 版本sqrt(x*x + y*y)工作得相当好时(当值xy既不是很大也不是很小时),它可能比函数更准确,hypot具体取决于 和 的xy。可以预期它们都会产生与数学结果相差几个 ULP 的结果。但由于它们是通过不同方法获得的不同近似值,它们可能会有所不同(在最坏的情况下,相差两倍“几个 ULP”)。

一个典型的实现hypot(x, y)是首先交换xy如果有必要的话,它x具有最大的量级,然后计算x * sqrt(1 + (y/x)*(y/x)). 这解决了x*x溢出的问题。作为副作用,这意味着即使没有溢出,结果也与 略有不同sqrt(x*x + y*y)

请注意,sqrt(x*x + y*y)将其应用于小整数时通常会更精确(就像您在测试中所做的那样):何时xy是小整数,x*x并且y*y它们的总和可以精确地计算为浮点值。如果这个和是一个整数的平方,浮点函数sqrt只能计算这个整数。简而言之,在这种情况下,尽管是浮点计算,但从头到尾都是精确的。相比之下,hypot上面的典型实现是从计算x/y(在您的测试中95.0/168.0)开始的,并且这个结果通常不能精确地表示为浮点值。第一步已经产生了一个近似值,这个近似值可能导致最终结果错误(就像在您的测试中一样)!


没有标准算法hypot:只希望计算数学表达式 √(x 2 + y 2 ) 的良好近似值,同时避免上溢和下溢问题。本文展示了不同的实现,并指出我提到的流行实现会牺牲准确性以避免上溢和下溢(但本文还提供了一个浮点实现,hypot它甚至比where更准确)。sqrt(x*x + y*y)sqrt(x*x + y*y)

于 2019-10-15T15:16:09.870 回答