Хорошо известно, что сравнение поплавков на равенство немного затруднительно из-за проблем округления и точности.
Например: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
Каков рекомендуемый способ борьбы с этим в Python?
Конечно, есть где-нибудь стандартная библиотечная функция для этого?
python
floating-point
Гордон Ригли
источник
источник
all
,any
,max
,min
являются в основном каждый однострочечники, и они не только при условии , в библиотеке, они встроенные функции. Так что причины BDFL не в этом. Одна строка кода, которую пишет большинство людей, довольно проста и часто не работает, что является веской причиной для предоставления чего-то лучшего. Конечно, любой модуль, предоставляющий другие стратегии, должен также предоставлять предостережения, описывающие, когда они уместны, и, что более важно, когда их нет. Числовой анализ сложен, и это не позор, что разработчики языка обычно не пытаются инструменты, чтобы помочь с этим.Ответы:
Python 3.5 добавляет
math.isclose
иcmath.isclose
функции , как описано в PEP 485 .Если вы используете более раннюю версию Python, эквивалентная функция приведена в документации .
rel_tol
относительный допуск, он умножается на большее из двух аргументов; по мере того как значения становятся больше, увеличивается и допустимая разница между ними, в то же время считая их равными.abs_tol
является абсолютным допуском, который применяется как есть во всех случаях. Если разница меньше, чем любой из этих допусков, значения считаются равными.источник
a
илиb
являетсяnumpy
array
,numpy.isclose
работает.rel_tol
- относительный допуск , он умножается на большее из двух аргументов; по мере того как значения становятся больше, увеличивается и допустимая разница между ними, в то же время считая их равными.abs_tol
является абсолютным допуском, который применяется как есть во всех случаях. Если разница меньше, чем любой из этих допусков, значения считаются равными.isclose
функция (выше) не является полной реализацией.isclose
всегда придерживается менее консервативного критерия. Я упоминаю об этом только потому, что это поведение противоречит мне. Если бы я указал два критерия, я бы всегда ожидал, что меньший допуск заменит более высокий.Что-то простое, как следующее, не достаточно хорошо?
источник
abs(f1-f2) < tol*max(abs(f1),abs(f2))
. Такого рода относительный допуск является единственным значимым способом сравнения чисел с плавающей запятой в целом, поскольку на них обычно влияет ошибка округления в небольших десятичных разрядах.>>> abs(0.04 - 0.03) <= 0.01
он даетFalse
. Я используюPython 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
abs(f1 - f2) <= allowed_error
не работает, как ожидалось.Я бы согласился, что ответ Гарета, вероятно, наиболее уместен в качестве облегченной функции / решения.
Но я подумал, что было бы полезно отметить, что если вы используете NumPy или рассматриваете его, для этого есть упакованная функция.
Небольшая оговорка: установка NumPy может быть нетривиальным процессом в зависимости от вашей платформы.
источник
pip
Windows.Используйте
decimal
модуль Python , который предоставляетDecimal
класс.Из комментариев:
источник
Я не знаю ничего в стандартной библиотеке Python (или где-либо еще), которая реализует
AlmostEqual2sComplement
функцию Доусона . Если вы хотите именно такое поведение, вам придется реализовать его самостоятельно. (В этом случае, вместо того, чтобы использовать умные побитовые хаки Доусона, вам, вероятно, лучше использовать более обычные тесты формыif abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2
или аналогичные. Чтобы получить поведение, подобное Доусону, вы можете сказать что-то вродеif abs(a-b) <= eps*max(EPS,abs(a),abs(b))
небольшого исправленияEPS
; это не совсем так такой же, как Доусон, но похож по духу.источник
eps1
иeps2
определите относительную и абсолютную терпимость: вы готовы разрешитьa
иb
примерно вeps1
разы различать, насколько они великиeps2
.eps
это единая толерантность; вы готовы разрешитьa
иb
примерно вeps
разы различать, насколько они велики, при условии, чтоEPS
предполагается, что все, что имеет размер или меньше, имеет размерEPS
. Если вы приметеEPS
наименьшее неденормированное значение вашего типа с плавающей запятой, это очень похоже на компаратор Доусона (за исключением коэффициента 2 ^ # бит, потому что Доусон измеряет допуск в ulps).Общепринятое мнение, что числа с плавающей точкой не могут сравниваться на равенство, является неточным. Числа с плавающей запятой ничем не отличаются от целых чисел: если вы оцените «a == b», вы получите истину, если они будут идентичными числами, и ложью в противном случае (при том понимании, что два NaN, конечно, не являются одинаковыми числами).
Фактическая проблема заключается в следующем: если я провел некоторые вычисления и не уверен, что два числа, которые я должен сравнивать, являются точными, то что? Эта проблема одинакова для чисел с плавающей запятой и целых чисел. Если вы оцените целочисленное выражение «7/3 * 3», оно не будет сравниваться равным «7 * 3/3».
Итак, предположим, мы спросили: «Как сравнить целые числа на равенство?» в такой ситуации. Там нет однозначного ответа; что вы должны сделать, зависит от конкретной ситуации, в частности, от того, какие у вас ошибки и чего вы хотите достичь.
Вот несколько возможных вариантов.
Если вы хотите получить «истинный» результат, если математически точные числа будут равны, то вы можете попытаться использовать свойства вычислений, которые вы выполняете, чтобы доказать, что вы получаете одинаковые ошибки в двух числах. Если это выполнимо, и вы сравниваете два числа, которые являются результатом выражений, которые дали бы равные числа, если вычислить точно, то вы получите «истину» из сравнения. Другой подход заключается в том, что вы можете проанализировать свойства вычислений и доказать, что ошибка никогда не превышает определенную величину, возможно, абсолютную величину или величину по отношению к одному из входов или одному из выходов. В этом случае вы можете спросить, отличаются ли два вычисленных числа не более чем на эту сумму, и вернуть «true», если они находятся в пределах интервала. Если вы не можете доказать ошибку, Вы можете догадаться и надеяться на лучшее. Один из способов угадать - оценить множество случайных выборок и посмотреть, какое распределение вы получите в результатах.
Конечно, поскольку мы устанавливаем требование, что вы получите «true», если математически точные результаты равны, мы оставили открытой возможность того, что вы получите «true», даже если они неравны. (Фактически, мы можем удовлетворить требование, всегда возвращая «true». Это делает расчет простым, но в целом нежелательным, поэтому я расскажу об улучшении ситуации ниже.)
Если вы хотите получить «ложный» результат, если математически точные числа будут неравными, вам нужно доказать, что ваша оценка чисел дает разные числа, если математически точные числа будут неравными. Это может быть невозможно для практических целей во многих общих ситуациях. Итак, давайте рассмотрим альтернативу.
Полезным требованием может быть получение «ложного» результата, если математически точные числа отличаются более чем на определенную величину. Например, возможно, мы собираемся вычислить, куда попал мяч, брошенный в компьютерной игре, и мы хотим знать, ударил ли он по летучей мыши. В этом случае мы, безусловно, хотим получить «истину», если мяч ударяет по бите, и мы хотим получить «ложь», если мяч находится далеко от летучей мыши, и мы можем принять неверный «истинный» ответ, если мяч математически точное моделирование пропустило летучую мышь, но находится в миллиметре от удара по ней. В этом случае нам нужно доказать (или угадать / оценить), что наши расчеты положения мяча и положения летучей мыши имеют общую ошибку не более одного миллиметра (для всех интересующих позиций). Это позволило бы нам всегда возвращаться
То, как вы решите, что возвращать при сравнении чисел с плавающей запятой, очень сильно зависит от вашей конкретной ситуации.
Относительно того, как вы можете доказать границы ошибок для расчетов, это может быть сложным вопросом. Любая реализация с плавающей запятой, использующая стандарт IEEE 754 в режиме округления до ближайшего, возвращает число с плавающей запятой, ближайшее к точному результату для любой базовой операции (в частности, умножение, деление, сложение, вычитание, квадратный корень). (В случае связывания округлите, чтобы младший бит был четным.) (Будьте особенно осторожны с квадратным корнем и делением; ваша языковая реализация может использовать методы, которые не соответствуют IEEE 754 для них.) Из-за этого требования мы знаем ошибка в одном результате составляет не более 1/2 от значения младшего значащего бита. (Если бы это было больше, округление дошло бы до другого числа, которое находится в пределах 1/2 от значения.)
Идти оттуда становится значительно сложнее; Следующим шагом является выполнение операции, когда на одном из входов уже есть какая-то ошибка. Для простых выражений за этими ошибками можно проследить вычисления, чтобы достичь границы окончательной ошибки. На практике это делается только в нескольких ситуациях, например при работе с высококачественной математикой. И, конечно же, вам необходимо точно контролировать, какие именно операции выполняются. Языки высокого уровня часто дают компилятору большую слабость, поэтому вы можете не знать, в каком порядке выполняются операции.
Об этой теме можно написать (и написать) гораздо больше, но на этом я должен остановиться. Итак, ответ таков: для этого сравнения нет библиотечной подпрограммы, потому что нет единого решения, удовлетворяющего большинству потребностей, которое стоит включить в библиотечную подпрограмму. (Если для вас достаточно сравнения с относительным или абсолютным интервалом ошибок, вы можете сделать это просто без библиотечной процедуры.)
источник
(7/3*3 == 7*3/3)
. Это напечатаноFalse
.from __future__ import division
. Если вы этого не сделаете, чисел с плавающей запятой не существует, и сравнение проводится между двумя целыми числами.Если вы хотите использовать его в контексте тестирования / TDD, я бы сказал, что это стандартный способ:
источник
Для этого в Python 3.5 был добавлен math.isclose () ( исходный код ). Вот его порт для Python 2. Отличие от однострочного в Mark Ransom заключается в том, что он может правильно обрабатывать «inf» и «-inf».
источник
Я нашел следующее сравнение полезным:
источник
str(.1 + .2) == str(.3)
возвращает False. Описанный выше метод работает только для Python 2.В некоторых случаях, когда вы можете повлиять на представление исходного числа, вы можете представить их в виде дробей, а не чисел, используя целочисленный числитель и знаменатель. Таким образом, вы можете иметь точные сравнения.
См. Фракция из модуля фракций для деталей.
источник
Мне понравилось предложение @Sesquipedal, но с модификацией (особый случай использования, когда оба значения равны 0, возвращает False). В моем случае я был на Python 2.7 и просто использовал простую функцию:
источник
Полезно для случая, когда вы хотите убедиться, что 2 числа одинаковы «до точности», не нужно указывать допуск:
Найти минимальную точность двух чисел
Округлите их оба с минимальной точностью и сравните
Как написано, работает только для чисел без 'e' в их строковом представлении (что означает 0.9999999999995e-4 <число <= 0.9999999999995e11)
Пример:
источник
isclose(1.0, 1.1)
производитFalse
иisclose(0.1, 0.000000000001)
возвращаетTrue
.Для сравнения до заданного десятичного числа без
atol/rtol
:источник
Это может быть немного уродливым взломом, но он работает довольно хорошо, когда вам не нужно больше, чем точность с плавающей запятой по умолчанию (около 11 десятичных знаков).
Функция round_to использует метод format из встроенного класса str для округления числа с плавающей точкой до строки, представляющей число с плавающей запятой с необходимым количеством десятичных знаков, а затем применяет eval встроенную функцию к округленной строке с плавающей запятой для возврата на числовой тип с плавающей точкой.
Функция is_close просто применяет простое условное выражение к округленному с плавающей точкой.
Обновить:
Как предлагает @stepehjfox, более чистый способ создания функции rount_to, избегающей «eval», использует вложенное форматирование :
Следуя той же идее, код может быть еще проще с использованием великолепных новых f-строк (Python 3.6+):
Таким образом, мы могли бы обернуть все это в одну простую и понятную функцию is_close :
источник
eval()
для получения параметризованного форматирования. Нечто подобноеreturn '{:.{precision}f'.format(float_num, precision=decimal_precision)
должно сделать этоreturn '{:.{precision}}f'.format(float_num, precision=decimal_precision)
С точки зрения абсолютной ошибки, вы можете просто проверить
Некоторая информация о том, почему float действует странно в Python https://youtu.be/v4HhvoNLILk?t=1129
Вы также можете использовать math.isclose для относительных ошибок
источник