Какой способ сравнения двух значений 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);
}
Похоже на переработку отходов.
Кто-нибудь знает более умный компаратор поплавков?
Если можете, отбросьте плавающую точку и используйте фиксированные точки. Например, используйте миллиметры с {фиксированной запятой} вместо метров с {плавающей запятой}.
Вот как это реализовано в библиотеке Boost Test Library: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/ testing-tools / floati ng_point_comparison. html
> Было бы эффективнее добавить ... в начало функции? <invoke Knuth> Преждевременная оптимизация - корень всех зол </invoke Knuth> Просто используйте абс (a-b)
Похоже, вы пропустили последний пункт своей цитаты - «только после того, как этот [критический] код был идентифицирован». Если этот код не будет идентифицирован как узкое место, его оптимизация до нечеткости будет пустой тратой времени (и потенциально опасной).
== can be perfectly correct, but this entirely depends on the context not given in the question. Until that context is known, == still stays the "самый эффективный способ".
См. Также Следует ли сравнивать числа с плавающей запятой на равенство с ошибкой относительный?





Сравнение с эпсилон-значением - это то, что делает большинство людей (даже в программировании игр).
Однако вам следует немного изменить свою реализацию:
bool AreSame(double a, double b)
{
return fabs(a - b) < EPSILON;
}
Обновлено: Кристер добавил много отличной информации по этой теме на последнее сообщение в блоге. Наслаждаться.
@OJ: что-то не так с первым образцом кода? Я думал, что единственная проблема была в такой ситуации: float a = 3.4; if (a == 3.4){...}, т.е. когда вы сравниваете сохраненную плавающую точку с литералом | В этом случае сохраняются оба числа, поэтому они будут иметь одинаковое представление, если они равны, так какой вред при использовании a == b?
@ Лазер: cygnus-software.com/papers/comparingfloats/comparingfloats.h tm.
@DonReba: только если EPSILON определен как DBL_EPSILON. Обычно это конкретное значение, выбираемое в зависимости от требуемой точности сравнения.
Сравнение EPSILON не работает, когда числа с плавающей запятой большие, так как разница между последовательными числами с плавающей запятой также становится большой. См. Эта статья.
Неудивительно, что в некоторых играх есть Z-файтинг, когда текстуры / объекты вдали мерцают, как в Battlefield 4. Сравнивать разницу с EPSILON бесполезно. Вам нужно сравнить с порогом, который имеет смысл для имеющихся единиц. Также используйте std::abs, поскольку он перегружен для разных типов с плавающей запятой.
Старая ссылка кажется устаревшей, здесь новая страница randomascii.wordpress.com/2012/02/25/…
Я проголосовал против, поскольку пример кода показывает, что типичная ошибка повторяется большинством программистов. Плавающая точка всегда связана с относительными ошибками, поскольку это плавающая точка (а не фиксированная точка). Таким образом, он никогда не будет правильно работать с фиксированной ошибкой (эпсилон).
Он будет работать правильно, ЕСЛИ этого требует ваша ситуация.
У этого метода много недостатков, например, если числа a и b уже меньше, чем у EPSILON, их разница может быть значительной. И наоборот, если числа очень большие, то даже пара битов ошибки приведет к сбою сравнения, даже если вы действительно хотите, чтобы числа считались равными. Вы можете сделать EPSILON параметром функции, а затем передать ответственность за выбор значения вызывающей стороне, но я думаю, что на практике люди не будут особо задумываться о том, что им следует передать, и поэтому проблема не исчезнет.
@SirGuy, опубликуйте ответ, чтобы продемонстрировать, как это правильно сделать, или дайте ссылку на него здесь. Я бы хотел увидеть альтернативы, не основанные на эпсилоне.
Однако, продолжая использовать эпсилон-подход, можно полностью избавиться от fabs(), дважды используя вычитание и &&, как я показываю в своем новом ответе здесь: stackoverflow.com/a/65015333/4561887.
@SirGuy, я просто добавил подход в моем ответе, который вычисляет переменную epsilon на основе сравниваемых значений с плавающей запятой. Интересно, решает ли это проблемы?
Код, который вы написали, ошибочен:
return (diff < EPSILON) && (-diff > EPSILON);
Правильный код будет:
return (diff < EPSILON) && (diff > -EPSILON);
(... и да, это другое)
Интересно, не заставят ли фабрики в некоторых случаях потерять ленивую оценку. Я бы сказал, это зависит от компилятора. Возможно, вы захотите попробовать и то, и другое. Если в среднем они эквивалентны, возьмите реализацию с фабсами.
Если у вас есть информация о том, какое из двух чисел с плавающей запятой с большей вероятностью будет больше, чем другое, вы можете поиграть в порядке сравнения, чтобы лучше использовать ленивую оценку.
Наконец, вы можете получить лучший результат, вставив эту функцию. Хотя вряд ли сильно улучшится ...
Обновлено: OJ, спасибо за исправление вашего кода. Я удалил свой комментарий соответственно
Вопрос был отредактирован, чтобы быть правильным. И return (diff < EPSILON) && (diff > -EPSILON);, и return (diff < EPSILON) && (-diff < EPSILON); эквивалентны и оба верны.
Для более глубокого подхода прочтите Сравнение чисел с плавающей запятой. Вот фрагмент кода по этой ссылке:
// Usable AlmostEqual function
bool AlmostEqual2sComplement(float A, float B, int maxUlps)
{
// Make sure maxUlps is non-negative and small enough that the
// default NAN won't compare as equal to anything.
assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);
int aInt = *(int*)&A;
// Make aInt lexicographically ordered as a twos-complement int
if (aInt < 0)
aInt = 0x80000000 - aInt;
// Make bInt lexicographically ordered as a twos-complement int
int bInt = *(int*)&B;
if (bInt < 0)
bInt = 0x80000000 - bInt;
int intDiff = abs(aInt - bInt);
if (intDiff <= maxUlps)
return true;
return false;
}
Какое предполагаемое значение maxUlps?
Будет ли "*(int*)&A;" нарушать строгое правило псевдонима?
Нет. Это копирование, а не псевдоним.
Согласно gtest (поиск ULP), 4 является приемлемым числом.
И вот пара обновлений статьи Брюса Доусона (одно из которых указано во введении к статье): randomascii.wordpress.com/2012/02/25/… и randomascii.wordpress.com/2012/06/26/…
Мне потребовалось некоторое время, чтобы понять, что было на ULP: единицы на последнем месте
@JeffCharter или единицы наименьшей точности.
@osgx: Да, это нарушает строгий псевдоним. А правильный код настолько прост: memcpy(&aInt, &A, sizeof (int)); Добавьте static_assert(sizeof(int) == sizeof(float)); на всякий случай.
@Ben Нарушение строгого псевдонима в этом случае не имеет значения, поскольку указатель никогда не может быть псевдонимом (потому что указатель немедленно разыменовывается). Это будет иметь значение только в многопоточной среде, и если это создаст для вас ошибку, значит, вы написали действительно ужасный код. Во всех остальных случаях memcpy - это пустая трата ресурсов процессора и программиста.
Это зависит от того, насколько точным будет сравнение. Если вы хотите сравнить точно такое же число, просто используйте ==. (Вы почти никогда не захотите этого делать, если на самом деле не хотите точно такое же число.) На любой достойной платформе вы также можете сделать следующее:
diff= a - b; return fabs(diff)<EPSILON;
поскольку fabs имеет тенденцию быть довольно быстрым. Под довольно быстрым я подразумеваю, что это в основном побитовое И, так что лучше быть быстрым.
Целочисленные уловки для сравнения чисел типа double и float хороши, но, как правило, затрудняют эффективную обработку различных конвейеров ЦП. И в наши дни это определенно не быстрее на некоторых упорядоченных архитектурах из-за использования стека в качестве области временного хранения для часто используемых значений. (Загрузите-хит-магазин для тех, кому не все равно.)
Общее сравнение чисел с плавающей запятой вообще бессмысленно. Как сравнивать, действительно зависит от решаемой проблемы. Во многих задачах числа достаточно дискретны, чтобы их можно было сравнивать в пределах заданного допуска. К сожалению, столько же проблем, где такой трюк не работает. Например, рассмотрите возможность работы с функцией Хевисайда (ступенчатой) рассматриваемого числа (на ум приходят цифровые опционы на акции), когда ваши наблюдения очень близки к препятствию. Выполнение сравнения на основе допусков не принесет особой пользы, так как фактически сместит проблему с первоначального барьера на два новых. Опять же, для таких проблем не существует универсального решения, и для конкретного решения может потребоваться изменение численного метода для достижения стабильности.
`return fabs(a - b) < EPSILON;
Это нормально, если:
Но иначе это приведет к неприятностям. Числа с двойной точностью имеют разрешение около 16 знаков после запятой. Если два числа, которые вы сравниваете, больше по величине, чем EPSILON * 1.0E16, то вы также можете сказать:
return a==b;
Я исследую другой подход, который предполагает, что вам нужно беспокоиться о первой проблеме, и предположить, что вторая подходит для вашего приложения. Решение будет примерно таким:
#define VERYSMALL (1.0E-150)
#define EPSILON (1.0E-8)
bool AreSame(double a, double b)
{
double absDiff = fabs(a - b);
if (absDiff < VERYSMALL)
{
return true;
}
double maxAbs = max(fabs(a) - fabs(b));
return (absDiff/maxAbs) < EPSILON;
}
Это дорого с точки зрения вычислений, но иногда требуется. Это то, что мы должны делать в моей компании, потому что мы имеем дело с инженерной библиотекой, а исходные данные могут отличаться на несколько десятков порядков.
В любом случае, суть в следующем (и применима практически к каждой проблеме программирования): оцените, что вам нужно, а затем придумайте решение, отвечающее вашим потребностям - не думайте, что простой ответ удовлетворит ваши потребности. Если после вашей оценки вы обнаружите, что fabs(a-b) < EPSILON будет достаточно, отлично - используйте его! Но помните о его недостатках и других возможных решениях.
Помимо опечаток (s / - /, / отсутствует запятая в fmax ()), эта реализация имеет ошибку для чисел, близких к нулю, которые находятся в EPSILON, но пока не совсем ОЧЕНЬ МАЛЕНЬКИХ. Например, AreSame (1.0E-10, 1.0E-9) сообщает false, потому что относительная ошибка огромна. Вы станете героем своей компании.
@brlcad Вы не поняли суть точки плавающий. 1.0E-10 и 1.0E-9 различаются величиной 10. Так что это правда, что они не одно и то же. Точка плавающий всегда связана с ошибками относительный. Если у вас есть система, которая рассматривает 1.0E-10 и 1.0E-9 как почти равные, поскольку оба они «довольно близки к нулю» (что звучит разумно для людей, но не имеет ничего общего с математикой), то этот EPSILON необходимо отрегулировать соответствующим образом для такой системы.
Переносимый способ получить epsilon в C++ -
#include <limits>
std::numeric_limits<double>::epsilon()
Тогда функция сравнения становится
#include <cmath>
#include <limits>
bool AreSame(double a, double b) {
return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}
напишите std::fabs вместо fabs, если хотите оставаться последовательным.
Скорее всего, вам понадобится кратное этому эпсилону.
Разве вы не можете просто использовать std :: abs? AFAIK, std :: abs также перегружен для удвоений. Пожалуйста, предупредите меня, если я ошибаюсь.
@kolistivra, вы ошибаетесь. 'F' в 'fabs' не означает тип float. Вы, вероятно, думаете о функциях языка C fabsf () и fabsl ().
Очевидно, неверно, поскольку он будет считать два двойных значения больше 2 одинаковыми, только если они равны, и при этом epsilon / 1024 и epsilon будут считаться одинаковыми.
Фактически по причинам изложено в статье Брюсаэпсилон изменения, поскольку значение с плавающей запятой становится больше. Смотрите ту часть, где он говорит «Для чисел больше 2,0 разрыв между числами с плавающей запятой увеличивается, и если вы сравниваете числа с плавающей запятой с помощью FLT_EPSILON, то вы просто выполняете более дорогостоящую и менее очевидную проверку равенства».
Я знаю, что это старый, но std :: abs перегружен для типов с плавающей запятой в cmath.
@kolistivra не ошибается, хотя, вероятно, лучше использовать std :: fabs, чтобы избежать случайного преобразования в int. stackoverflow.com/questions/3118165/…
Нет. Если a или b больше или равно единице, то это то же самое, что и использование оператора равенства. Если a и b меньше единицы, то относительный размер epsilon будет увеличиваться по мере уменьшения значений a и b. Из en.cppreference.com/w/cpp/types/numeric_limits/epsilonstd::numeric_limits<T>::epsilon() «возвращает машинный эпсилон, то есть разницу между 1.0 и следующим значением, представленным типом с плавающей запятой T.»
Будьте предельно осторожны, используя любые другие предложения. Все зависит от контекста.
Я провел много времени, отслеживая ошибки в системе, которая предполагала a==b, если |a-b|<epsilon. Основные проблемы заключались в следующем:
Неявное предположение в алгоритме, что если a==b и b==c, то a==c.
Использование одного и того же эпсилон для линий, измеренных в дюймах, и линий, измеренных в милах (0,001 дюйма). То есть a==b, но 1000a!=1000b. (Вот почему почтиEqual2sComplement запрашивает epsilon или max ULPS).
Использование одного и того же эпсилона как для косинуса углов, так и для длины линий!
Использование такой функции сравнения для сортировки элементов в коллекции. (В этом случае использование встроенного оператора C++ == для чисел типа double дало правильные результаты.)
Как я уже сказал: все зависит от контекста и ожидаемого размера a и b.
Кстати, std::numeric_limits<double>::epsilon() - это "машинный эпсилон". Это разница между 1.0 и следующим значением, представляемым двойным числом. Я предполагаю, что его можно использовать в функции сравнения, но только если ожидаемые значения меньше 1. (Это ответ на ответ @cdv ...)
Кроме того, если у вас в основном есть арифметика int в doubles (здесь мы используем двойные значения для хранения значений int в некоторых случаях), ваша арифметика будет правильной. Например, 4.0 / 2.0 будет таким же, как 1.0 + 1.0. Это до тех пор, пока вы не делаете вещи, которые приводят к дробям (4.0 / 3.0) или не выходите за пределы размера int.
+1 за указание на очевидное (которое часто игнорируется). Для универсального метода вы можете сделать эпсилон относительно fabs(a)+fabs(b), но с компенсацией NaN, суммы 0 и переполнения это становится довольно сложным.
Должно быть что-то я не понимаю. Типичный float / double - МАНТИССА x 2 ^ EXP. epsilon будет зависеть от экспоненты. Например, если мантисса составляет 24 бита, а экспонента - 8 бит со знаком, то 1/(2^24)*2^127 или ~2^103 является epsilon для некоторых значений; или это относится к минимальному эпсилон?
Подожди секунду. Ты имел в виду то, что я сказал? Вы говорите, почему |a-b|<epsilon является правильным нет. Добавьте эту ссылку к своему ответу; если вы согласны с cygnus-software.com/papers/comparingfloats/comparingfloats.h tm и я могу удалить свои тупые комментарии.
+1 В связи с чем возникает необходимость в использовании специальной погрешности в зависимости от сценария и того, что вы сравниваете
В статье, упомянутой выше, есть смысл: cygnus-software.com/papers/comparingfloats/comparingfloats.h tm -> почтиEqualRelativeOrAbsolute (...) подходит лучше всего. При использовании точки плавающий вы имеете дело с относительными ошибками, иначе вам не понадобится точка плавающий. Проблема в том, что большинство людей используют числа с плавающей точкой из-за своей лени. Кажется, это так просто. Но это не так. Большинству людей будет лучше с фиксированной точкой, но это так экзотично :)
Если кому интересно, сделал версию на дубль решения @ user2261015: bool almostEqual2sComplement(double A, double B, int maxUlps) { assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); int64_t aLong = *reinterpret_cast<int64_t*>( &A ); if (aLong < 0) aLong = 0x8000000000000000 - aLong; int64_t bLong = *reinterpret_cast<int64_t*>( &B ); if (bLong < 0) bLong = 0x8000000000000000 - bLong; int64_t longDiff = (aLong - bLong) & 0x7FFFFFFFFFFFFFFF; if (longDiff <= maxUlps) return true; return false; }
Это очень длинный комментарий, а не ответ сам по себе. Есть ли (набор) канонических ответов для всех контекстов?
Старая ссылка кажется устаревшей, здесь новая страница randomascii.wordpress.com/2012/02/25/…
здесь есть хороший пример, как это сделать со стандартной библиотекой: en.cppreference.com/w/cpp/types/numeric_limits/epsilon
Это не отвечает на вопрос.
На самом деле, если a == b и b == c, тогда a == c является истинным утверждением. Когда вы начинаете пытаться использовать алгебру и предполагаете, что ошибок точности не будет, вы столкнетесь с проблемами.
Сравнение чисел с плавающей запятой зависит от контекста. Поскольку даже изменение порядка операций может привести к разным результатам, важно знать, насколько «равными» должны быть числа.
Сравнение чисел с плавающей запятой Брюса Доусона - хорошее место для начала при рассмотрении сравнения с плавающей запятой.
Следующие определения взяты из Искусство программирования Кнута:
bool approximatelyEqual(float a, float b, float epsilon)
{
return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool essentiallyEqual(float a, float b, float epsilon)
{
return fabs(a - b) <= ( (fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool definitelyGreaterThan(float a, float b, float epsilon)
{
return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
bool definitelyLessThan(float a, float b, float epsilon)
{
return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}
Конечно, выбор эпсилон зависит от контекста и определяет, насколько равными должны быть числа.
Другой метод сравнения чисел с плавающей запятой - посмотреть на ULP (единицы на последнем месте) чисел. Документ Что должен знать каждый компьютерный ученый о числах с плавающей запятой не касается конкретно сравнений, но является хорошим источником для понимания того, как работает с плавающей запятой, и каковы подводные камни, в том числе, что такое ULP.
Спасибо, что опубликовали, как определить, какое число меньше / больше!
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); спас мне жизнь. LOL Обратите внимание, что эта версия (я не проверял, применима ли она и к другим) также учитывает изменение, которое может произойти в неотъемлемой части числа с плавающей запятой (пример: 2147352577.9999997616 == 2147352576.0000000000, где вы можете ясно видеть, что есть почти разница 2 между двумя числами), что довольно приятно! Это происходит, когда накопленная ошибка округления выходит за пределы десятичной части числа.
Очень хорошая и полезная статья Брюса Доусона, спасибо!
Учитывая, что этот вопрос помечен как C++, ваши чеки будет легче читать, если они написаны как std::max(std::abs(a), std::abs(b)) (или std::min()); std::abs в C++ перегружен типами float и double, поэтому он отлично работает (хотя вы всегда можете оставить fabs для удобства чтения).
definitelyGreaterThan сообщает истинный для чего-то, что определенно должно быть равно, то есть нет больше чем.
Оказывается, проблема была в моем коде, разница между исходным ожидаемым значением и проанализированной строкой.
Я обнаружил, что Платформа тестирования Google C++ содержит хорошую кроссплатформенную реализацию на основе шаблонов почтиEqual2sComplement, которая работает как с числами типа double, так и с числами с плавающей запятой. Учитывая, что он выпущен под лицензией 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-лицензия).
Пример использования:
double left = // something
double right = // something
const FloatingPoint<double> lhs(left), rhs(right);
if (lhs.AlmostEquals(rhs)) {
//they're equal!
}
Вот код:
// Copyright 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: [email protected] (Zhanyong Wan), [email protected] (Sean Mcafee)
//
// The Google C++ Testing Framework (Google Test)
// This template class serves as a compile-time function from size to
// type. It maps a size in bytes to a primitive type with that
// size. e.g.
//
// TypeWithSize<4>::UInt
//
// is typedef-ed to be unsigned int (unsigned integer made up of 4
// bytes).
//
// Such functionality should belong to STL, but I cannot find it
// there.
//
// Google Test uses this class in the implementation of floating-point
// comparison.
//
// For now it only handles UInt (unsigned int) as that's all Google Test
// needs. Other types can be easily added in the future if need
// arises.
template <size_t size>
class TypeWithSize {
public:
// This prevents the user from using TypeWithSize<N> with incorrect
// values of N.
typedef void UInt;
};
// The specialization for size 4.
template <>
class TypeWithSize<4> {
public:
// unsigned int has size 4 in both gcc and MSVC.
//
// As base/basictypes.h doesn't compile on Windows, we cannot use
// uint32, uint64, and etc here.
typedef int Int;
typedef unsigned int UInt;
};
// The specialization for size 8.
template <>
class TypeWithSize<8> {
public:
#if GTEST_OS_WINDOWS
typedef __int64 Int;
typedef unsigned __int64 UInt;
#else
typedef long long Int; // NOLINT
typedef unsigned long long UInt; // NOLINT
#endif // GTEST_OS_WINDOWS
};
// This template class represents an IEEE floating-point number
// (either single-precision or double-precision, depending on the
// template parameters).
//
// The purpose of this class is to do more sophisticated number
// comparison. (Due to round-off error, etc, it's very unlikely that
// two floating-points will be equal exactly. Hence a naive
// comparison by the == operation often doesn't work.)
//
// Format of IEEE floating-point:
//
// The most-significant bit being the leftmost, an IEEE
// floating-point looks like
//
// sign_bit exponent_bits fraction_bits
//
// Here, sign_bit is a single bit that designates the sign of the
// number.
//
// For float, there are 8 exponent bits and 23 fraction bits.
//
// For double, there are 11 exponent bits and 52 fraction bits.
//
// More details can be found at
// http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
//
// Template parameter:
//
// RawType: the raw floating-point type (either float or double)
template <typename RawType>
class FloatingPoint {
public:
// Defines the unsigned integer type that has the same size as the
// floating point number.
typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;
// Constants.
// # of bits in a number.
static const size_t kBitCount = 8*sizeof(RawType);
// # of fraction bits in a number.
static const size_t kFractionBitCount =
std::numeric_limits<RawType>::digits - 1;
// # of exponent bits in a number.
static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;
// The mask for the sign bit.
static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);
// The mask for the fraction bits.
static const Bits kFractionBitMask =
~static_cast<Bits>(0) >> (kExponentBitCount + 1);
// The mask for the exponent bits.
static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);
// How many ULP's (Units in the Last Place) we want to tolerate when
// comparing two numbers. The larger the value, the more error we
// allow. A 0 value means that two numbers must be exactly the same
// to be considered equal.
//
// The maximum error of a single floating-point operation is 0.5
// units in the last place. On Intel CPU's, all floating-point
// calculations are done with 80-bit precision, while double has 64
// bits. Therefore, 4 should be enough for ordinary use.
//
// See the following article for more details on ULP:
// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm.
static const size_t kMaxUlps = 4;
// Constructs a FloatingPoint from a raw floating-point number.
//
// On an Intel CPU, passing a non-normalized NAN (Not a Number)
// around may change its bits, although the new value is guaranteed
// to be also a NAN. Therefore, don't expect this constructor to
// preserve the bits in x when x is a NAN.
explicit FloatingPoint(const RawType& x) { u_.value_ = x; }
// Static methods
// Reinterprets a bit pattern as a floating-point number.
//
// This function is needed to test the AlmostEquals() method.
static RawType ReinterpretBits(const Bits bits) {
FloatingPoint fp(0);
fp.u_.bits_ = bits;
return fp.u_.value_;
}
// Returns the floating-point number that represent positive infinity.
static RawType Infinity() {
return ReinterpretBits(kExponentBitMask);
}
// Non-static methods
// Returns the bits that represents this number.
const Bits &bits() const { return u_.bits_; }
// Returns the exponent bits of this number.
Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }
// Returns the fraction bits of this number.
Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }
// Returns the sign bit of this number.
Bits sign_bit() const { return kSignBitMask & u_.bits_; }
// Returns true iff this is NAN (not a number).
bool is_nan() const {
// It's a NAN if the exponent bits are all ones and the fraction
// bits are not entirely zeros.
return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
}
// Returns true iff this number is at most kMaxUlps ULP's away from
// rhs. In particular, this function:
//
// - returns false if either number is (or both are) NAN.
// - treats really large numbers as almost equal to infinity.
// - thinks +0.0 and -0.0 are 0 DLP's apart.
bool AlmostEquals(const FloatingPoint& rhs) const {
// The IEEE standard says that any comparison operation involving
// a NAN must return false.
if (is_nan() || rhs.is_nan()) return false;
return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
<= kMaxUlps;
}
private:
// The data type used to store the actual floating-point number.
union FloatingPointUnion {
RawType value_; // The raw floating-point number.
Bits bits_; // The bits that represent the number.
};
// Converts an integer from the sign-and-magnitude representation to
// the biased representation. More precisely, let N be 2 to the
// power of (kBitCount - 1), an integer x is represented by the
// unsigned number x + N.
//
// For instance,
//
// -N + 1 (the most negative number representable using
// sign-and-magnitude) is represented by 1;
// 0 is represented by N; and
// N - 1 (the biggest number representable using
// sign-and-magnitude) is represented by 2N - 1.
//
// Read http://en.wikipedia.org/wiki/Signed_number_representations
// for more details on signed number representations.
static Bits SignAndMagnitudeToBiased(const Bits &sam) {
if (kSignBitMask & sam) {
// sam represents a negative number.
return ~sam + 1;
} else {
// sam represents a positive number.
return kSignBitMask | sam;
}
}
// Given two numbers in the sign-and-magnitude representation,
// returns the distance between them as an unsigned number.
static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
const Bits &sam2) {
const Bits biased1 = SignAndMagnitudeToBiased(sam1);
const Bits biased2 = SignAndMagnitudeToBiased(sam2);
return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
}
FloatingPointUnion u_;
};
Обновлено: этому сообщению 4 года. Вероятно, он все еще в силе, и код хороший, но некоторые люди нашли улучшения. Лучше всего скачайте последнюю версию AlmostEquals прямо из исходного кода Google Test, а не из той, которую я здесь вставил.
+1: Я согласен, что это правильно. Однако это не объясняет почему. См. Здесь: cygnus-software.com/papers/comparingfloats/comparingfloats.h tm Я прочитал это сообщение в блоге после того, как написал здесь свой комментарий о наивысшем балле; Я считаю, что он говорит то же самое и предлагает рациональное решение, реализованное выше. Поскольку кода так много, люди упускают ответ.
Есть несколько неприятных вещей, которые могут произойти, когда происходит неявное приведение типов, скажем, FloatPoint
Хорошая находка! Думаю, лучше всего было бы внести их в Google Test, откуда этот код был украден. Я обновлю сообщение, чтобы отразить, что, вероятно, есть более новая версия. Если ребята из Google будут зудеть, не могли бы вы вставить его, например, суть GitHub? Тогда я тоже на это свяжусь.
Неужели только я получаю «ложь», сравнивая двойные 0 с 1e-16? Предвзятое представление 0 - 9223372036854775808, а предвзятое представление 1e-16 - 13590969439990876604. Кажется, это разрыв в представлении, или я делаю что-то не так?
Я извлек необходимые строки в файл сути. Любой может связаться с здесь.
Какой смысл публиковать это решение, если вы не объясняете, как оно работает? Бессмысленный ответ, я не понимаю, почему за него проголосовали.
Я был бы очень осторожен с любым из этих ответов, которые включают вычитание с плавающей запятой (например, fabs (a-b) катастрофическая отмена.
Хотя он и не переносится, я думаю, что ответ grom лучше всего помогает избежать этих проблем.
+1 за хорошую информацию. Однако я не понимаю, как можно испортить сравнение на равенство, увеличив относительную ошибку; IMHO ошибка становится существенной только в результате вычитания, однако ее порядок величины по сравнению с двумя вычитаемыми операндами должен быть достаточно надежным, чтобы судить о равенстве. Если только разрешение не должно быть выше в целом, но в этом случае единственное решение - перейти к представлению с плавающей запятой с более значимыми битами в мантиссе.
Вычитание двух почти равных чисел НЕ приводит к катастрофической отмене - фактически, оно вообще не вносит никакой ошибки (см. Теорему Стербенца). Катастрофическая отмена происходит раньше, при расчете самих a и b. Нет абсолютно никаких проблем с использованием вычитания с плавающей запятой как части нечеткого сравнения (хотя, как говорили другие, абсолютное значение эпсилон может или не может быть подходящим для данного варианта использования).
К сожалению, даже ваш «расточительный» код неверен. EPSILON - это наименьшее значение, которое можно добавить к 1.0 и изменить его значение. Значение 1.0 очень важно - большие числа не меняются при добавлении в EPSILON. Теперь вы можете масштабировать это значение в соответствии с числами, которые вы сравниваете, чтобы определить, разные они или нет. Правильное выражение для сравнения двух двойников:
if (fabs(a - b) <= DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
// ...
}
Это как минимум. В целом, однако, вы хотели бы учитывать шум в своих расчетах и игнорировать несколько наименее значимых битов, чтобы более реалистичное сравнение выглядело так:
if (fabs(a - b) <= 16 * DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
// ...
}
Если производительность сравнения очень важна для вас и вы знаете диапазон своих значений, вам следует вместо этого использовать числа с фиксированной запятой.
«EPSILON - это наименьшее значение, которое можно добавить к 1.0 и изменить его значение»: Фактически, эта честь достается преемнику 0.5 * EPSILON (в режиме округления до ближайшего по умолчанию). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Как вы думаете, почему EPSILON в вопросе - это DBL_EPSILON или FLT_EPSILON? Проблема в вашем собственном воображении, когда вы заменили DBL_EPSILON (что действительно было бы неправильным выбором) в код, который его не использовал.
@BenVoigt, ты прав, в то время я думал об этом, и я интерпретировал вопрос именно в этом свете.
Как указывали другие, использование эпсилон-показателя с фиксированной степенью (например, 0,0000001) будет бесполезный для значений, отличных от значения эпсилон. Например, если ваши два значения - 10000.000977 и 10000, то между этими двумя числами есть 32-битные значения с плавающей запятой НЕТ - 10000 и 10000.000977 настолько близки, насколько вы можете получить, не будучи побитно идентичными. Здесь эпсилон меньше 0,0009 не имеет смысла; вы также можете использовать прямой оператор равенства.
Аналогичным образом, когда два значения приближаются к размеру эпсилон, относительная ошибка возрастает до 100%.
Таким образом, попытки смешать число с фиксированной запятой, такое как 0,00001, со значениями с плавающей запятой (где показатель степени является произвольным) - бессмысленное упражнение. Это будет работать только в том случае, если вы можете быть уверены, что значения операндов лежат в узкой области (то есть, близко к некоторой определенной экспоненте), и если вы правильно выберете значение эпсилон для этого конкретного теста. Если вы вытащите число из воздуха («Эй! 0,00001 маленькое, так что это должно быть хорошо!»), Вы обречены на числовые ошибки. Я потратил много времени на отладку плохого числового кода, когда какой-то бедняга подбрасывает случайные значения эпсилон, чтобы заставить работать еще один тестовый пример.
Если вы занимаетесь численным программированием любого типа и считаете, что вам нужно достичь эпсилонов с фиксированной точкой, ПРОЧИТАЙТЕ СТАТЬЮ БРЮСА ПО СРАВНЕНИЮ ЧИСЕЛ ПЛАВАЮЩЕЙ ТОЧКИ.
Мой класс на основе ранее опубликованных ответов. Очень похоже на код Google, но я использую смещение, которое подталкивает все значения NaN выше 0xFF000000. Это позволяет быстрее проверять NaN.
Этот код предназначен для демонстрации концепции, а не для общего решения. Код Google уже показывает, как вычислить все значения, специфичные для платформы, и я не хотел все это дублировать. Я провел ограниченное тестирование этого кода.
typedef unsigned int U32;
// Float Memory Bias (unsigned)
// ----- ------ ---------------
// NaN 0xFFFFFFFF 0xFF800001
// NaN 0xFF800001 0xFFFFFFFF
// -Infinity 0xFF800000 0x00000000 ---
// -3.40282e+038 0xFF7FFFFF 0x00000001 |
// -1.40130e-045 0x80000001 0x7F7FFFFF |
// -0.0 0x80000000 0x7F800000 |--- Valid <= 0xFF000000.
// 0.0 0x00000000 0x7F800000 | NaN > 0xFF000000
// 1.40130e-045 0x00000001 0x7F800001 |
// 3.40282e+038 0x7F7FFFFF 0xFEFFFFFF |
// Infinity 0x7F800000 0xFF000000 ---
// NaN 0x7F800001 0xFF000001
// NaN 0x7FFFFFFF 0xFF7FFFFF
//
// Either value of NaN returns false.
// -Infinity and +Infinity are not "close".
// -0 and +0 are equal.
//
class CompareFloat{
public:
union{
float m_f32;
U32 m_u32;
};
static bool CompareFloat::IsClose( float A, float B, U32 unitsDelta = 4 )
{
U32 a = CompareFloat::GetBiased( A );
U32 b = CompareFloat::GetBiased( B );
if ( (a > 0xFF000000) || (b > 0xFF000000) )
{
return( false );
}
return( (static_cast<U32>(abs( a - b ))) < unitsDelta );
}
protected:
static U32 CompareFloat::GetBiased( float f )
{
U32 r = ((CompareFloat*)&f)->m_u32;
if ( r & 0x80000000 )
{
return( ~r - 0x007FFFFF );
}
return( r + 0x7F800000 );
}
};
На самом деле в числовом программном обеспечении есть случаи, когда вы хотите проверить, равны ли два числа с плавающей запятой точно. Я разместил это по аналогичному вопросу
https://stackoverflow.com/a/10973098/1447411
Так что нельзя сказать, что "CompareDoubles1" в целом неправильный.
На самом деле очень надежная ссылка на хороший ответ, хотя он очень специализирован, чтобы ограничить кого-либо без научных вычислений или опыта численного анализа (например, LAPACK, BLAS), чтобы не понимать полноту. Другими словами, предполагается, что вы читали что-то вроде Числовые рецепты Introduction или Числовой анализ от Burden & Faires.
Понимая, что это старая ветка, но эта статья - одна из самых простых, которые я нашел по сравнению чисел с плавающей запятой, и если вы хотите узнать больше, в ней также есть более подробные ссылки, и на основном сайте рассматривается полный спектр проблем. работа с числами с плавающей запятой Руководство по плавающим точкам: сравнение.
Мы можем найти несколько более практическую статью в Еще раз о допусках с плавающей запятой и отметить, что есть тест абсолютная терпимость, который сводится к этому в C++:
bool absoluteToleranceCompare(double x, double y)
{
return std::fabs(x - y) <= std::numeric_limits<double>::epsilon() ;
}
и тест относительная терпимость:
bool relativeToleranceCompare(double x, double y)
{
double maxXY = std::max( std::fabs(x) , std::fabs(y) ) ;
return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXY ;
}
В статье отмечается, что абсолютный тест не проходит, когда x и y большие, и терпит неудачу в относительном случае, когда они маленькие. Предполагая, что абсолютная и относительная толерантность одинаковы, комбинированный тест будет выглядеть так:
bool combinedToleranceCompare(double x, double y)
{
double maxXYOne = std::max( { 1.0, std::fabs(x) , std::fabs(y) } ) ;
return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXYOne ;
}
По количеству:
Если epsilon представляет собой небольшую часть величины количества (то есть относительной стоимости) в некотором определенном физическом смысле, а типы A и B сопоставимы в том же смысле, то я думаю, что следующее вполне правильно:
#include <limits>
#include <iomanip>
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cassert>
template< typename A, typename B >
inline
bool close_enough(A const & a, B const & b,
typename std::common_type< A, B >::type const & epsilon)
{
using std::isless;
assert(isless(0, epsilon)); // epsilon is a part of the whole quantity
assert(isless(epsilon, 1));
using std::abs;
auto const delta = abs(a - b);
auto const x = abs(a);
auto const y = abs(b);
// comparable generally and |a - b| < eps * (|a| + |b|) / 2
return isless(epsilon * y, x) && isless(epsilon * x, y) && isless((delta + delta) / (x + y), epsilon);
}
int main()
{
std::cout << std::boolalpha << close_enough(0.9, 1.0, 0.1) << std::endl;
std::cout << std::boolalpha << close_enough(1.0, 1.1, 0.1) << std::endl;
std::cout << std::boolalpha << close_enough(1.1, 1.2, 0.01) << std::endl;
std::cout << std::boolalpha << close_enough(1.0001, 1.0002, 0.01) << std::endl;
std::cout << std::boolalpha << close_enough(1.0, 0.01, 0.1) << std::endl;
return EXIT_SUCCESS;
}
Мой способ может быть неправильным, но полезным
Преобразуйте оба числа с плавающей запятой в строки, а затем выполните сравнение строк
bool IsFlaotEqual(float a, float b, int decimal)
{
TCHAR form[50] = _T("");
_stprintf(form, _T("%%.%df"), decimal);
TCHAR a1[30] = _T(""), a2[30] = _T("");
_stprintf(a1, form, a);
_stprintf(a2, form, b);
if ( _tcscmp(a1, a2) == 0 )
return true;
return false;
}
наложение операторов также может быть выполнено
+1: эй, я не собираюсь программировать игры с этим, но идея круговых поплавков возникала несколько раз в блоге Брюса Доусона (трактат?: D) по этой проблеме, и если вы в ловушке комнату, и кто-то приставляет пистолет к вашей голове и говорит: «Эй, вы должны сравнить два поплавка с точностью до X значащих цифр, у вас есть 5 минут, ИДТИ!» это, вероятно, стоит рассмотреть. ;)
@shelleybutterfly. Опять же, вопрос был в том, какой способ эффективный сравнивать два числа с плавающей запятой.
@TommyA lol, возможно, но держу пари, что за раунд-триппер проголосовали против по причинам, не связанным с эффективностью. Хотя моя интуиция подсказывает, что это было бы довольно неэффективно по сравнению с HW fp math, но я также говорю, что алгоритмы в программном fp вряд ли будут иметь большую разницу, по крайней мере. Я с нетерпением жду проведенного вами анализа, показывающего, что проблемы с эффективностью в этом случае значительны. Кроме того, иногда неоптимальный ответ все же может быть ценным ответом, и, поскольку он был отвергнут - несмотря на то, что это действующий метод, который даже упоминался в блоге Доусона по этому вопросу, поэтому я подумал, что он заслуживает голоса положительного.
/// testing whether two doubles are almost equal. We consider two doubles
/// equal if the difference is within the range [0, epsilon).
///
/// epsilon: a positive number (supposed to be small)
///
/// if either x or y is 0, then we are comparing the absolute difference to
/// epsilon.
/// if both x and y are non-zero, then we are comparing the relative difference
/// to epsilon.
bool almost_equal(double x, double y, double epsilon)
{
double diff = x - y;
if (x != 0 && y != 0){
diff = diff/y;
}
if (diff < epsilon && -1.0*diff < epsilon){
return true;
}
return false;
}
Я использовал эту функцию для своего небольшого проекта, и она работает, но обратите внимание на следующее:
Ошибка двойной точности может стать для вас сюрпризом. Скажем, epsilon = 1.0e-6, тогда 1.0 и 1.000001 НЕ должны считаться равными согласно приведенному выше коду, но на моей машине функция считает их равными, это потому, что 1.000001 нельзя точно перевести в двоичный формат, это вероятно 1.0000009xxx. Я тестировал его с 1.0 и 1.0000011 и на этот раз получил ожидаемый результат.
Вы не можете сравнивать два double с фиксированным EPSILON. В зависимости от значения double, EPSILON может быть разным.
Лучше двойное сравнение:
bool same(double a, double b)
{
return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
&& std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}
В более общем виде:
template <typename T>
bool compareNumber(const T& a, const T& b) {
return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}
Примечание:
Как указывает @SirGuy, этот подход ошибочен.
Я оставляю этот ответ здесь как пример, которому не следует следовать.
У этого метода много недостатков, например, если числа a и b уже меньше, чем epsilon(), разница может быть значительной. И наоборот, если числа очень большие, то даже пара битов ошибки приведет к сбою сравнения, даже если вы действительно хотите, чтобы числа считались равными. Это как раз тот тип «общего» алгоритма сравнения, которого вы хотите избежать.
@SirGuy Чем это отличается от ответа на 3 сообщения и 100+ голосов?
@algae, если вы спрашиваете, почему я поставил этот комментарий к этому ответу, а не тот, который получил более 100 голосов, тогда я не заметил, почему.
Почему бы не выполнить побитовое XOR? Два числа с плавающей запятой равны, если их соответствующие биты равны. Думаю, решение поместить биты экспоненты перед мантиссой было принято, чтобы ускорить сравнение двух чисел с плавающей запятой. Я думаю, что во многих ответах здесь отсутствует точка сравнения эпсилон. Значение Epsilon зависит только от того, с какой точностью сравниваются числа с плавающей запятой. Например, после выполнения некоторых арифметических действий с числами с плавающей запятой вы получите два числа: 2,5642943554342 и 2,5642943554345. Они не равны, но для решения имеют значение только 3 десятичные цифры, поэтому они равны: 2,564 и 2,564. В этом случае вы выбираете эпсилон равным 0,001. Сравнение Epsilon также возможно с помощью побитового XOR. Поправьте меня, если я ошибаюсь.
Пожалуйста, не добавляйте один и тот же ответ на несколько вопросов. Ответьте на лучший, а остальные отметьте как дубликаты. См. meta.stackexchange.com/questions/104227/…
Я не думаю, что «эпсилон-сравнение» возможно с использованием только ExOr (и одного или двух сравнений), даже ограниченного нормализованными представлениями в том же формате.
В итоге я потратил довольно много времени на изучение материала в этой замечательной ветке. Я сомневаюсь, что все хотят тратить так много времени, поэтому я хотел бы выделить краткое изложение того, что я узнал, и решения, которое я реализовал.
Краткое резюме
numeric_limits::epsilon(), который совпадает с FLT_EPSILON в float.h. Однако это проблематично, потому что эпсилон для сравнения значений типа 1.0 не то же самое, что эпсилон для значений типа 1Е9. FLT_EPSILON определен для 1.0.fabs(a-b) <= epsilon, однако это не работает, потому что эпсилон по умолчанию определен для 1.0. Нам нужно масштабировать эпсилон вверх или вниз с точки зрения a и b.max(a,b), либо вы можете получить следующие представимые числа вокруг a, а затем посмотреть, попадает ли b в этот диапазон. Первый называется «относительным» методом, а позже - методом ULP.Реализация служебных функций (C++ 11)
//implements relative method - do not use for comparing with zero
//use this most of the time, tolerance needs to be meaningful in your context
template<typename TReal>
static bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
TReal diff = std::fabs(a - b);
if (diff <= tolerance)
return true;
if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
return true;
return false;
}
//supply tolerance that is meaningful in your context
//for example, default tolerance may not work if you are comparing double with float
template<typename TReal>
static bool isApproximatelyZero(TReal a, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
if (std::fabs(a) <= tolerance)
return true;
return false;
}
//use this when you want to be on safe side
//for example, don't start rover unless signal is above 1
template<typename TReal>
static bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
TReal diff = a - b;
if (diff < tolerance)
return true;
if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
return true;
return false;
}
template<typename TReal>
static bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
TReal diff = a - b;
if (diff > tolerance)
return true;
if (diff > std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
return true;
return false;
}
//implements ULP method
//use this when you are only concerned about floating point precision issue
//for example, if you want to see if a is 1.0 by checking if its within
//10 closest representable floating point numbers around 1.0.
template<typename TReal>
static bool isWithinPrecisionInterval(TReal a, TReal b, unsigned int interval_size = 1)
{
TReal min_a = a - (a - std::nextafter(a, std::numeric_limits<TReal>::lowest())) * interval_size;
TReal max_a = a + (std::nextafter(a, std::numeric_limits<TReal>::max()) - a) * interval_size;
return min_a <= b && max_a >= b;
}
isDefinitelyLessThan проверяет diff < tolerance, что означает, что a и b почти равны (и поэтому a не определенно меньше b). Разве в обоих случаях не имеет смысла проверять разницу> толерантность? Или, возможно, добавьте аргумент orEqualTo, который контролирует, должна ли приблизительная проверка равенства возвращать истину или нет.
Для отношений меньше и больше, нам нужно использовать < и >.
Я использую этот код:
bool AlmostEqual(double v1, double v2)
{
return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
}
epsilon не для этого.
Почему нет? Вы можете это объяснить?
@debuti epsilon - это просто расстояние между 1 и следующим представимым числом после 1. В лучшем случае этот код просто пытается проверить, равны ли два числа точно друг другу, но потому что не степени 2 умножаются на epsilon , он даже это не делает правильно.
Да, и std::fabs(std::min(v1, v2)) неверен - для отрицательных входов выбирает тот, у которого большая величина.
Я пишу это для java, но, возможно, вам это пригодится. Он использует long вместо double, но заботится о NaN, субнормальных значениях и т. д.
public static boolean equal(double a, double b) {
final long fm = 0xFFFFFFFFFFFFFL; // fraction mask
final long sm = 0x8000000000000000L; // sign mask
final long cm = 0x8000000000000L; // most significant decimal bit mask
long c = Double.doubleToLongBits(a), d = Double.doubleToLongBits(b);
int ea = (int) (c >> 52 & 2047), eb = (int) (d >> 52 & 2047);
if (ea == 2047 && (c & fm) != 0 || eb == 2047 && (d & fm) != 0) return false; // NaN
if (c == d) return true; // identical - fast check
if (ea == 0 && eb == 0) return true; // ±0 or subnormals
if ((c & sm) != (d & sm)) return false; // different signs
if (abs(ea - eb) > 1) return false; // b > 2*a or a > 2*b
d <<= 12; c <<= 12;
if (ea < eb) c = c >> 1 | sm;
else if (ea > eb) d = d >> 1 | sm;
c -= d;
return c < 65536 && c > -65536; // don't use abs(), because:
// There is a posibility c=0x8000000000000000 which cannot be converted to positive
}
public static boolean zero(double a) { return (Double.doubleToLongBits(a) >> 52 & 2047) < 3; }
Имейте в виду, что после ряда операций с плавающей запятой число может сильно отличаться от ожидаемого. Нет кода, чтобы исправить это.
Вот доказательство того, что использование std::numeric_limits::epsilon() не является ответом - он не работает для значений больше единицы:
Доказательство моего комментария выше:
#include <stdio.h>
#include <limits>
double ItoD (__int64 x) {
// Return double from 64-bit hexadecimal representation.
return *(reinterpret_cast<double*>(&x));
}
void test (__int64 ai, __int64 bi) {
double a = ItoD(ai), b = ItoD(bi);
bool close = std::fabs(a-b) < std::numeric_limits<double>::epsilon();
printf ("%.16f and %.16f %s close.\n", a, b, close ? "are " : "are not");
}
int main()
{
test (0x3fe0000000000000L,
0x3fe0000000000001L);
test (0x3ff0000000000000L,
0x3ff0000000000001L);
}
Запуск дает такой результат:
0.5000000000000000 and 0.5000000000000001 are close.
1.0000000000000000 and 1.0000000000000002 are not close.
Обратите внимание, что во втором случае (один и чуть больше одного) два входных значения настолько близки, насколько это возможно, и все же сравниваются как не близкие. Таким образом, для значений больше 1.0 вы можете просто использовать тест на равенство. Фиксированные эпсилоны не спасут вас при сравнении значений с плавающей запятой.
Я считаю, что return *(reinterpret_cast<double*>(&x));, хотя он обычно работает, на самом деле является неопределенным поведением.
Справедливо, хотя этот код является иллюстративным, поэтому достаточно, чтобы продемонстрировать проблему для точки покрытия numeric_limits<>::epsilon и IEEE 754.
Также справедливо, но, imho, неразумно публиковать сообщения о переполнении стека, ожидая такого понимания. Код буду копируется вслепую, что еще больше затрудняет искоренение этого очень распространенного паттерна - вместе с уловкой объединения - которого следует просто избегать, как и следует всем UD.
Qt реализует две функции, возможно, вы сможете у них поучиться:
static inline bool qFuzzyCompare(double p1, double p2)
{
return (qAbs(p1 - p2) <= 0.000000000001 * qMin(qAbs(p1), qAbs(p2)));
}
static inline bool qFuzzyCompare(float p1, float p2)
{
return (qAbs(p1 - p2) <= 0.00001f * qMin(qAbs(p1), qAbs(p2)));
}
И вам могут понадобиться следующие функции, так как
Note that comparing values where either p1 or p2 is 0.0 will not work, nor does comparing values where one of the values is NaN or infinity. If one of the values is always 0.0, use qFuzzyIsNull instead. If one of the values is likely to be 0.0, one solution is to add 1.0 to both values.
static inline bool qFuzzyIsNull(double d)
{
return qAbs(d) <= 0.000000000001;
}
static inline bool qFuzzyIsNull(float f)
{
return qAbs(f) <= 0.00001f;
}
Нашел еще одну интересную реализацию на: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
#include <cmath>
#include <limits>
#include <iomanip>
#include <iostream>
#include <type_traits>
#include <algorithm>
template<class T>
typename std::enable_if<!std::numeric_limits<T>::is_integer, bool>::type
almost_equal(T x, T y, int ulp)
{
// the machine epsilon has to be scaled to the magnitude of the values used
// and multiplied by the desired precision in ULPs (units in the last place)
return std::fabs(x-y) <= std::numeric_limits<T>::epsilon() * std::fabs(x+y) * ulp
// unless the result is subnormal
|| std::fabs(x-y) < std::numeric_limits<T>::min();
}
int main()
{
double d1 = 0.2;
double d2 = 1 / std::sqrt(5) / std::sqrt(5);
std::cout << std::fixed << std::setprecision(20)
<< "d1 = " << d1 << "\nd2 = " << d2 << '\n';
if (d1 == d2)
std::cout << "d1 == d2\n";
else
std::cout << "d1 != d2\n";
if (almost_equal(d1, d2, 2))
std::cout << "d1 almost equals d2\n";
else
std::cout << "d1 does not almost equal d2\n";
}
Это еще одно решение с лямбда:
#include <cmath>
#include <limits>
auto Compare = [](float a, float b, float epsilon = std::numeric_limits<float>::epsilon()){ return (std::fabs(a - b) <= epsilon); };
Это точно так же, как и многие другие ответы, за исключением того, что это лямбда и не имеет объяснения, поэтому это не добавляет большого значения в качестве ответа.
Как насчет этого?
template<typename T>
bool FloatingPointEqual( T a, T b ) { return !(a < b) && !(b < a); }
Я видел разные подходы - но никогда не видел этого, поэтому мне тоже любопытно услышать какие-либо комментарии!
@Mehdi Я только что пробовал использовать repl.it/repls/SvelteSimpleNumerator#main.cpp, и, похоже, он ведет себя так, как ожидалось, но, возможно, у вас есть конкретная реализация компилятора, на которую можно ссылаться, которая этого не делает?
Это то же самое, что и A==B, только с большим количеством инструкций, и не решает проблему того, что это сравнение часто оказывается ложным просто из-за числовых ошибок, поэтому вы обычно используете что-то вроде std::abs(A-B) < epsilon. Он также вводит странное поведение, заключающееся в том, что любое действительное или недопустимое число с плавающей запятой, которое вы сравниваете со значением NaN, вернет истину.
Следующим способом вы сравниваете системно-зависимое «строковое представление» двух значений (в вашем случае с плавающей точкой). Аналогично тому, как вы распечатываете их оба и видите своими глазами, выглядят ли они одинаково:
#include <iostream>
#include <string>
bool floatApproximatelyEquals(const float a, const float b) {
return std::to_string(a) == std::to_string(b);
}
Proc:
Минусы:
Вы должны выполнить эту обработку для сравнения с плавающей запятой, так как float нельзя точно сравнить, как целочисленные типы. Вот функции для различных операторов сравнения.
==)Я также предпочитаю метод вычитания, а не полагаться на fabs() или abs(), но мне пришлось бы ускорить его профилирование на различных архитектурах от 64-битного ПК до микроконтроллера ATMega328 (Arduino), чтобы действительно увидеть, сильно ли это влияет на производительность.
Итак, давайте забудем обо всем этом, что касается абсолютных значений, и просто проведем вычитание и сравнение!
Изменено с Пример Microsoft здесь:
/// @brief See if two floating point numbers are approximately equal.
/// @param[in] a number 1
/// @param[in] b number 2
/// @param[in] epsilon A small value such that if the difference between the two numbers is
/// smaller than this they can safely be considered to be equal.
/// @return true if the two numbers are approximately equal, and false otherwise
bool is_float_eq(float a, float b, float epsilon) {
return ((a - b) < epsilon) && ((b - a) < epsilon);
}
bool is_double_eq(double a, double b, double epsilon) {
return ((a - b) < epsilon) && ((b - a) < epsilon);
}
Пример использования:
constexpr float EPSILON = 0.0001; // 1e-4
is_float_eq(1.0001, 0.99998, EPSILON);
Я не совсем уверен, но мне кажется, что некоторые из критических замечаний по поводу подхода, основанного на эпсилоне, как описано в комментариях ниже этот ответ, получивший высокую оценку, могут быть решены с помощью переменной epsilon, масштабируемой в соответствии с сравниваемыми значениями с плавающей запятой, так:
float a = 1.0001;
float b = 0.99998;
float epsilon = std::max(std::fabs(a), std::fabs(b)) * 1e-4;
is_float_eq(a, b, epsilon);
Таким образом, значение эпсилон масштабируется вместе со значениями с плавающей запятой и, следовательно, никогда не бывает настолько маленьким, что становится несущественным.
Для полноты картины добавим остальное:
>) и меньше (<):/// @brief See if floating point number `a` is > `b`
/// @param[in] a number 1
/// @param[in] b number 2
/// @param[in] epsilon a small value such that if `a` is > `b` by this amount, `a` is considered
/// to be definitively > `b`
/// @return true if `a` is definitively > `b`, and false otherwise
bool is_float_gt(float a, float b, float epsilon) {
return a > b + epsilon;
}
bool is_double_gt(double a, double b, double epsilon) {
return a > b + epsilon;
}
/// @brief See if floating point number `a` is < `b`
/// @param[in] a number 1
/// @param[in] b number 2
/// @param[in] epsilon a small value such that if `a` is < `b` by this amount, `a` is considered
/// to be definitively < `b`
/// @return true if `a` is definitively < `b`, and false otherwise
bool is_float_lt(float a, float b, float epsilon) {
return a < b - epsilon;
}
bool is_double_lt(double a, double b, double epsilon) {
return a < b - epsilon;
}
>=) и меньше или равно (<=)/// @brief Returns true if `a` is definitively >= `b`, and false otherwise
bool is_float_ge(float a, float b, float epsilon) {
return a > b - epsilon;
}
bool is_double_ge(double a, double b, double epsilon) {
return a > b - epsilon;
}
/// @brief Returns true if `a` is definitively <= `b`, and false otherwise
bool is_float_le(float a, float b, float epsilon) {
return a < b + epsilon;
}
bool is_double_le(double a, double b, double epsilon) {
return a < b + epsilon;
}
Единственное, что неоптимально в оригинальной реализации плаката - это то, что он содержит дополнительную ветку в &&. Ответ OJ оптимален. fabs - это внутренняя функция, которая представляет собой единственную инструкцию на x87, и, я полагаю, почти на все остальное тоже. Примите уже ответ OJ!