Каков наилучший способ сравнить float для почти равенства в Python?

Хорошо известно, что сравнение float для равенства немного затруднительно из-за проблем округления и точности.

Например: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

Каков рекомендуемый способ справиться с этим в Python?

Неужели для этого где-то есть стандартная библиотечная функция?

Ответ 1

Python 3.5 добавляет функции math.isclose и cmath.isclose, как описано в PEP 485.

Если вы используете более раннюю версию Python, эквивалентная функция приведена в документации .

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

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

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

Ответ 2

Что-то простое, как следующее, недостаточно хорошее?

return abs(f1 - f2) <= allowed_error

Ответ 3

Я бы согласился, что ответ Гарета, вероятно, наиболее уместен в качестве облегченной функции/решения.

Но я думал, что было бы полезно отметить, что если вы используете NumPy или рассматриваете его, для этого есть пакетная функция.

numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)

Небольшой отказ от ответственности: установка NumPy может быть нетривиальным опытом в зависимости от вашей платформы.

Ответ 4

Используйте модуль Python decimal, который предоставляет класс decimal.

Из комментариев:

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

Ответ 5

Я не знаю ничего в стандартной библиотеке Python (или где-либо еще), которая реализует функцию Dawson AlmostEqual2sComplement. Если вы хотите такого поведения, вам придется его реализовать самостоятельно. (В этом случае вместо использования разумных побитовых хаков Доусона вы, вероятно, лучше использовали бы более традиционные тесты формы if abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2 или аналогичные. Чтобы получить поведение, похожее на Доусон, вы можете сказать что-то вроде if abs(a-b) <= eps*max(EPS,abs(a),abs(b)) для некоторого небольшого фиксированного EPS, это не совсем то же самое, что и Доусон, но похожее по духу.

Ответ 6

Общая мудрость, что числа с плавающей запятой не могут сравниваться для равенства, является неточной. Числа с плавающей запятой ничем не отличаются от целых чисел. Если вы оцениваете "a == b", вы получите истинное значение, если они идентичны и неверны в противном случае (при том понимании, что два NaN, конечно, не идентичные числа).

Реальная проблема заключается в следующем: если я сделал некоторые вычисления и не уверен, что два числа, которые мне нужно сравнить, точно верны, то что? Эта проблема одинакова для с плавающей запятой, как и для целых чисел. Если вы оцениваете целочисленное выражение "7/3 * 3", оно не будет сравниваться с "7 * 3/3".

Итак, предположим, мы спросили: "Как сравнить целые числа для равенства?" в такой ситуации. Нет единого ответа; то, что вы должны делать, зависит от конкретной ситуации, особенно от того, какие у вас есть ошибки и чего вы хотите достичь.

Вот несколько возможных вариантов.

Если вы хотите получить "истинный" результат, если математически точные числа будут равны, вы можете попытаться использовать свойства вычислений, которые вы выполняете, чтобы доказать, что вы получаете одинаковые ошибки в двух числах. Если это осуществимо, и вы сравниваете два числа, которые выходят из выражений, которые будут давать равные числа, если они будут вычислены точно, вы получите "истинное" из сравнения. Другой подход заключается в том, что вы можете проанализировать свойства вычислений и доказать, что ошибка никогда не превышает некоторую величину, возможно, абсолютную величину или величину относительно одного из входов или одного из выходов. В этом случае вы можете спросить, отличаются ли два рассчитанных числа на эту сумму, и верните "истину" , если они находятся в пределах интервала. Если вы не можете доказать ошибку, вы можете догадаться и надеяться на лучшее. Один из способов угадывания - оценить множество случайных выборок и посмотреть, какое распределение вы получите в результатах.

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

Если вы хотите получить "ложный" результат, если математически точные числа будут неравными, вам нужно доказать, что ваша оценка чисел дает разные числа, если математически точные числа будут неравными. Это может быть невозможно для практических целей во многих распространенных ситуациях. Итак, рассмотрим альтернативу.

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

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

Что касается того, как вы проводите проверку границ ошибок для вычислений, это может быть сложным вопросом. Любая реализация с плавающей запятой, использующая стандарт IEEE 754 в режиме округления до ближайшей точки, возвращает число с плавающей запятой, ближайшую к точному результату, для любой базовой операции (в частности, умножения, деления, сложения, вычитания, квадратного корня). (В случае галстука, округлите, так что бит минимальный.) (Будьте особенно осторожны с квадратным корнем и делением, для реализации на вашем языке могут использоваться методы, которые не соответствуют требованиям IEEE 754.) Из-за этого требования мы знаем, ошибка в одном результате составляет не более половины от значения наименее значимого бита. (Если бы это было больше, округление пошло бы к другому числу, которое находится в пределах 1/2 значения.)

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

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

Ответ 7

Если вы хотите использовать его в контексте тестирования /TDD, я бы сказал, что это стандартный способ:

from nose.tools import assert_almost_equals

assert_almost_equals(x, y, places=7) #default is 7

Ответ 8

Я нашел следующее сравнение полезным:

str(f1) == str(f2)

Ответ 9

math.isclose() был добавлен в Python 3.5 для этого (исходный код). Вот его порт на Python 2. Это отличие от одного лайнера Mark Ransom заключается в том, что он может правильно обрабатывать "inf" и "-inf".

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    '''
    Python 2 implementation of Python 3.5 math.isclose()
    https://hg.python.org/cpython/file/tip/Modules/mathmodule.c#l1993
    '''
    # sanity check on the inputs
    if rel_tol < 0 or abs_tol < 0:
        raise ValueError("tolerances must be non-negative")

    # short circuit exact equality -- needed to catch two infinities of
    # the same sign. And perhaps speeds things up a bit sometimes.
    if a == b:
        return True

    # This catches the case of two infinities of opposite sign, or
    # one infinity and one finite number. Two infinities of opposite
    # sign would otherwise have an infinite relative tolerance.
    # Two infinities of the same sign are caught by the equality check
    # above.
    if math.isinf(a) or math.isinf(b):
        return False

    # now do the regular computation
    # this is essentially the "weak" test from the Boost library
    diff = math.fabs(b - a)
    result = (((diff <= math.fabs(rel_tol * b)) or
               (diff <= math.fabs(rel_tol * a))) or
              (diff <= abs_tol))
    return result

Ответ 10

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

Подробнее см. Fraction из модуля фракций.

Ответ 11

Мне понравилось предложение @Sesquipedal, но с модификацией (особый случай использования, когда оба значения равны 0, возвращает значение False). В моем случае я был на Python 2.7 и просто использовал простую функцию:

if f1 ==0 and f2 == 0:
    return True
else:
    return abs(f1-f2) < tol*max(abs(f1),abs(f2))

Ответ 12

Полезно для случая, когда вы хотите убедиться, что 2 числа одинаковы "до точности", не нужно указывать допуск:

  • Найти минимальную точность двух чисел

  • Округлите их оба с минимальной точностью и сравните

def isclose(a,b):                                       
    astr=str(a)                                         
    aprec=len(astr.split('.')[1]) if '.' in astr else 0 
    bstr=str(b)                                         
    bprec=len(bstr.split('.')[1]) if '.' in bstr else 0 
    prec=min(aprec,bprec)                                      
    return round(a,prec)==round(b,prec)                               

Как написано, работает только для чисел без 'e' в их строковом представлении (что означает 0.9999999999995e-4 <число <= 0.9999999999995e11)

Пример:

>>> isclose(10.0,10.049)
True
>>> isclose(10.0,10.05)
False

Ответ 13

Это может быть немного уродливое взломать, но оно работает очень хорошо, когда вам не нужно больше, чем поплавок по умолчанию (около 11 десятичных знаков). Хорошо работает на python 2.7.

Функция round_to использует метод формата из встроенного класса str для округления float до строка, представляющая поплавок с количеством требуемых десятичных знаков, а затем применяет встроенную функцию eval для округленной строки с плавающей запятой вернитесь к числовому типу с плавающей точкой.

Функция is_close просто применяет простой условный к округленному поплавку.

def round_to(float_num, decimal_precision):
    return eval("'{:." + str(int(decimal_precision)) + "f}'.format(" + str(float_num) + ")")

def is_close(float_a, float_b, decimal_precision):
    if round_to(float_a, decimal_precision) == round_to(float_b, decimal_precision):
        return True
    return False

a = 10.0 / 3
# Result: 3.3333333333333335
b = 10.0001 / 3
# Result: 3.3333666666666666

print is_close(a, b, decimal_precision=4)
# Result: False

print is_close(a, b, decimal_precision=3)
# Result: True

Ответ 14

Для сравнения до заданного десятичного знака без atol/rtol:

def almost_equal(a, b, decimal=6):
    return '{0:.{1}f}'.format(a, decimal) == '{0:.{1}f}'.format(b, decimal)

print(almost_equal(0.0, 0.0001, decimal=5)) # False
print(almost_equal(0.0, 0.0001, decimal=4)) # True