равенство с плавающей точкой в Python и в целом

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

Следующая часть кода распечатает 1.0 == 1.0 -> False

Но если Вы заменяете factors[units_from] с 10.0 и factors[units_to ] с 1.0 / 2.54 это распечатает 1.0 == 1.0 -> True

#!/usr/bin/env python

base = 'cm'
factors = {
    'cm'        : 1.0,
    'mm'        : 10.0,
    'm'         : 0.01,
    'km'        : 1.0e-5,
    'in'        : 1.0 / 2.54,
    'ft'        : 1.0 / 2.54 / 12.0,
    'yd'        : 1.0 / 2.54 / 12.0 / 3.0,
    'mile'      : 1.0 / 2.54 / 12.0 / 5280,
    'lightyear' : 1.0 / 2.54 / 12.0 / 5280 / 5.87849981e12,
}

# convert 25.4 mm to inches
val = 25.4
units_from = 'mm'
units_to = 'in'

base_value = val / factors[units_from]
ret = base_value * factors[units_to  ]
print ret, '==', 1.0, '->', ret == 1.0

Позвольте мне сначала сказать, что я вполне уверен, что продолжается здесь. Я видел его прежде в C, просто никогда в Python, но начиная с Python в реализованном в C, мы видим его.

Я знаю, что числа с плавающей точкой изменят значения, собирающиеся от регистра ЦП кэшироваться и отступать. Я знаю, что сравнение, что должно быть двумя равными переменными, возвратит false, если один из них был разбит на страницы, в то время как другой остался резидентный объект в регистре.

Вопросы

  • Что лучший способ состоит в том, чтобы избежать проблем как это?... В Python или в целом.
  • Я делаю что-то полностью неправильно?

Примечание стороны

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

Риторические вопросы

  • Если это - потенциально опасная проблема, так как она заставляет программы вести себя в вопросе undetermanistic, должны компиляторы предупреждать или ошибка, когда они обнаруживают, что Вы проверяете равенство плаваний
  • Компиляторы должны поддерживать опцию заменить все равенство плавающее, сверяется с 'достаточно близко' функцией?
  • Сделайте компиляторы уже делают это, и я просто не могу найти информацию.
16
задан mskfisher 9 May 2012 в 19:17
поделиться

6 ответов

Спасибо за ответы. Большинство из них были очень хорошими и содержали хорошие ссылки, поэтому я просто скажу это и отвечу на свой вопрос.

Каспин опубликовал эту ссылку .

Он также упомянул, что Google Tests использовал сравнение ULP, и когда я взглянул на код Google, я увидел, что они упоминают ту же самую ссылку на cygnus-software.

Я завершил реализацию некоторых алгоритмов на C как расширение Python, а затем обнаружил, что могу сделать это и на чистом Python. Код размещен ниже.

В конце концов, я, вероятно, просто добавлю различия ULP в свой набор уловок.

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

>>> f1 = 25.4
>>> f2 = f1
>>> 
>>> for i in xrange(1, 11):
...     f2 /= 10.0          # to cm
...     f2 *= (1.0 / 2.54)  # to in
...     f2 *= 25.4          # back to mm
...     print 'after %2d loops there are %2d doubles between them' % (i, dulpdiff(f1, f2))
... 
after  1 loops there are  1 doubles between them
after  2 loops there are  2 doubles between them
after  3 loops there are  3 doubles between them
after  4 loops there are  4 doubles between them
after  5 loops there are  6 doubles between them
after  6 loops there are  7 doubles between them
after  7 loops there are  8 doubles between them
after  8 loops there are 10 doubles between them
after  9 loops there are 10 doubles between them
after 10 loops there are 10 doubles between them

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

>>> # 0 degrees Fahrenheit is -32 / 1.8 degrees Celsius
... f = -32 / 1.8
>>> s = str(f)
>>> s
'-17.7777777778'
>>> # floats between them...
... fulpdiff(f, float(s))
0
>>> # doubles between them...
... dulpdiff(f, float(s))
6255L

import struct
from functools import partial

# (c) 2010 Eric L. Frederich
#
# Python implementation of algorithms detailed here...
# from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

def c_mem_cast(x, f=None, t=None):
    '''
    do a c-style memory cast

    In Python...

    x = 12.34
    y = c_mem_cast(x, 'd', 'l')

    ... should be equivilent to the following in c...

    double x = 12.34;
    long   y = *(long*)&x;
    '''
    return struct.unpack(t, struct.pack(f, x))[0]

dbl_to_lng = partial(c_mem_cast, f='d', t='l')
lng_to_dbl = partial(c_mem_cast, f='l', t='d')
flt_to_int = partial(c_mem_cast, f='f', t='i')
int_to_flt = partial(c_mem_cast, f='i', t='f')

def ulp_diff_maker(converter, negative_zero):
    '''
    Getting the ulp difference of floats and doubles is similar.
    Only difference if the offset and converter.
    '''
    def the_diff(a, b):

        # Make a integer lexicographically ordered as a twos-complement int
        ai = converter(a)
        if ai < 0:
            ai = negative_zero - ai

        # Make b integer lexicographically ordered as a twos-complement int
        bi = converter(b)
        if bi < 0:
            bi = negative_zero - bi

        return abs(ai - bi)

    return the_diff

# double ULP difference
dulpdiff = ulp_diff_maker(dbl_to_lng, 0x8000000000000000)
# float  ULP difference
fulpdiff = ulp_diff_maker(flt_to_int, 0x80000000        )

# default to double ULP difference
ulpdiff = dulpdiff
ulpdiff.__doc__ = '''
Get the number of doubles between two doubles.
'''
4
ответ дан 30 November 2019 в 21:45
поделиться

Позвольте мне сначала ответить, сказав, что вам следует прочитать классику Дэвида Голдберга Что должен знать каждый компьютерный ученый об арифметике с плавающей запятой .

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

Согласно модели с плавающей запятой, 2.54 на самом деле представляется как

>>> 2859785763380265 * 2 ** -50
2.54

Это представление, однако, не является точным:

>>> from fractions import Fraction
>>> float(Fraction(2859785763380265, 2 ** 50) - Fraction(254, 100))
3.552713678800501e-17

Теперь, выражение, которое вы оцениваете на самом деле:

>>> 25.4 / 10 * (1/2.54)
0.99999999999999989

Проблема заключается в 1 / 2.54:

>>> Fraction.from_float(1/2.54)
Fraction(1773070719437203, 4503599627370496)

Но что вы ожидаете, так это

>>> 1/Fraction.from_float(2.54)
Fraction(1125899906842624, 2859785763380265)

Чтобы ответить на ваши вопросы:

  • Это сложная проблема, но она явно детерминированная, в ней нет ничего загадочного.
  • Вы не можете автоматически заменить равенство сравнением достаточно близким . Последнее требует, чтобы вы указали допуск, который зависит от решаемой проблемы, то есть от того, какой точности вы ожидаете от своих результатов. Также существует множество ситуаций, когда вам действительно нужно равенство, а не достаточно близкое сравнение.
4
ответ дан 30 November 2019 в 21:45
поделиться

Как лучше всего избежать проблем вот так? ... На Python или вообще.

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

Должны ли компиляторы поддерживать возможность заменить все проверки равенства с плавающей запятой на функция «достаточно близко»?

Если бы это было так, вы бы получили странные результаты:

>>> float.tolerance = 1e-8    # hypothetical "close enough" definition
>>> a = 1.23456789
>>> b = 1.23456790
>>> c = 1.23456791
>>> a == b
True
>>> b == c
True
>>> a == c
False

Если вы думаете, что сейчас достаточно сложно хранить числа с плавающей запятой в словаре, попробуйте использовать нетранзитивную функцию = = оператор! И производительность была бы отстой, потому что единственный способ гарантировать x == y hash (x) == hash (y) - это иметь одинаковый хэш-код для всех чисел с плавающей запятой. И это было бы несовместимо с целыми числами.

1
ответ дан 30 November 2019 в 21:45
поделиться

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

Риторические вопросы

  • Это ** ЯВЛЯЕТСЯ опасной проблемой **, поскольку она может скрыть ошибки или создать бесконечные циклы, если такое сравнение используется в качестве критерия остановки.
  • Современные компиляторы C / C ++ предупреждают о сравнении чисел с плавающей запятой на равенство
  • Все известные мне средства проверки статического кода будут выводить ошибки для языков, которые я использую

Я полагаю, что для python это то же самое, что и дельта, используемая для сравнение может отличаться, выбор должен быть на усмотрение разработчика. Это означает, что невозможно обеспечить полностью автоматическое преобразование по умолчанию.

0
ответ дан 30 November 2019 в 21:45
поделиться

Разница в том, что если вы замените факторов [units_to] на 1.0 / 2.54 , вы сделаете:

(base_value * 1.0) / 2.54

Со словарем, вы делаете:

base_value * (1.0 / 2.54)

Порядок округления имеет значение. Это легче увидеть, если вы это сделаете:

>>> print (((25.4 / 10.0) * 1.0) / 2.54).__repr__()
1.0
>>> print ((25.4 / 10.0) * (1.0 / 2.54)).__repr__()
0.99999999999999989

Обратите внимание, что здесь нет недетерминированного или неопределенного поведения. Существует стандарт IEEE-754, которому реализации должны соответствовать (чтобы не утверждать, что они всегда соблюдают ).

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

Наконец, есть, конечно, варианты арифметики произвольной точности, включая python-gmp и decimal . Подумайте, действительно ли вам нужны они, потому что они действительно сильно влияют на производительность.

Нет проблем с перемещением между обычными регистрами и кешем. Возможно, вы думаете о 80-битной расширенной точности x86.

6
ответ дан 30 November 2019 в 21:45
поделиться

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

Это гораздо легче сказать, чем сделать. Природа плавающей точки делает фиксированную границу ошибки бесполезной. Небольшая граница ошибки (например, 2*float_epsilon) хорошо работает, когда значения близки к 0.0, но потерпит неудачу, если значения близки к 1000. Граница ошибки для значений размером 1 000 000.0 будет слишком слабой для значений, близких к 0.0.

Лучшее решение - знать область математики и выбирать подходящую границу погрешности в каждом конкретном случае.

Когда это непрактично или вы ленивы, Единицы в последнем месте (ULPs) - очень новое и надежное решение. Все детали довольно сложны, вы можете прочитать больше здесь.

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

Тест Google использует ULP для сравнения чисел с плавающей точкой. Они выбрали значение по умолчанию 4 ULP для того, чтобы два числа с плавающей точкой сравнивались одинаково. Вы также можете использовать их код в качестве ссылки для создания собственного компаратора с плавающей точкой в стиле ULP.

7
ответ дан 30 November 2019 в 21:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: