Почему модуль math возвращает неправильный результат?

Первый тест

A = 12345678917
print 'A =',A
B = sqrt(A**2)
print 'B =',int(B)

< Сильный > Результат

A = 12345678917
B = 12345678917

Здесь результат правильный.

Второй тест

A = 123456758365483459347856
print 'A =',A
B = sqrt(A**2)
print 'B =',int(B)

< Сильный > Результат

A = 123456758365483459347856
B = 123456758365483467538432

Здесь результат неверен.

Почему это так?

3
Denis Leonov 9 Янв 2017 в 18:39

4 ответа

Лучший ответ

Поскольку math.sqrt(..) сначала приводит число к с плавающей запятой и с плавающей запятой имеют ограниченный mantisse : он может правильно представлять только часть числа. Так что float(A**2) не равен A**2. Затем он вычисляет math.sqrt, что также приблизительно правильно.

Большинство функций, работающих с плавающей запятой, никогда не будут полностью корректными по отношению к своим целочисленным аналогам. Вычисления с плавающей запятой практически неотъемлемы.

Если вычислить A**2, получим:

>>> 12345678917**2
152415787921658292889L

Теперь, если кто-то преобразует его в float(..), он получает:

>>> float(12345678917**2)
1.5241578792165828e+20

Но если вы сейчас спросите, равны ли эти два:

>>> float(12345678917**2) == 12345678917**2
False

Таким образом, информация была потеряна при преобразовании ее в число с плавающей точкой.

Подробнее о том, как работают поплавки и почему они являются приблизительными, можно прочитать в статье в Википедии о IEEE-754 , формальное определение того, как работают числа с плавающей запятой.

4
Willem Van Onsem 9 Янв 2017 в 15:44

То, как числа с плавающей запятой хранятся в памяти, делает вычисления с ними подверженными небольшим ошибкам, которые, тем не менее, могут быть значительными, когда требуются точные результаты. Как упоминалось в одном из комментариев, библиотека decimal может помочь вам здесь:

>>> A = Decimal(12345678917)
>>> A
Decimal('123456758365483459347856')
>>> B = A.sqrt()**2
>>> B
Decimal('123456758365483459347856.0000')
>>> A == B
True
>>> int(B)
123456758365483459347856

Я использую версию 3.6, в которой нет жестко заданного ограничения на размер целых чисел. Я не знаю, приведет ли в 2.7 приведение B к int к переполнению, но decimal невероятно полезно вне зависимости от этого.

0
Isaac Saffold 9 Июл 2017 в 18:28

документация для математического модуля гласит: «Он предоставляет доступ к определенным математическим функциям по стандарту С ". В нем также говорится: «За исключением случаев, когда явно указано иное, все возвращаемые значения являются числами с плавающей запятой».

Они вместе означают, что параметр функции квадратного корня является значением с плавающей запятой. В большинстве систем это означает значение с плавающей запятой, которое умещается в 8 байтов, что в языке Си называется "double". Ваш код преобразует ваше целочисленное значение в такое значение перед вычислением квадратного корня, а затем возвращает такое значение.

Однако 8-байтовое значение с плавающей точкой может хранить по адресу большинство от 15 до 17 значащих десятичных цифр. Это то, что вы получаете в своих результатах.

Если вы хотите повысить точность в своих квадратных корнях, используйте функцию, которая гарантированно даст полную точность для целочисленного аргумента. Просто выполните поиск в Интернете, и вы найдете несколько. Те, кто обычно делают вариации метода Ньютона-Рафсона, повторяют и в конце концов получают правильный ответ. Имейте в виду, что это значительно медленнее, чем функция sqrt математического модуля.

Вот рутина, которую я изменил из Интернета. Я не могу привести источник прямо сейчас. Эта версия также работает для нецелочисленных аргументов, но просто возвращает целочисленную часть квадратного корня.

def isqrt(x):
    """Return the integer part of the square root of x, even for very
    large values."""
    if x < 0:
        raise ValueError('square root not defined for negative numbers')
    n = int(x)
    if n == 0:
        return 0
    a, b = divmod(n.bit_length(), 2)
    x = (1 << (a+b)) - 1
    while True:
        y = (x + n//x) // 2
        if y >= x:
            return x
        x = y
4
Rory Daulton 9 Янв 2017 в 15:51

Если вы хотите вычислить sqrt действительно больших чисел и вам нужны точные результаты, вы можете использовать {{X0 } } :

import sympy

num = sympy.Integer(123456758365483459347856)

print(int(num) == int(sympy.sqrt(num**2)))
2
Willem Van Onsem 9 Янв 2017 в 15:54