Арифметика с плавающей точкой: возможно ли небезопасное использование конкретного сравнения? - PullRequest
5 голосов
/ 15 января 2012

Следующий код Python вычисляет количество итераций, чтобы сделать вещи, основываясь на некоторых переменных.

  # a - b - c is always a multiple of d.
  i = (a - b - c) / d
  while i:
    # do stuff
    i -= 1

Все переменные будут одного типа, то есть только ints или floats или что-то еще.Меня беспокоит, будет ли это работать правильно, если значения floats.Я знаю достаточно, чтобы всегда учитывать ловушки, полагаясь на точные значения с плавающей точкой.Но я не могу сказать, опасно ли вышесказанное или нет.Я могу использовать i = int(round((a - b - c) / d)), но мне любопытно, как лучше понимать числа с плавающей точкой.

Все сводится к следующему: a - b - c является точным кратным d.Поэтому я полагаюсь на (a-b-c)/d, чтобы получить значение i, из которого я могу вычесть 1 и получить ожидаемое количество итераций в цикле while с подразумеваемым предположением, что i == 0 становится истинным.То есть можно ли уменьшить вычисленные кратные значения на 1, чтобы достигнуть ровно 0?

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

Ответы [ 3 ]

4 голосов
/ 15 января 2012

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

>>> from decimal import Decimal
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')

Обратите внимание, что Python 2.7 изменил способЧисла с плавающей запятой пишутся (как работает repr(f)), так что теперь она показывает самую короткую строку, которая даст такое же число с плавающей запятой, если вы сделаете float(s).Это означает, что repr(0.3) == '0.3' в Python 2.7, но repr(0.3) == '0.29999999999999999' в более ранних версиях.Я упоминаю об этом, поскольку это может еще больше запутать ситуацию, когда вы действительно захотите увидеть, что стоит за числами.

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

>>> (Decimal(2.0) - Decimal(1.1)) / Decimal(0.3) - Decimal(3) 
Decimal('-1.85037170771E-16')

Здесь мы можем ожидать (2.0 - 1.1) / 0.3 == 3.0, но есть небольшая ненулевая разница.Однако, если вы выполняете вычисления с обычными числами с плавающей запятой, вы получите ноль:

>>> (2 - 1.1) / 0.3 - 3
0.0
>>> bool((2 - 1.1) / 0.3 - 3)
False

Результат округляется где-то по пути, поскольку 1.85e-16 не равен нулю:

>>> bool(-1.85037170771E-16)
True

Я не уверен точно, где происходит это округление.

Что касается завершения цикла в целом, то я могу предложить одну подсказку: для чисел с плавающей запятой менее 2 53, IEEE 754 может представлять все целые числа :

>>> 2.0**53    
9007199254740992.0
>>> 2.0**53 + 1
9007199254740992.0
>>> 2.0**53 + 2
9007199254740994.0

Пробел между представленными числами составляет 2 от 2 53 до 2 54 , какпоказано выше.Но если ваше i является целым числом, меньшим 2 53 , то i - 1 также будет представимым целым числом, и вы в конечном итоге наберете 0.0, что в Python считается ложным.

3 голосов
/ 15 января 2012

Я дам вам независимый от языка ответ (я действительно не знаю Python).

В вашем коде много потенциальных проблем. Во-первых, это:

(a - b - c)

Если a (например) 10 9 , а b и c равны 1, то ответ будет 10 9 , а не 10 9 -2 (я предполагаю, что здесь с плавающей точкой одинарной точности).

Тогда есть это:

i = (a - b - c) / d

Если числитель и знаменатель являются числами, которые не могут быть точно представлены в плавающей запятой (например, 0,3 и 0,1), то результат может быть не точным целым числом (это может быть 3,0000001 вместо 3). Следовательно, ваш цикл никогда не прекратится.

Тогда есть это:

i -= 1

Как и выше, если i в настоящее время 10 9 , то результатом этой операции все равно будет 10 9 , поэтому ваш цикл никогда не прекратится.

Поэтому вам настоятельно рекомендуется выполнить все вычисления в целочисленной арифметике.

1 голос
/ 15 января 2012

Вы правы, что может быть не сходимость на нуле (по крайней мере для большего количества итераций, чем вы предполагали)Почему бы вашему тесту не быть: while i >= 1.В этом случае, как и с целыми числами, если значение i падает ниже 1, цикл заканчивается.

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