Какой самый эффективный способ сравнить два double
или два float
значения?
Просто делать это не правильно
bool CompareDoubles1 (double A, double B)
{
return A == B;
}
Но что-то вроде:
bool CompareDoubles2 (double A, double B)
{
diff = A - B;
return (diff < EPSILON) && (-diff < EPSILON);
}
Кажется, переработка отходов.
Кто-нибудь знает умный поплавковый компаратор?
<invoke Knuth>
Преждевременная оптимизация - корень всего зла.</invoke Knuth>
Просто используйте abs (ab) <EPS, как отмечено выше, это понятно и легко для понимания.==
может быть совершенно правильным, но это полностью зависит от контекста, не приведенного в вопросе. Пока этот контекст не известен, по-==
прежнему остается «наиболее эффективным способом» .Ответы:
Будьте предельно осторожны, используя любые другие предложения. Все зависит от контекста.
Я потратил много времени на отслеживание ошибок в системе, которая предположительно,
a==b
если|a-b|<epsilon
. Основными проблемами были:Неявная презумпция в алгоритме, если
a==b
иb==c
тогдаa==c
.Использование того же эпсилона для линий, измеряемых в дюймах, и линий, измеряемых в милях (0,001 дюйма). Это
a==b
но1000a!=1000b
. (Вот почему почтиEqual2sComplement запрашивает epsilon или max ULPS).Использование одного и того же эпсилона как для косинуса углов, так и для длины линий!
Использование такой функции сравнения для сортировки элементов в коллекции. (В этом случае использование встроенного оператора C ++ == для двойных значений дает правильные результаты.)
Как я уже сказал: все зависит от контекста и ожидаемого размера
a
иb
.Кстати,
std::numeric_limits<double>::epsilon()
это «машина эпсилон». Это разница между 1.0 и следующим значением, представленным двойным. Я предполагаю, что это можно использовать в функции сравнения, но только если ожидаемые значения меньше 1. (Это в ответ на ответ @ cdv ...)Кроме того, если у вас в основном есть
int
арифметикаdoubles
(здесь мы используем double для хранения значений int в некоторых случаях), ваша арифметика будет правильной. Например, 4.0 / 2.0 будет таким же, как 1.0 + 1.0. Это до тех пор, пока вы не делаете вещи, которые приводят к дробным частям (4.0 / 3.0) или не выходят за пределы размера int.источник
fabs(a)+fabs(b)
но с компенсацией NaN, 0 суммы и переполнения, это становится довольно сложным.float
/double
является мантиссой х 2 ^ EXP .epsilon
будет зависеть от показателя. Например, если мантисса 24 бит, а показатель степени подписан 8 бит , то1/(2^24)*2^127
или~2^103
являетсяepsilon
для некоторых значений; или это относится к минимальному эпсилону ?|a-b|<epsilon
, это не правильно. Пожалуйста, добавьте эту ссылку к вашему ответу; если вы согласны с cygnus-software.com/papers/comparingfloats/comparingfloats.htm и я могу удалить мои глупые комментарии.Сравнение со значением эпсилона - это то, что делает большинство людей (даже в программировании игр).
Вы должны немного изменить свою реализацию:
Изменить: Кристер добавил стопку отличной информации по этой теме в недавнем сообщении в блоге . Наслаждаться.
источник
float a = 3.4; if(a == 3.4){...}
то есть, когда вы сравниваете сохраненную плавающую точку с литералом | В этом случае оба числа сохраняются, поэтому они будут иметь одинаковое представление, если они равны, так какой вред делатьa == b
?EPSILON
определен какDBL_EPSILON
. Обычно это будет конкретное значение, выбранное в зависимости от требуемой точности сравнения.EPSILON
сравнение не работает, когда значения с плавающей точкой велики, так как разница между последовательными числами с плавающей точкой также становится большой. Смотрите эту статью .EPSILON
практически бесполезно. Вы должны сравнить с порогом, который имеет смысл для подразделений под рукой. Кроме того, используйте,std::abs
так как он перегружен для разных типов с плавающей запятой.Я обнаружил, что Google C ++ Testing Framework содержит хорошую кроссплатформенную основанную на шаблонах реализацию NearEqual2sComplement, которая работает как с двойными, так и с плавающими числами. Учитывая, что он выпущен по лицензии BSD, использование его в вашем собственном коде не должно быть проблемой, если вы сохраняете лицензию. Я извлек приведенный ниже код из
http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.hhttps://github.com/google/googletest/blob /master/googletest/include/gtest/internal/gtest-internal.h и добавил лицензию сверху.Убедитесь, что #define GTEST_OS_WINDOWS имеет какое-то значение (или измените код, в котором он используется, на что-то, что соответствует вашей кодовой базе - в конце концов, это BSD-лицензия).
Пример использования:
Вот код:
РЕДАКТИРОВАТЬ: Этому посту 4 года. Это, вероятно, все еще действует, и код хорош, но некоторые люди нашли улучшения. Лучше всего получить последнюю версию
AlmostEquals
прямо из исходного кода Google Test, а не ту, которую я вставил здесь.источник
Сравнение чисел с плавающей запятой для зависит от контекста. Поскольку даже изменение порядка операций может давать разные результаты, важно знать, насколько «равными» должны быть числа.
Сравнение чисел с плавающей запятой по Брюсу Доусону - это хорошее место, чтобы начать сравнение с плавающей запятой.
Следующие определения взяты из Искусства компьютерного программирования от Кнута :
Конечно, выбор эпсилона зависит от контекста и определяет, насколько равными должны быть числа.
Другой метод сравнения чисел с плавающей запятой - посмотреть на ULP (единицы на последнем месте) чисел. Несмотря на то, что статья не касается конкретно сравнений, статья « То, что должен знать каждый компьютерщик» о числах с плавающей запятой, является хорошим ресурсом для понимания того, как работает с плавающей запятой и каковы подводные камни, включая ULP.
источник
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
спас мою жизнь LOL Обратите внимание, что эта версия (я не проверял, подходит ли она и для других) также учитывает изменения, которые могут произойти в неотъемлемой части числа с плавающей запятой (пример:2147352577.9999997616 == 2147352576.0000000000
где вы можете ясно видеть, что есть почти различие2
между двумя числами), что довольно приятно! Это происходит, когда накопленная ошибка округления переполняет десятичную часть числа.std::max(std::abs(a), std::abs(b))
(или сstd::min()
);std::abs
в C ++ перегружен типами float и double, поэтому он работает просто отлично (fabs
хотя вы всегда можете сохранить читабельность).Для более глубокого подхода прочитайте Сравнение чисел с плавающей запятой . Вот фрагмент кода по этой ссылке:
источник
*(int*)&A;
" нарушать строгое правило алиасинга?Понимание того, что это старая ветка, но эта статья является одной из самых простых, которые я обнаружил при сравнении чисел с плавающей запятой, и, если вы хотите узнать больше, она также содержит более подробные ссылки, а основной сайт охватывает полный спектр вопросов. Работа с числами с плавающей точкой Руководство по числам с плавающей точкой : Сравнение .
Мы можем найти более практичную статью в разделе «Допуски с плавающей точкой», где отмечается, что существует тест абсолютного допуска , который сводится к этому в C ++:
и тест относительной толерантности :
В статье отмечается , что абсолютный тест не пройден , когда
x
иy
большие и не в относительном случае , когда они маленькие. Предполагая, что абсолютная и относительная толерантность одинаковы, комбинированный тест будет выглядеть так:источник
Портативный способ получить эпсилон в C ++
Тогда функция сравнения становится
источник
Я потратил довольно много времени на изучение материала в этой замечательной теме. Я сомневаюсь, что все хотят тратить так много времени, поэтому я бы выделил краткое изложение того, что я узнал, и решения, которое я реализовал.
Краткое резюме
numeric_limits::epsilon()
же, как FLT_EPSILON в float.h. Это, однако, проблематично, потому что epsilon для сравнения значений, таких как 1.0, не совпадает с epsilon для значений, таких как 1E9. FLT_EPSILON определен для 1.0.fabs(a-b) <= epsilon
однако это не работает, потому что epsilon по умолчанию определен для 1.0. Нам нужно масштабировать эпсилон вверх или вниз с точки зрения a и b.max(a,b)
либо вы можете получить следующие представимые числа вокруг a, а затем посмотреть, попадает ли b в этот диапазон. Первый называется «относительным» методом, а позже - методом ULP.Реализация служебных функций (C ++ 11)
источник
isDefinitelyLessThan
проверкиdiff < tolerance
, что означает, что a и b почти равны (и, следовательно, a не определенно меньше, чем b). Разве не имеет смысла проверять diff> терпимость в обоих случаях? Или, возможно, добавитьorEqualTo
аргумент, который контролирует, должна ли приблизительная проверка на равенство возвращать true или нет.Код, который вы написали, содержит ошибки:
Правильный код будет:
(... и да, это другое)
Интересно, не заставят ли вас в какой-то степени похабы ленивые оценки? Я бы сказал, что это зависит от компилятора. Вы можете попробовать оба. Если они в среднем эквивалентны, возьмите реализацию с потрясающими.
Если у вас есть какая-то информация о том, какой из двух чисел с большей вероятностью будет больше, чем другие, вы можете сыграть в порядке сравнения, чтобы лучше использовать ленивую оценку.
Наконец, вы можете получить лучший результат, вставив эту функцию. Хотя вряд ли что-то улучшится ...
Редактировать: OJ, спасибо за исправление вашего кода. Я стер свой комментарий соответственно
источник
Это хорошо, если:
Но в противном случае это приведет к неприятностям. Числа двойной точности имеют разрешение около 16 десятичных знаков. Если сравниваемые два числа больше по величине, чем EPSILON * 1.0E16, то вы также можете сказать:
Я рассмотрю другой подход, который предполагает, что вам нужно беспокоиться о первой проблеме, и предположим, что вторая подойдет для вашего приложения. Решение будет что-то вроде:
Это дорого в вычислительном отношении, но иногда это то, что требуется. Это то, что мы должны сделать в моей компании, потому что мы имеем дело с инженерной библиотекой, и входные данные могут варьироваться на несколько десятков порядков.
В любом случае, суть заключается в следующем (и относится практически к каждой проблеме программирования): оцените свои потребности, а затем найдите решение, отвечающее вашим потребностям - не думайте, что простой ответ удовлетворит ваши потребности. Если после вашей оценки вы обнаружите, что этого
fabs(a-b) < EPSILON
будет достаточно, идеально - используйте его! Но следует помнить о его недостатках и других возможных решениях.источник
Как отмечали другие, использование эпсилона с фиксированной экспонентой (например, 0,0000001) будет бесполезным для значений, отличных от значения эпсилона. Например, если ваши два значения 10000.000977 и 10000, тогда НЕТ между этими двумя числами 32-битных значений с плавающей точкой - 10000 и 10000.000977 настолько близки, насколько это возможно, не будучи бит-идентичным. Здесь эпсилон менее 0,0009 не имеет смысла; Вы также можете использовать оператор прямого равенства.
Аналогично, когда эти два значения приближаются к размеру эпсилона, относительная ошибка возрастает до 100%.
Таким образом, попытка смешать число с фиксированной запятой, например 0,00001, со значениями с плавающей запятой (где показатель степени произвольный) является бессмысленным упражнением. Это будет работать только в том случае, если вы можете быть уверены, что значения операндов находятся в узкой области (т. Е. Близко к некоторому конкретному показателю степени), и если вы правильно выберете значение эпсилона для этого конкретного теста. Если вы вытаскиваете число из воздуха («Эй! 0,00001 - это мало, значит, это должно быть хорошо!»), Вы обречены на ошибки в цифрах. Я потратил много времени на отладку плохого числового кода, где некоторые плохие чмо бросают в случайные значения эпсилон, чтобы заставить работать еще один тестовый пример.
Если вы занимаетесь каким-либо числовым программированием и считаете, что вам нужно получить эпсилоны с фиксированной запятой, ПРОЧИТАЙТЕ СТАТЬЮ БРЮСА ПО СРАВНЕНИЮ Плавающих чисел .
Сравнение чисел с плавающей точкой
источник
Qt реализует две функции, возможно, вы можете поучиться у них:
И вам могут понадобиться следующие функции, так как
источник
Общее сравнение чисел с плавающей точкой, как правило, не имеет смысла. Как сравнивать, действительно зависит от поставленной задачи. Во многих задачах числа достаточно дискретизированы, чтобы их можно было сравнивать с заданным допуском. К сожалению, столько же проблем, когда такой трюк не работает. В качестве одного примера рассмотрите возможность работы с функцией Хевисайда (шага) рассматриваемого числа (на ум приходят варианты цифровых акций), когда ваши наблюдения находятся очень близко к барьеру. Выполнение сравнения, основанного на допуске, не принесло бы много пользы, поскольку оно фактически сдвинет проблему с исходного барьера на два новых. Опять же, нет универсального решения для таких проблем, и конкретное решение может потребовать даже перехода к числовому методу для достижения стабильности.
источник
К сожалению, даже ваш «расточительный» код неверен. EPSILON - это наименьшее значение, которое можно добавить к 1.0 и изменить его значение. Значение 1.0 очень важно - большие числа не меняются при добавлении в EPSILON. Теперь вы можете масштабировать это значение до числа, которое вы сравниваете, чтобы определить, отличаются они или нет. Правильное выражение для сравнения двух двойных чисел:
Это как минимум. В целом, однако, вы бы хотели учесть шум в своих вычислениях и игнорировать несколько наименее значимых битов, поэтому более реалистичное сравнение будет выглядеть так:
Если для вас очень важна эффективность сравнения и вы знаете диапазон своих значений, вам следует вместо этого использовать числа с фиксированной запятой.
источник
EPSILON
в вопросе естьDBL_EPSILON
илиFLT_EPSILON
? Проблема в вашем собственном воображении, где вы подставилиDBL_EPSILON
(что действительно было бы неправильным выбором) в код, который его не использовал.Мой класс основан на ранее опубликованных ответах. Очень похоже на код Google, но я использую смещение, при котором все значения NaN превышают 0xFF000000. Это позволяет быстрее проверить NaN.
Этот код предназначен для демонстрации концепции, а не для общего решения. Код Google уже показывает, как вычислить все значения, специфичные для платформы, и я не хотел дублировать все это. Я провел ограниченное тестирование этого кода.
источник
Вот доказательство того, что с помощью
std::numeric_limits::epsilon()
не является ответом - оно терпит неудачу для значений больше единицы:Доказательство моего комментария выше:
Запуск дает этот вывод:
Обратите внимание, что во втором случае (один и чуть больше единицы) два входных значения находятся настолько близко, насколько это возможно, и все же сравниваются как не близкие. Таким образом, для значений больше 1.0 вы можете просто использовать тест на равенство. Исправленные эпсилоны не спасут вас при сравнении значений с плавающей точкой.
источник
return *(reinterpret_cast<double*>(&x));
хотя это обычно работает, на самом деле неопределенное поведение.numeric_limits<>::epsilon
и точку зрения IEEE 754.Нашел еще одну интересную реализацию на: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
источник
Я был бы очень осторожен с любым из этих ответов, который включает вычитание с плавающей запятой (например, fabs (ab) <epsilon). Во-первых, числа с плавающей запятой становятся более разреженными при больших величинах и при достаточно больших значениях, где расстояние больше, чем эпсилон, вы можете просто сделать a == b. Во-вторых, вычитание двух очень близких чисел с плавающей запятой (как это обычно бывает, учитывая, что вы ищете близкое равенство) - это именно то, как вы получаете катастрофическое аннулирование .
Несмотря на то, что ответ не переносимый, я думаю, что ответ Грома делает все возможное, чтобы избежать этих проблем.
источник
a
иb
самих себя. Нет абсолютно никаких проблем с использованием вычитания с плавающей запятой как части нечеткого сравнения (хотя, как уже говорили другие, абсолютное значение эпсилона может или не может быть подходящим для данногоЕсть на самом деле случаи в числовом программном управлении, где вы хотите , чтобы проверить , является ли два число с плавающей точкой в точности равно. Я отправил это на подобный вопрос
https://stackoverflow.com/a/10973098/1447411
Таким образом, вы не можете сказать, что «CompareDoubles1» является неправильным в целом.
источник
Это зависит от того, насколько точно вы хотите, чтобы сравнение было. Если вы хотите сравнить для того же числа, просто перейдите с ==. (Вы почти никогда не захотите делать это, если вы на самом деле не хотите точно такое же число.) На любой приличной платформе вы также можете сделать следующее:
как
fabs
правило, довольно быстро. Под довольно быстрым я подразумеваю, что это в основном побитовое И, так что лучше быть быстрым.Целочисленные трюки для сравнения значений типа double и float хороши, но, как правило, затрудняют эффективную обработку различными конвейерами ЦП. И в наши дни это определенно не быстрее на определенных архитектурах по порядку из-за использования стека в качестве временного хранилища для значений, которые часто используются. (Load-hit-store для тех, кому не все равно.)
источник
По шкале количеств:
Если
epsilon
малая доля величины (то есть относительной величины) в некотором определенном физическом смыслеA
иB
типах сопоставима в том же смысле, чем я думаю, то следующее совершенно правильно:источник
Я использую этот код:
источник
epsilon
, для чего.epsilon
- это просто расстояние между 1 и следующим представимым числом после 1. В лучшем случае этот код просто пытается проверить, точно ли два числа равны друг другу, но поскольку умножаются не степени 2epsilon
, он даже не делает это правильно.std::fabs(std::min(v1, v2))
это неверно - для отрицательных входов он выбирает тот, который имеет большую величину.Я пишу это для Java, но, возможно, вы найдете это полезным. Он использует длинные вместо двойных, но заботится о NaN, субнормальных и т. Д.
Имейте в виду, что после ряда операций с плавающей запятой число может сильно отличаться от того, что мы ожидаем. Там нет кода, чтобы исправить это.
источник
Как насчет этого?
Я видел разные подходы - но никогда не видел этого, поэтому мне любопытно также услышать любые комментарии!
источник
Я использовал эту функцию для своего небольшого проекта, и она работает, но обратите внимание на следующее:
Ошибка двойной точности может стать сюрпризом для вас. Допустим, epsilon = 1.0e-6, тогда 1.0 и 1.000001 НЕ должны считаться равными в соответствии с приведенным выше кодом, но на моем компьютере функция считает их равными, потому что 1.000001 не может быть точно переведен в двоичный формат, это вероятно 1.0000009xxx. Я проверяю это с 1.0 и 1.0000011, и на этот раз я получаю ожидаемый результат.
источник
Это еще одно решение с лямбда:
источник
Мой путь может быть не правильным, но полезным
Конвертируйте оба числа с плавающей точкой в строки и затем сравнивайте строки
Оператор перекрытия также может быть сделано
источник
Вы не можете сравнить два
double
с фиксированнымEPSILON
. В зависимости от стоимостиdouble
,EPSILON
меняется.Лучшее двойное сравнение будет:
источник
В более общем виде:
источник
a
иb
они уже меньше, чемepsilon()
разница, они все равно могут быть значительными. И наоборот, если числа очень большие, то даже несколько бит ошибки приведут к сбою сравнения, даже если вы хотите, чтобы числа считались равными. Этот ответ - именно тот тип «универсального» алгоритма сравнения, который вы хотите избежать.Почему бы не выполнить побитовое XOR? Два числа с плавающей запятой равны, если их соответствующие биты равны. Я думаю, решение разместить биты экспоненты перед мантиссой было принято, чтобы ускорить сравнение двух чисел. Я думаю, что многие ответы здесь не имеют смысла сравнения эпсилон. Значение эпсилона зависит только от того, с какой точностью сравниваются числа с плавающей запятой. Например, выполнив некоторую арифметику с плавающей точкой, вы получите два числа: 2.5642943554342 и 2.5642943554345. Они не равны, но для решения имеют значение только 3 десятичных знака, поэтому они равны: 2,564 и 2,564. В этом случае вы выбираете эпсилон равным 0,001. Эпсилон-сравнение также возможно с побитовым XOR. Поправь меня, если я ошибаюсь.
источник