"Округленное" число с умножением на 0,01 приводит к x.y00000000000001, а не к x.y? - PullRequest
3 голосов
/ 29 марта 2012

Причина, по которой я спрашиваю это, состоит в том, что в OpenERP есть проверка, что она сводит меня с ума:

>>> round(1.2 / 0.01) * 0.01
1.2
>>> round(12.2 / 0.01) * 0.01
12.200000000000001
>>> round(122.2 / 0.01) * 0.01
122.2
>>> round(1222.2 / 0.01) * 0.01
1222.2

Как видите, вторая roundвозвращает нечетное значение.

Может кто-нибудь объяснить мне, почему это происходит?

Ответы [ 4 ]

8 голосов
/ 29 марта 2012

Это на самом деле не имеет ничего общего с round, вы можете стать свидетелем точно такой же проблемы, если просто сделаете 1220 * 0.01:

>>> 1220*0.01
12.200000000000001

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

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

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

Также см .:

Простой пример числовой нестабильности с плавающей точкой: числа конечны. допустим, мы сохранили 4 цифры после точки на данном компьютере или языке. Если умножить 0,0001 на 0,0001, получится что-то ниже 0,0001, и поэтому сохранить этот результат невозможно! В этом случае, если вы вычислите (0,0001 x 0,0001) / 0,0001 = 0,0001, этот простой компьютер не сможет быть точным, потому что он пытается сначала умножить, а потом делить. В javascript деление на дроби приводит к аналогичным неточностям.

5 голосов
/ 29 марта 2012

Используемый тип float хранит двоичные числа с плавающей запятой.Не каждое десятичное число точно представлено как float.В частности, нет точного представления 1,2 или 0,01, поэтому фактическое число, хранящееся в компьютере, будет очень незначительно отличаться от значения, записанного в исходном коде.Эта ошибка представления может привести к тому, что вычисления будут давать немного отличающиеся результаты от точного математического результата.

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

Вы также можете рассмотреть возможность использования типа decimal, в котором хранится десятичное число числа с плавающей запятой.Если вы используете decimal, то 1.2 может быть сохранено точно.Однако работа с decimal снизит производительность вашего кода.Вы должны использовать его только в том случае, если важно точное представление десятичных чисел.Вы также должны знать, что decimal не означает, что у вас никогда не будет проблем.Например, 0.33333 ... не имеет точного представления как decimal.

4 голосов
/ 29 марта 2012

Из-за способа хранения чисел с плавающей запятой теряется точность из-за способа хранения, поэтому вы видите, что это удостоверение не содержит

>>> 12.2 / 0.01 * 0.01 == 12.2
False

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

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

В Python также есть десятичный модуль, который может быть вам полезен

2 голосов
/ 29 марта 2012

Другие ответили на ваш вопрос и отметили, что многие числа не имеют точного двоичного дробного представления.Если вы привыкли работать только с десятичными числами, может показаться очень странным, что хорошее «круглое» число, например 0,01, может быть не заканчивающимся числом в какой-либо другой базе.В духе «видеть - значит верить», вот небольшая программа на Python, которая распечатает двоичное представление любого числа с любым желаемым количеством цифр.

from decimal import Decimal

n = Decimal("0.01")     # the number to print the binary equivalent of
m = 1000                # maximum number of digits to print

p = -1
r = []
w = int(n)
n = abs(n) - abs(w)

while n and -p < m:
    s = Decimal(2) ** p
    if n >= s:
        r.append("1")
        n -= s
    else:
        r.append("0")
    p -= 1

print "%s.%s%s" % ("-" if w < 0 else "", bin(abs(w))[2:], 
                   "".join(r), "..." if n else "")
...