Что является более точным, x**.5 или math.sqrt(x)?

Я недавно обнаружил, что x**.5 а также math.sqrt(x) не всегда дают одинаковый результат в Python:

Python 2.6.1 (r261:67517, Dec 4 2008, 16:51:00) [MSC v.1500 32 bit (Intel)]
on win32
>>> 8885558**.5 - math.sqrt(8885558)
-4.5474735088646412e-13

Проверяя все целые числа ниже 10**7, два метода дали разные результаты для почти точно 0,1% выборок, причем размер ошибки увеличивался (медленно) для больших чисел.

Таким образом, вопрос в том, какой метод является более точным?

9 ответов

Решение

Ни один из них не является более точным, они оба расходятся от фактического ответа в равных частях:

>>> (8885558**0.5)**2
8885557.9999999981
>>> sqrt(8885558)**2
8885558.0000000019

>>> 2**1023.99999999999
1.7976931348498497e+308

>>> (sqrt(2**1023.99999999999))**2
1.7976931348498495e+308
>>> ((2**1023.99999999999)**0.5)**2
1.7976931348498499e+308

>>> ((2**1023.99999999999)**0.5)**2 - 2**1023.99999999999
1.9958403095347198e+292
>>> (sqrt(2**1023.99999999999))**2 - 2**1023.99999999999
-1.9958403095347198e+292

http://mail.python.org/pipermail/python-list/2003-November/238546.html

Модуль math оборачивает математические функции библиотеки C платформы с такими же именами; math.pow() наиболее полезно, если вам нужна (или просто хотите) высокая совместимость с расширениями C, вызывающими C pow(),

__builtin__.pow() является реализацией инфикса Python ** оператор и имеет дело с комплексными числами, неограниченными целочисленными степенями, а также модульным возведением в степень (C pow() не справляется ни с одним из них).

** является более полным. math.sqrt вероятно, это просто реализация C sqrt, которая, вероятно, связана с pow,

И функция pow, и функция math.sqrt() могут вычислять результаты, которые являются более точными, чем то, что может сохранять тип с плавающей запятой по умолчанию. Я думаю, что ошибки, которые вы видите, являются результатом ограничений математики с плавающей запятой, а не неточностей функций. Кроме того, когда возникает разница в ~10^(-13) при получении квадратного корня из 7-значного числа? Даже самые точные физические вычисления редко требуют много значащих цифр...

Другая причина использования math.sqrt() заключается в том, что его легче читать и понимать, что, как правило, является хорошей причиной для того, чтобы делать что-то определенным образом.

Использование decimal чтобы найти более точные квадратные корни:

>>> import decimal
>>> decimal.getcontext().prec = 60
>>> decimal.Decimal(8885558).sqrt()
Decimal("2980.86531061032678789963529280900544861029083861907705317042")

Каждый раз, когда вам предоставляется выбор между двумя функциями, встроенными в язык, более конкретная функция почти всегда будет равна или лучше, чем универсальная (поскольку, если бы она была хуже, кодеры просто реализовали бы ее в терминах общей функции). Sqrt более специфичен, чем обобщенное возведение в степень, поэтому вы можете ожидать, что это лучший выбор. И это, по крайней мере, с точки зрения скорости. С точки зрения точности, вы не имеете дело с достаточной точностью в своих числах, чтобы иметь возможность узнать.

Примечание: чтобы уточнить, sqrt быстрее в Python 3.0. Это медленнее в старых версиях Python. См. Измерения Дж.Ф. Себастьяна в разделе " Что быстрее в Python: x**.5 или math.sqrt(x)"?,

Это должно быть что-то вроде платформы, потому что я получаю разные результаты:

Python 2.5.1 (r251:54863, Jan 13 2009, 10:26:13) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
>>> 8885558**.5 - math.sqrt(8885558)
0.0

Какую версию Python вы используете и какую ОС?

Я предполагаю, что это как-то связано с продвижением и кастингом. Другими словами, поскольку вы делаете 8885558**.5, 8885558 должен быть переведен в число с плавающей точкой. Все это обрабатывается по-разному в зависимости от операционной системы, процессора и версии Python. Добро пожаловать в удивительный мир арифметики с плавающей точкой.:-)

У меня та же проблема с вами в Win XP Python 2.5.1, а у меня нет в 32-битной Gentoo Python 2.5.4. Это вопрос реализации библиотеки C.

Теперь на Win, math.sqrt(8885558)**2 дает 8885558.0000000019, в то время как (8885558**.5)**2 дает 8885557.9999999981, которые, кажется, равны тому же эпсилону.

Я говорю, что нельзя сказать, какой вариант лучше.

В теории math.sqrt должен иметь более высокую точность, чем math.pow. См. Метод Ньютона для вычисления квадратных корней [0]. Однако ограничение количества десятичных цифр числа с плавающей точкой Python (или C double), вероятно, замаскирует разницу.

[0] http://en.wikipedia.org/wiki/Integer_square_root

Я не получаю такое же поведение. Возможно, ошибка зависит от платформы? На amd64 я получаю это:

Python 2.5.2 (r252: 60911, 10 марта 2008 г., 15:14:55) 
[GCC 3.3.5 (propolice)] на openbsd4
Введите "помощь", "авторское право", "кредиты" или "лицензия" для получения дополнительной информации.
>>> импорт математики
>>> math.sqrt(8885558) - (8885558**.5)
0.0
>>> (8885558**.5) - math.sqrt(8885558)
0.0

Я выполнил тот же тест и получил те же результаты, 10103 различий из 10000000. Это было использование Python 2.7 в Windows.

Разница заключается в округлении. Я полагаю, что когда два результата различаются, это только одна ULP, которая является наименьшей возможной разницей для поплавка. Истинный ответ лежит между двумя, но float не имеет возможности точно представить его, и оно должно быть округлено.

Как отмечено в другом ответе, decimal Модуль может быть использован для получения большей точности, чем float, Я использовал это, чтобы получить лучшее представление об истинной ошибке, и во всех случаях sqrt был ближе, чем **0.5, Хотя не сильно!

>>> s1 = sqrt(8885558)
>>> s2 = 8885558**0.5
>>> s3 = decimal.Decimal(8885558).sqrt()
>>> s1, s2, s3
(2980.865310610327, 2980.8653106103266, Decimal('2980.865310610326787899635293'))
>>> s3 - decimal.Decimal(s1)
Decimal('-2.268290468226740188598632812E-13')
>>> s3 - decimal.Decimal(s2)
Decimal('2.2791830406379010009765625E-13')
Другие вопросы по тегам