Что является более точным, 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, вызывающими Cpow()
,
__builtin__.pow()
является реализацией инфикса Python**
оператор и имеет дело с комплексными числами, неограниченными целочисленными степенями, а также модульным возведением в степень (Cpow()
не справляется ни с одним из них).
** является более полным. 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), вероятно, замаскирует разницу.
Я не получаю такое же поведение. Возможно, ошибка зависит от платформы? На 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')