Что является лучшим способом, чтобы сравнить поплавки для почти-равенство в Python?



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



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



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



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

519   12  

12 ответов:

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

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

return abs(f1 - f2) <= allowed_error

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from nose.tools import assert_almost_equals

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

математика.находится неподалеку() была добавил в 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

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

str(f1) == str(f2)

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

посмотреть фракция из модуля фракций для деталей.

Мне понравилось предложение @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))

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

на round_to

Comments

    Ничего не найдено.