为什么 sqrt(x*x + y*y) != math.hypot(x, y) 在 Python 3.8 中?



我正在尝试在闪亮的新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",它被认为是一个整数(。

函数hypot提供了数学表达式 √(x 2 + y2( 的另一个近似值,就像浮点表达式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)运行良好时(当xy的值既不是很大也不是很小时(,它可能比函数hypot更不准确,具体取决于xy的值。可以预期它们都会产生与数学结果相差几个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)应用于小整数时,更精确是正常的(就像您在测试中所做的那样(:当xy是小整数时,x*xy*y及其总和可以精确地计算为浮点值。如果此总和是整数的平方,则浮点函数sqrt只能计算此整数。简而言之,在这种情况下,计算尽管是浮点数,但从头到尾都是精确的。相比之下,上面典型的hypot实现从计算x/y开始(在您的测试中,95.0/168.0(,并且此结果通常不能完全表示为浮点值。第一步已经产生了近似值,这种近似值可能会导致最终结果错误(就像在您的测试中一样(!


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

相关内容

  • 没有找到相关文章

最新更新