Каков наилучший способ сравнения чисел с плавающей точкой на почти равенство в Python? - PullRequest
270 голосов
/ 08 апреля 2011

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

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

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

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

Ответы [ 14 ]

254 голосов
/ 08 октября 2015

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 - абсолютный допуск, который применяется как есть во всех случаях.Если разница меньше, чем любой из этих допусков, значения считаются равными.

61 голосов
/ 08 апреля 2011

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

return abs(f1 - f2) <= allowed_error
36 голосов
/ 04 июня 2013

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

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

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

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

13 голосов
/ 08 апреля 2011

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

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

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

11 голосов
/ 12 апреля 2011

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

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

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

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

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

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

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

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

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

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

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

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

11 голосов
/ 08 апреля 2011

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

4 голосов
/ 06 сентября 2016

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
4 голосов
/ 16 июня 2016

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

from nose.tools import assert_almost_equals

assert_almost_equals(x, y, places=7) #default is 7
3 голосов
/ 26 мая 2015

Мне показалось полезным следующее сравнение:

str(f1) == str(f2)
1 голос
/ 04 апреля 2019

Полезно для случая, когда вы хотите убедиться, что 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) </p>

Пример:

>>> isclose(10.0,10.049)
True
>>> isclose(10.0,10.05)
False
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...