7

I'm trying to run some tests on the shiny new Python 3.8 and noticed an issue with math.hypot. From the docs:

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).

However, these are not equivalent in 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)

In 3.7 both ways produce exactly the same result ("193.0", which is considered an integer).

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
  • From a pure math standpoint they are the same, but the interpreter will call them different based on the precision of the method. –  Oct 15 '19 at 15:11
  • 2
    I don't think this is a good duplicate to redirect to. There is a lot of specific information regarding `hypot` that can be provided here, whereas most of the explanations in the so-called duplicate do not apply to this question. – Pascal Cuoq Oct 15 '19 at 15:15
  • 1
    @PascalCuoq there's one for C++: [When to use `std::hypot(x,y)` over `std::sqrt(x*x + y*y)`](https://stackoverflow.com/q/32435796/995714) – phuclv Oct 16 '19 at 07:46
  • 3
    To the question of what changed in Python 3.8, the answer is here: https://github.com/python/cpython/pull/8474 `hypot` went from accepting 2 arguments to accepting an arbitrary number of arguments, and in the process the 2-argument version changed from a straight C libm call to something more complicated - the two-argument special case of the general machinery. Possibly this should be considered a bug, and 2-argument hypot should be special-cased to use the libm-provided hypot. – Mark Dickinson Oct 16 '19 at 17:25
  • @MarkDickinson On the other hand, there is no guarantee about what the libm-provided `hypot` is, so the new version is more platform-independent at least. – Pascal Cuoq Oct 16 '19 at 20:47
  • Possible duplicate of [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – pppery Oct 25 '19 at 02:30

1 Answers1

16

The function hypot offers another approximation of the mathematical expression √(x2 + y2), just like the floating-point expression sqrt(x*x + y*y) is an approximation of this same mathematical expression.

The function hypot is recommended because it solves very noticeable defects that are present in the floating-point computation sqrt(x*x + y*y) with very large or small values. For instance, if x is only a bit larger than the square root of the maximum finite floating-point value, sqrt(x*x + y*y) always produces +inf because x*x produces +inf.

Compare:

>>> 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)

For these two (respectively very large and very small) pairs of inputs, hypot is doing fine, whereas sqrt(x*x + y*y) is catastrophically wrong.


When the naïve version sqrt(x*x + y*y) works reasonably well (when the values x and y are neither very large nor very small), it can be may be more or less accurate than the function hypot depending on the values of x and y. They can both be expected to produce a result that is a few ULPs away from the mathematical result. But since they are different approximations obtained by different methods, they may differ (in the worst case by twice “a few ULPs”).

One typical implementation for hypot(x, y) is first to swap x and y if necessary so that x has the largest magnitude, and then compute x * sqrt(1 + (y/x)*(y/x)). This solves the problem with x*x overflowing. As a side-effect, it means that even when there is no overflow, the result is slightly different from sqrt(x*x + y*y).

Note that it's normal that sqrt(x*x + y*y) is more precise when you apply it to small integers (as you do in your test): when x and y are small integers, x*x and y*y and their sum can be computed exactly as floating-point values. If this sum is the square of an integer, the floating-point function sqrt can only compute this integer. In short, in this scenario the computations, despite being floating-point, are exact from beginning to end. In contrast, the typical hypot implementation above starts by computing x/y (in your test, 95.0/168.0), and this result is not in general representable exactly as a floating-point value. The first step already incurs an approximation, and this approximation can result in the final result being wrong (as it is in your test)!


There is no standard algorithm for hypot: it is only expected to compute a good approximation of the mathematical expression √(x2 + y2) while avoiding the overflow and underflow problems. This article shows different implementations, and points out that the popular implementation that I mentioned sacrifices accuracy to avoid overflow and underflow (but the article also provides a floating-point implementation for hypot that is more accurate than sqrt(x*x + y*y) even where sqrt(x*x + y*y) works).

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281