Подводные камни числовых значений в Python "Как глубоко?" - PullRequest
3 голосов
/ 22 августа 2010

Я довольно зеленый программист и сейчас изучаю Python.Я дошел до главы 17 в «Учиться мыслить как компьютерный ученый» (Классы и методы), и я только что написал свой первый тестовый документ, который провалился таким образом, которого я действительно не полностью понимаю:

class Point(object):
    '''
    represents a point object.
    attributes: x, y
    '''

    def ___init___(self, x = 0, y = 0):
        '''
        >>> point = Point()
        >>> point.y
        0
        >>> point = Point(4.7, 8.2)
        >>> point.x
        4.7
        '''

        self.x = x
        self.y = y

Второй doctest для __init__ завершается неудачно и возвращает 4.7000000000000002 вместо 4.7.Однако, если я переписываю doctest с оператором «print» следующим образом:

>>> point = Point(4.7, 8.2)
>>> print point.x
4.7

Он работает правильно.

Итак, я прочитал о том, как Python хранит плавающие объекты, и теперь я понимаю, чтоИз-за двоичного представления десятичных чисел причина расхождения заключается в том, что Python хранит 4.7 в виде строки из 1 и 0, которые почти, но не совсем равны 4.7.

Но я не понимаю, почему при вызове «point.x» возвращается 4.7000000000000002, а при вызове «print point.x» возвращается 4.7.При каких других обстоятельствах Python предпочтет округлить, как это происходит с «print»?Как работает это округление?Могут ли эти следы значительных цифр привести к ошибкам в программировании (кроме, очевидно, неудачных тестов)?Может ли отказ обратить внимание на округление создать опасную неоднозначность?

Поскольку это связано с двоичным представлением десятичных чисел, я уверен, что это на самом деле общая проблема CS, а не специфическая для Python,но то, что мне действительно нужно знать прямо сейчас, - это то, что я могу сделать, особенно как программист на Python, чтобы избежать любых связанных с этим проблем и / или заражений багами.

Кроме того, для бонусных баллов есть какой-то другой способ, которымPython может хранить числа с плавающей запятой помимо значений по умолчанию, активируемых строкой типа «a = 4.7»?Я знаю, что есть пакет Decimal, но я не совсем уверен, как он работает.Честно говоря, все эти вещи динамической типизации меня иногда смущают.

Редактировать: Я должен указать, что я использую Python 2.6 (в какой-то момент я хочу использовать NumPy и Biopython)

Ответы [ 5 ]

4 голосов
/ 22 августа 2010
>>> point.x

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

>>> print point.x

1007 * происходит *

3 голосов
/ 22 августа 2010

Это связано с тем, как компьютеры хранят числа с плавающей запятой. Подробное описание этого здесь . Однако для вашего случая быстрое решение - проверить не печатное представление point.x, а если point.x равно 4.7. Итак ...

>>> point = Point(4.7, 8.2)
>>> point.x == 4.7
True

Или лучше:

>>> point = Point(4.7, 8.2)
>>> eps = 2**-53 #get epsilon for standard double precision number
>>> -eps <= point.x - 4.7 <= eps
True

Где eps - максимальное значение ошибок округления в арифметике с плавающей запятой. Подробнее о эпсилоне см. здесь .

РЕДАКТИРОВАТЬ: -eps <= point.x - 4.7 <= eps эквивалентно abs(point.x - 4.7) <= eps. Я только добавляю это, потому что не все знакомы с цепочкой операторов сравнения в Python.

РЕДАКТИРОВАТЬ 2: Поскольку вы упомянули numpy, у numpy есть способ получить eps, не вычисляя его самостоятельно. Используйте eps = numpy.finfo(float).eps вместо 2**-53, если вы используете numpy. Обратите внимание, что эпилон по какой-то причине больше, чем должен быть, и равен 2**-52, а не 2**-53. Я понятия не имею, почему это так.

2 голосов
/ 22 августа 2010

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

a == b if abs(a-b) <= eps, where eps is the required precision.

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

1 голос
/ 22 августа 2010

Это полное руководство объясняет все.

Здесь - специфичные для Python объяснения.

1 голос
/ 22 августа 2010

Вы получаете другое поведение, потому что print усекает числа:

In [1]: 1.23456789012334
Out[1]: 1.23456789012334 
In [2]: print 1.23456789012334
1.23456789012

Обратите внимание, с точностью, используемой в числах с плавающей точкой Python:

In [3]: 4.7 == 4.7000000000000002
Out[3]: True

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

...