Что является более точным, x **. 5 или math.sqrt (x)? - PullRequest
22 голосов
/ 09 мая 2009

Я недавно обнаружил, что 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 ]

25 голосов
/ 09 мая 2009

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

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

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

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

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

11 голосов
/ 09 мая 2009

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

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

7 голосов
/ 09 мая 2009

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

>>> import decimal
>>> decimal.getcontext().prec = 60
>>> decimal.Decimal(8885558).sqrt()
Decimal("2980.86531061032678789963529280900544861029083861907705317042")
4 голосов
/ 09 мая 2009

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

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

3 голосов
/ 09 мая 2009

У меня та же проблема с вами на 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, что, похоже, равноценно эпсилону.

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

3 голосов
/ 09 мая 2009

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

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. Добро пожаловать в удивительный мир арифметики с плавающей точкой. : -)

1 голос
/ 09 мая 2009

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

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

1 голос
/ 09 мая 2009

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

Python 2.5.2 (r252:60911, Mar 10 2008, 15:14:55) 
[GCC 3.3.5 (propolice)] on openbsd4
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> math.sqrt(8885558) - (8885558**.5)
0.0
>>> (8885558**.5) - math.sqrt(8885558)
0.0
0 голосов
/ 24 сентября 2013

Я выполнил тот же тест и получил те же результаты, 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')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...