Неточный логарифм в Python - PullRequest
9 голосов
/ 31 мая 2009

Я ежедневно работаю с Python 2.4 в своей компании. Я использовал универсальную функцию логарифма 'log' из стандартной математической библиотеки, и когда я ввел log (2 ** 31, 2), она вернула 31.000000000000004, что показалось мне немного странным.

Я сделал то же самое с другими способностями 2, и это сработало отлично. Я запустил 'log10 (2 ** 31) / log10 (2)' и получил раунд 31.0

Я попытался запустить ту же оригинальную функцию в Python 3.0.1, предполагая, что она была исправлена ​​в более продвинутой версии.

Почему это происходит? Возможно ли, что в Python есть некоторые неточности в математических функциях?

Ответы [ 8 ]

47 голосов
/ 31 мая 2009

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

Если это на самом деле имеет значение, используйте десятичный тип Python .

Пример:

from decimal import Decimal, Context
ctx = Context(prec=20)
two = Decimal(2)
ctx.divide(ctx.power(two, Decimal(31)).ln(ctx), two.ln(ctx))
20 голосов
/ 31 мая 2009

Вы должны прочитать «Что должен знать каждый компьютерщик об арифметике с плавающей точкой».

http://docs.sun.com/source/806-3568/ncg_goldberg.html

17 голосов
/ 31 мая 2009

Всегда предположим, что в операциях с плавающей запятой будет некоторая ошибка, и проверим на равенство с учетом этой ошибки (либо процентное значение, например, 0,00001%, либо фиксированное значение, например, 0,00000000001). Эта неточность является заданной, поскольку не все десятичные числа могут быть представлены в двоичном виде с фиксированной точностью до числа битов.

Ваш конкретный случай не является одним из них, если Python использует IEEE754, поскольку 31 должен легко представляться даже с одинарной точностью. Однако возможно, что он теряет точность в одном из многих шагов, необходимых для вычисления log 2 2 31 , просто потому, что у него нет кода для обнаружения особых случаев, таких как прямая мощность из двух.

5 голосов
/ 31 мая 2009

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

В целом, неверно полагать, что операции с плавающей запятой точны, особенно с одинарной точностью. Раздел "Проблемы точности" из статьи в Википедии:)

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

IEEE двойные числа с плавающей запятой имеют 52 бит точности . Начиная с 10 ^ 15 <2 ^ 52 <10 ^ 16, двойное число имеет от 15 до 16 значащих цифр. Результат 31,000000000000004 верен до 16 цифр, поэтому он настолько хорош, насколько вы можете ожидать. </p>

2 голосов
/ 05 февраля 2019

float неточны

Я не покупаю этот аргумент, потому что точная мощность двух представлена ​​точно на большинстве платформ (с базовой плавающей точкой IEEE 754).

Итак, если мы действительно хотим, чтобы log2 с точной степенью 2 был точным, мы можем.
Я продемонстрирую это в Squeak Smalltalk, потому что в этом языке легко изменить базовую систему, но язык не имеет особого значения, вычисления с плавающей запятой универсальны, а объектная модель Python не так уж далека от Smalltalk.

Для ведения журнала в базе n существует функция log:, определенная в Number, которая наивно использует неперианский логарифм ln:

log: aNumber 
    "Answer the log base aNumber of the receiver."
    ^self ln / aNumber ln

self ln (возьмите неперианский логарифм получателя), aNumber ln и / - это три операции, которые будут округлять результат до ближайшего числа с плавающей точкой, и эти ошибки округления могут накапливаться ... Так что наивная реализация зависит от наблюдаемая вами ошибка округления, и я предполагаю, что реализация функции журнала в Python не сильно отличается.

((2 raisedTo: 31) log: 2) = 31.000000000000004

Но если я изменю определение следующим образом:

log: aNumber 
    "Answer the log base aNumber of the receiver."
    aNumber = 2 ifTrue: [^self log2].
    ^self ln / aNumber ln

предоставить общий log2 в классе Number:

log2
    "Answer the base-2 log of the receiver."
    ^self asFloat log2

и это уточнение в классе Float:

log2
    "Answer the base 2 logarithm of the receiver.
    Care to answer exact result for exact power of two."
    ^self significand ln / Ln2 + self exponent asFloat

, где Ln2 - это константа (2 ln), тогда я получаю точный log2 для точной степени двух, потому что значимое такого числа = 1,0 (включая субнормальное для показателя пика / значимости и определения) и 1.0 ln = 0.0 .

Реализация довольно тривиальна и должна без труда переводиться в Python (возможно, в VM); стоимость времени выполнения очень дешевая, поэтому важно только то, насколько важной мы считаем эту функцию или нет.

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

2 голосов
/ 31 мая 2009

Это нормально. Я ожидал бы, что log10 будет более точным, чем log (x, y), поскольку он точно знает, какова база логарифма, также может быть некоторая аппаратная поддержка для вычисления логарифмов с основанием-10.

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

Esentation repr (float.__repr__) числа в python пытается вернуть строку цифр, максимально приближенную к действительному значению при обратном преобразовании, учитывая, что арифметика IEEE-754 точна до предела. В любом случае, если вы print отредактируете результат, вы не заметите:

>>> from math import log
>>> log(2**31,2)
31.000000000000004
>>> print log(2**31,2)
31.0

print преобразует свои аргументы в строки (в данном случае, с помощью метода float.__str__), который учитывает неточность, отображая меньше цифр:

>>> log(1000000,2)
19.931568569324174
>>> print log(1000000,2)
19.9315685693
>>> 1.0/10
0.10000000000000001
>>> print 1.0/10
0.1

обычно бесполезный 'ответ очень полезен, на самом деле:)

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