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

Eug*_*ash 5 python math floating-point 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)
Run Code Online (Sandbox Code Playgroud)

在3.7中,两种方法都产生完全相同的结果("193.0",它被认为是整数)。

Pas*_*uoq 13

该函数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)
Run Code Online (Sandbox Code Playgroud)

对于这两个(分别非常大和非常小的)输入对,hypot它做的很好,而sqrt(x*x + y*y)灾难性的是错误的。


当幼稚的版本sqrt(x*x + y*y)效果相当好(当值x以及y既不是非常大的,也非常小),也可以是可能比功能或多或少准确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*xy*y和它们的和可以精确计算浮点值。如果此和是整数的平方,则浮点函数sqrt只能计算该整数。简而言之,在这种情况下,尽管是浮点运算,但计算从头到尾都是精确的。相反,hypot以上的典型实现是从计算开始的x/y(在测试中为95.0/168.0),并且此结果通常不能完全表示为浮点值。第一步已经产生了一个近似值,这个近似值可能导致最终结果是错误的(就像在测试中一样)!


没有标准算法可用于hypot:仅期望计算出数学表达式?(x 2 + y 2)的近似值,同时避免了上溢和下溢问题。这篇文章显示了不同的实现,并指出了流行的实现,我提到的牺牲精度以避免溢出和下溢(但文章也提供了浮点执行情况hypot更准确sqrt(x*x + y*y)即使sqrt(x*x + y*y)作品)。