Почему sqrt (x * x + y * y)! = Math.hypot (x, y) в Python 3.8? - PullRequest
5 голосов
/ 15 октября 2019

Я пытаюсь запустить несколько тестов на новом великолепном 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", который считается целым числом).

1 Ответ

13 голосов
/ 15 октября 2019

Функция 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) катастрофически неправильно.


КогдаНаивная версия 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) является более точным, когдаВы применяете его к маленьким целым числам (как в своем тесте): когда x и y - маленькие целые числа, x*x и y*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).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...