Каков наиболее эффективный способ плавающего и двойного сравнения?

Какой способ сравнения двух значений 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);
}

Похоже на переработку отходов.

Кто-нибудь знает более умный компаратор поплавков?

Единственное, что неоптимально в оригинальной реализации плаката - это то, что он содержит дополнительную ветку в &&. Ответ OJ оптимален. fabs - это внутренняя функция, которая представляет собой единственную инструкцию на x87, и, я полагаю, почти на все остальное тоже. Примите уже ответ OJ!

3yE 27.03.2010 20:04

Если можете, отбросьте плавающую точку и используйте фиксированные точки. Например, используйте миллиметры с {фиксированной запятой} вместо метров с {плавающей запятой}.

Thomas Matthews 11.06.2012 06:11

Вот как это реализовано в библиотеке Boost Test Library: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/‌ testing-tools / floati‌ ng_point_comparison.‌ html

Alessandro Jacopson 31.10.2008 16:38

> Было бы эффективнее добавить ... в начало функции? <invoke Knuth> Преждевременная оптимизация - корень всех зол </invoke Knuth> Просто используйте абс (a-b)

Andrew Coleson 20.08.2008 09:55

Похоже, вы пропустили последний пункт своей цитаты - «только после того, как этот [критический] код был идентифицирован». Если этот код не будет идентифицирован как узкое место, его оптимизация до нечеткости будет пустой тратой времени (и потенциально опасной).

Andrew Coleson 23.02.2010 08:55
«Просто делать это неправильно» - This is mere rubbish, of course using == can be perfectly correct, but this entirely depends on the context not given in the question. Until that context is known, == still stays the "самый эффективный способ".
Christian Rau 13.05.2013 11:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
567
7
476 584
32

Ответы 32

Сравнение с эпсилон-значением - это то, что делает большинство людей (даже в программировании игр).

Однако вам следует немного изменить свою реализацию:

bool AreSame(double a, double b)
{
    return fabs(a - b) < EPSILON;
}

Обновлено: Кристер добавил много отличной информации по этой теме на последнее сообщение в блоге. Наслаждаться.

@OJ: что-то не так с первым образцом кода? Я думал, что единственная проблема была в такой ситуации: float a = 3.4; if (a == 3.4){...}, т.е. когда вы сравниваете сохраненную плавающую точку с литералом | В этом случае сохраняются оба числа, поэтому они будут иметь одинаковое представление, если они равны, так какой вред при использовании a == b?

Lazer 18.04.2010 22:18

@ Лазер: cygnus-software.com/papers/comparingfloats/comparingfloats.h‌ tm.

peterchen 07.08.2010 10:47

@DonReba: только если EPSILON определен как DBL_EPSILON. Обычно это конкретное значение, выбираемое в зависимости от требуемой точности сравнения.

Nemo157 21.12.2011 00:38

Сравнение EPSILON не работает, когда числа с плавающей запятой большие, так как разница между последовательными числами с плавающей запятой также становится большой. См. Эта статья.

kevintodisco 23.10.2013 08:04

Неудивительно, что в некоторых играх есть Z-файтинг, когда текстуры / объекты вдали мерцают, как в Battlefield 4. Сравнивать разницу с EPSILON бесполезно. Вам нужно сравнить с порогом, который имеет смысл для имеющихся единиц. Также используйте std::abs, поскольку он перегружен для разных типов с плавающей запятой.

Maxim Egorushkin 19.02.2014 20:40

Старая ссылка кажется устаревшей, здесь новая страница randomascii.wordpress.com/2012/02/25/…

Marson Mao 27.10.2016 05:33

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

user2261015 13.04.2017 10:58

Он будет работать правильно, ЕСЛИ этого требует ваша ситуация.

qwr 21.02.2020 23:44

У этого метода много недостатков, например, если числа a и b уже меньше, чем у EPSILON, их разница может быть значительной. И наоборот, если числа очень большие, то даже пара битов ошибки приведет к сбою сравнения, даже если вы действительно хотите, чтобы числа считались равными. Вы можете сделать EPSILON параметром функции, а затем передать ответственность за выбор значения вызывающей стороне, но я думаю, что на практике люди не будут особо задумываться о том, что им следует передать, и поэтому проблема не исчезнет.

SirGuy 18.08.2020 16:47

@SirGuy, опубликуйте ответ, чтобы продемонстрировать, как это правильно сделать, или дайте ссылку на него здесь. Я бы хотел увидеть альтернативы, не основанные на эпсилоне.

Gabriel Staples 26.11.2020 05:36

Однако, продолжая использовать эпсилон-подход, можно полностью избавиться от fabs(), дважды используя вычитание и &&, как я показываю в своем новом ответе здесь: stackoverflow.com/a/65015333/4561887.

Gabriel Staples 26.11.2020 05:37

@SirGuy, я просто добавил подход в моем ответе, который вычисляет переменную epsilon на основе сравниваемых значений с плавающей запятой. Интересно, решает ли это проблемы?

Gabriel Staples 26.11.2020 05:58

Код, который вы написали, ошибочен:

return (diff < EPSILON) && (-diff > EPSILON);

Правильный код будет:

return (diff < EPSILON) && (diff > -EPSILON);

(... и да, это другое)

Интересно, не заставят ли фабрики в некоторых случаях потерять ленивую оценку. Я бы сказал, это зависит от компилятора. Возможно, вы захотите попробовать и то, и другое. Если в среднем они эквивалентны, возьмите реализацию с фабсами.

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

Наконец, вы можете получить лучший результат, вставив эту функцию. Хотя вряд ли сильно улучшится ...

Обновлено: OJ, спасибо за исправление вашего кода. Я удалил свой комментарий соответственно

Вопрос был отредактирован, чтобы быть правильным. И return (diff < EPSILON) && (diff > -EPSILON);, и return (diff < EPSILON) && (-diff < EPSILON); эквивалентны и оба верны.

Gabriel Staples 26.11.2020 08:50

Для более глубокого подхода прочтите Сравнение чисел с плавающей запятой. Вот фрагмент кода по этой ссылке:

// 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?

unj2 01.08.2011 05:10

Будет ли "*(int*)&A;" нарушать строгое правило псевдонима?

osgx 11.08.2011 09:31

Нет. Это копирование, а не псевдоним.

Alan Baljeu 09.04.2012 19:51

Согласно gtest (поиск ULP), 4 является приемлемым числом.

May Oakes 18.07.2012 00:30

И вот пара обновлений статьи Брюса Доусона (одно из которых указано во введении к статье): randomascii.wordpress.com/2012/02/25/… и randomascii.wordpress.com/2012/06/26/…

Michael Burr 14.08.2012 22:07

Мне потребовалось некоторое время, чтобы понять, что было на ULP: единицы на последнем месте

JeffCharter 13.03.2014 20:19

@JeffCharter или единицы наименьшей точности.

Steve Hollasch 31.05.2018 21:42

@osgx: Да, это нарушает строгий псевдоним. А правильный код настолько прост: memcpy(&aInt, &A, sizeof (int)); Добавьте static_assert(sizeof(int) == sizeof(float)); на всякий случай.

Ben Voigt 13.05.2019 07:06

@Ben Нарушение строгого псевдонима в этом случае не имеет значения, поскольку указатель никогда не может быть псевдонимом (потому что указатель немедленно разыменовывается). Это будет иметь значение только в многопоточной среде, и если это создаст для вас ошибку, значит, вы написали действительно ужасный код. Во всех остальных случаях memcpy - это пустая трата ресурсов процессора и программиста.

yyny 12.01.2021 13:07

Это зависит от того, насколько точным будет сравнение. Если вы хотите сравнить точно такое же число, просто используйте ==. (Вы почти никогда не захотите этого делать, если на самом деле не хотите точно такое же число.) На любой достойной платформе вы также можете сделать следующее:

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 21.10.2010 04:00

@brlcad Вы не поняли суть точки плавающий. 1.0E-10 и 1.0E-9 различаются величиной 10. Так что это правда, что они не одно и то же. Точка плавающий всегда связана с ошибками относительный. Если у вас есть система, которая рассматривает 1.0E-10 и 1.0E-9 как почти равные, поскольку оба они «довольно близки к нулю» (что звучит разумно для людей, но не имеет ничего общего с математикой), то этот EPSILON необходимо отрегулировать соответствующим образом для такой системы.

user2261015 12.11.2015 14:10

Переносимый способ получить 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, если хотите оставаться последовательным.

Alexandre C. 06.08.2010 15:42

Скорее всего, вам понадобится кратное этому эпсилону.

user7116 08.08.2010 05:41

Разве вы не можете просто использовать std :: abs? AFAIK, std :: abs также перегружен для удвоений. Пожалуйста, предупредите меня, если я ошибаюсь.

kolistivra 25.09.2010 12:41

@kolistivra, вы ошибаетесь. 'F' в 'fabs' не означает тип float. Вы, вероятно, думаете о функциях языка C fabsf () и fabsl ().

jcoffland 24.01.2012 13:21

Очевидно, неверно, поскольку он будет считать два двойных значения больше 2 одинаковыми, только если они равны, и при этом epsilon / 1024 и epsilon будут считаться одинаковыми.

AProgrammer 27.01.2012 01:16

Фактически по причинам изложено в статье Брюсаэпсилон изменения, поскольку значение с плавающей запятой становится больше. Смотрите ту часть, где он говорит «Для чисел больше 2,0 разрыв между числами с плавающей запятой увеличивается, и если вы сравниваете числа с плавающей запятой с помощью FLT_EPSILON, то вы просто выполняете более дорогостоящую и менее очевидную проверку равенства».

bobobobo 19.01.2013 05:07

Я знаю, что это старый, но std :: abs перегружен для типов с плавающей запятой в cmath.

mholzmann 30.06.2013 22:45

@kolistivra не ошибается, хотя, вероятно, лучше использовать std :: fabs, чтобы избежать случайного преобразования в int. stackoverflow.com/questions/3118165/…

Josh Milthorpe 27.07.2016 22:35

Нет. Если a или b больше или равно единице, то это то же самое, что и использование оператора равенства. Если a и b меньше единицы, то относительный размер epsilon будет увеличиваться по мере уменьшения значений a и b. Из en.cppreference.com/w/cpp/types/numeric_limits/epsilonstd::numeric_limits<T>::epsilon() «возвращает машинный эпсилон, то есть разницу между 1.0 и следующим значением, представленным типом с плавающей запятой T.»

Steve Hollasch 31.05.2018 21:39

Будьте предельно осторожны, используя любые другие предложения. Все зависит от контекста.

Я провел много времени, отслеживая ошибки в системе, которая предполагала a==b, если |a-b|<epsilon. Основные проблемы заключались в следующем:

  1. Неявное предположение в алгоритме, что если a==b и b==c, то a==c.

  2. Использование одного и того же эпсилон для линий, измеренных в дюймах, и линий, измеренных в милах (0,001 дюйма). То есть a==b, но 1000a!=1000b. (Вот почему почтиEqual2sComplement запрашивает epsilon или max ULPS).

  3. Использование одного и того же эпсилона как для косинуса углов, так и для длины линий!

  4. Использование такой функции сравнения для сортировки элементов в коллекции. (В этом случае использование встроенного оператора 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 и переполнения это становится довольно сложным.

peterchen 07.08.2010 10:41

Должно быть что-то я не понимаю. Типичный float / double - МАНТИССА x 2 ^ EXP. epsilon будет зависеть от экспоненты. Например, если мантисса составляет 24 бита, а экспонента - 8 бит со знаком, то 1/(2^24)*2^127 или ~2^103 является epsilon для некоторых значений; или это относится к минимальному эпсилон?

artless noise 17.03.2013 22:54

Подожди секунду. Ты имел в виду то, что я сказал? Вы говорите, почему |a-b|<epsilon является правильным нет. Добавьте эту ссылку к своему ответу; если вы согласны с cygnus-software.com/papers/comparingfloats/comparingfloats.h‌ tm и я могу удалить свои тупые комментарии.

artless noise 17.03.2013 23:16

+1 В связи с чем возникает необходимость в использовании специальной погрешности в зависимости от сценария и того, что вы сравниваете

Khaled.K 28.05.2013 13:04

В статье, упомянутой выше, есть смысл: cygnus-software.com/papers/comparingfloats/comparingfloats.h‌ tm -> почтиEqualRelativeOrAbsolute (...) подходит лучше всего. При использовании точки плавающий вы имеете дело с относительными ошибками, иначе вам не понадобится точка плавающий. Проблема в том, что большинство людей используют числа с плавающей точкой из-за своей лени. Кажется, это так просто. Но это не так. Большинству людей будет лучше с фиксированной точкой, но это так экзотично :)

user2261015 12.11.2015 14:17

Если кому интересно, сделал версию на дубль решения @ 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; }

Paulo Carvalho 12.02.2016 13:41

Это очень длинный комментарий, а не ответ сам по себе. Есть ли (набор) канонических ответов для всех контекстов?

Merlyn Morgan-Graham 30.07.2016 05:49

Старая ссылка кажется устаревшей, здесь новая страница randomascii.wordpress.com/2012/02/25/…

Marson Mao 27.10.2016 05:33

здесь есть хороший пример, как это сделать со стандартной библиотекой: en.cppreference.com/w/cpp/types/numeric_limits/epsilon

johnbakers 13.02.2017 21:28

Это не отвечает на вопрос.

qwr 30.01.2020 03:41

На самом деле, если a == b и b == c, тогда a == c является истинным утверждением. Когда вы начинаете пытаться использовать алгебру и предполагаете, что ошибок точности не будет, вы столкнетесь с проблемами.

John Thoits 24.11.2020 02:45

Сравнение чисел с плавающей запятой зависит от контекста. Поскольку даже изменение порядка операций может привести к разным результатам, важно знать, насколько «равными» должны быть числа.

Сравнение чисел с плавающей запятой Брюса Доусона - хорошее место для начала при рассмотрении сравнения с плавающей запятой.

Следующие определения взяты из Искусство программирования Кнута:

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.

Спасибо, что опубликовали, как определить, какое число меньше / больше!

Tomato 12.12.2013 09:26

fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); спас мне жизнь. LOL Обратите внимание, что эта версия (я не проверял, применима ли она и к другим) также учитывает изменение, которое может произойти в неотъемлемой части числа с плавающей запятой (пример: 2147352577.9999997616 == 2147352576.0000000000, где вы можете ясно видеть, что есть почти разница 2 между двумя числами), что довольно приятно! Это происходит, когда накопленная ошибка округления выходит за пределы десятичной части числа.

rbaleksandar 12.10.2016 15:24

Очень хорошая и полезная статья Брюса Доусона, спасибо!

BobMorane 28.08.2018 22:14

Учитывая, что этот вопрос помечен как C++, ваши чеки будет легче читать, если они написаны как std::max(std::abs(a), std::abs(b)) (или std::min()); std::abs в C++ перегружен типами float и double, поэтому он отлично работает (хотя вы всегда можете оставить fabs для удобства чтения).

Razakhel 01.10.2018 20:09

definitelyGreaterThan сообщает истинный для чего-то, что определенно должно быть равно, то есть нет больше чем.

mwpowellhtx 20.01.2019 00:05

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

mwpowellhtx 20.01.2019 20:31

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

artless noise 17.03.2013 23:07

Есть несколько неприятных вещей, которые могут произойти, когда происходит неявное приведение типов, скажем, FloatPointfp (0,03f). Я сделал несколько модификаций, чтобы предотвратить это. templateявный FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr

JeffCharter 18.03.2014 01:16

Хорошая находка! Думаю, лучше всего было бы внести их в Google Test, откуда этот код был украден. Я обновлю сообщение, чтобы отразить, что, вероятно, есть более новая версия. Если ребята из Google будут зудеть, не могли бы вы вставить его, например, суть GitHub? Тогда я тоже на это свяжусь.

skrebbel 01.05.2014 16:48

Неужели только я получаю «ложь», сравнивая двойные 0 с 1e-16? Предвзятое представление 0 - 9223372036854775808, а предвзятое представление 1e-16 - 13590969439990876604. Кажется, это разрыв в представлении, или я делаю что-то не так?

Gaston 01.10.2014 21:17

Для новейшего фрагмента кода см. здесь и здесь.

Jaege 25.11.2016 06:11

Я извлек необходимые строки в файл сути. Любой может связаться с здесь.

Yusuf Tarık Günaydın 25.07.2017 21:40

Какой смысл публиковать это решение, если вы не объясняете, как оно работает? Бессмысленный ответ, я не понимаю, почему за него проголосовали.

miguel.martin 12.09.2017 09:48

Я был бы очень осторожен с любым из этих ответов, которые включают вычитание с плавающей запятой (например, fabs (a-b) катастрофическая отмена.

Хотя он и не переносится, я думаю, что ответ grom лучше всего помогает избежать этих проблем.

+1 за хорошую информацию. Однако я не понимаю, как можно испортить сравнение на равенство, увеличив относительную ошибку; IMHO ошибка становится существенной только в результате вычитания, однако ее порядок величины по сравнению с двумя вычитаемыми операндами должен быть достаточно надежным, чтобы судить о равенстве. Если только разрешение не должно быть выше в целом, но в этом случае единственное решение - перейти к представлению с плавающей запятой с более значимыми битами в мантиссе.

sehe 17.05.2011 13:10

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

Sneftel 26.05.2018 15:54

К сожалению, даже ваш «расточительный» код неверен. 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

Pascal Cuoq 29.08.2013 23:05

Как вы думаете, почему EPSILON в вопросе - это DBL_EPSILON или FLT_EPSILON? Проблема в вашем собственном воображении, когда вы заменили DBL_EPSILON (что действительно было бы неправильным выбором) в код, который его не использовал.

Ben Voigt 21.04.2015 06:52

@BenVoigt, ты прав, в то время я думал об этом, и я интерпретировал вопрос именно в этом свете.

Don Reba 21.04.2015 11:56

Как указывали другие, использование эпсилон-показателя с фиксированной степенью (например, 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.

mctylr 09.08.2013 23:47

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

Мы можем найти несколько более практическую статью в Еще раз о допусках с плавающей запятой и отметить, что есть тест абсолютная терпимость, который сводится к этому в 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 25.05.2014 06:04

@shelleybutterfly. Опять же, вопрос был в том, какой способ эффективный сравнивать два числа с плавающей запятой.

Tommy Andersen 13.08.2014 15:11

@TommyA lol, возможно, но держу пари, что за раунд-триппер проголосовали против по причинам, не связанным с эффективностью. Хотя моя интуиция подсказывает, что это было бы довольно неэффективно по сравнению с HW fp math, но я также говорю, что алгоритмы в программном fp вряд ли будут иметь большую разницу, по крайней мере. Я с нетерпением жду проведенного вами анализа, показывающего, что проблемы с эффективностью в этом случае значительны. Кроме того, иногда неоптимальный ответ все же может быть ценным ответом, и, поскольку он был отвергнут - несмотря на то, что это действующий метод, который даже упоминался в блоге Доусона по этому вопросу, поэтому я подумал, что он заслуживает голоса положительного.

shelleybutterfly 24.08.2014 12:04

/// 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 22.09.2016 05:05

@SirGuy Чем это отличается от ответа на 3 сообщения и 100+ голосов?

algae 18.08.2020 14:33

@algae, если вы спрашиваете, почему я поставил этот комментарий к этому ответу, а не тот, который получил более 100 голосов, тогда я не заметил, почему.

SirGuy 18.08.2020 16:41

Почему бы не выполнить побитовое XOR? Два числа с плавающей запятой равны, если их соответствующие биты равны. Думаю, решение поместить биты экспоненты перед мантиссой было принято, чтобы ускорить сравнение двух чисел с плавающей запятой. Я думаю, что во многих ответах здесь отсутствует точка сравнения эпсилон. Значение Epsilon зависит только от того, с какой точностью сравниваются числа с плавающей запятой. Например, после выполнения некоторых арифметических действий с числами с плавающей запятой вы получите два числа: 2,5642943554342 и 2,5642943554345. Они не равны, но для решения имеют значение только 3 десятичные цифры, поэтому они равны: 2,564 и 2,564. В этом случае вы выбираете эпсилон равным 0,001. Сравнение Epsilon также возможно с помощью побитового XOR. Поправьте меня, если я ошибаюсь.

Пожалуйста, не добавляйте один и тот же ответ на несколько вопросов. Ответьте на лучший, а остальные отметьте как дубликаты. См. meta.stackexchange.com/questions/104227/…

Bhargav Rao 14.11.2016 00:44

Я не думаю, что «эпсилон-сравнение» возможно с использованием только ExOr (и одного или двух сравнений), даже ограниченного нормализованными представлениями в том же формате.

greybeard 14.11.2016 03:09

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

Краткое резюме

  1. 1e-8 примерно то же самое, что 1e-16? Если вы смотрите на зашумленные данные датчика, то, вероятно, да, но если вы выполняете молекулярное моделирование, то, возможно, нет! Итог: вам всегда нужно думать о значении толерантность в контексте вызова конкретной функции, а не просто делать его общей жестко запрограммированной константой для всего приложения.
  2. Для общих библиотечных функций все еще хорошо иметь параметр с допуск по умолчанию. Типичный выбор - numeric_limits::epsilon(), который совпадает с FLT_EPSILON в float.h. Однако это проблематично, потому что эпсилон для сравнения значений типа 1.0 не то же самое, что эпсилон для значений типа 1Е9. FLT_EPSILON определен для 1.0.
  3. Очевидной реализацией для проверки того, находится ли число в пределах допуска, является fabs(a-b) <= epsilon, однако это не работает, потому что эпсилон по умолчанию определен для 1.0. Нам нужно масштабировать эпсилон вверх или вниз с точки зрения a и b.
  4. Есть два решения этой проблемы: либо вы устанавливаете эпсилон пропорционально max(a,b), либо вы можете получить следующие представимые числа вокруг a, а затем посмотреть, попадает ли b в этот диапазон. Первый называется «относительным» методом, а позже - методом ULP.
  5. Оба метода фактически не работают при сравнении с 0. В этом случае приложение должно предоставить правильный допуск.

Реализация служебных функций (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, который контролирует, должна ли приблизительная проверка равенства возвращать истину или нет.

Matt Chambers 10.04.2020 00:30

Для отношений меньше и больше, нам нужно использовать < и >.

Shital Shah 02.08.2020 08:48

Я использую этот код:

bool AlmostEqual(double v1, double v2)
    {
        return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
    }

epsilon не для этого.

Sneftel 26.05.2018 15:46

Почему нет? Вы можете это объяснить?

debuti 11.09.2018 16:32

@debuti epsilon - это просто расстояние между 1 и следующим представимым числом после 1. В лучшем случае этот код просто пытается проверить, равны ли два числа точно друг другу, но потому что не степени 2 умножаются на epsilon , он даже это не делает правильно.

Sneftel 13.06.2019 17:03

Да, и std::fabs(std::min(v1, v2)) неверен - для отрицательных входов выбирает тот, у которого большая величина.

Sneftel 13.06.2019 18:27

Я пишу это для 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));, хотя он обычно работает, на самом деле является неопределенным поведением.

Jaap Versteegh 12.11.2019 00:15

Справедливо, хотя этот код является иллюстративным, поэтому достаточно, чтобы продемонстрировать проблему для точки покрытия numeric_limits<>::epsilon и IEEE 754.

Steve Hollasch 13.11.2019 01:45

Также справедливо, но, imho, неразумно публиковать сообщения о переполнении стека, ожидая такого понимания. Код буду копируется вслепую, что еще больше затрудняет искоренение этого очень распространенного паттерна - вместе с уловкой объединения - которого следует просто избегать, как и следует всем UD.

Jaap Versteegh 18.11.2019 19:35

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); };

Это точно так же, как и многие другие ответы, за исключением того, что это лямбда и не имеет объяснения, поэтому это не добавляет большого значения в качестве ответа.

stijn 29.04.2020 15:10

Как насчет этого?

template<typename T>
bool FloatingPointEqual( T a, T b ) { return !(a < b) && !(b < a); }

Я видел разные подходы - но никогда не видел этого, поэтому мне тоже любопытно услышать какие-либо комментарии!

@Mehdi Я только что пробовал использовать repl.it/repls/SvelteSimpleNumerator#main.cpp, и, похоже, он ведет себя так, как ожидалось, но, возможно, у вас есть конкретная реализация компилятора, на которую можно ссылаться, которая этого не делает?

derke 11.06.2020 01:47

Это то же самое, что и A==B, только с большим количеством инструкций, и не решает проблему того, что это сравнение часто оказывается ложным просто из-за числовых ошибок, поэтому вы обычно используете что-то вроде std::abs(A-B) < epsilon. Он также вводит странное поведение, заключающееся в том, что любое действительное или недопустимое число с плавающей запятой, которое вы сравниваете со значением NaN, вернет истину.

Stefan Fabian 01.07.2020 21:53

Следующим способом вы сравниваете системно-зависимое «строковое представление» двух значений (в вашем случае с плавающей точкой). Аналогично тому, как вы распечатываете их оба и видите своими глазами, выглядят ли они одинаково:

#include <iostream>
#include <string>

bool floatApproximatelyEquals(const float a, const float b) {
    return std::to_string(a) == std::to_string(b);
}

Proc:

  • числовой коэффициент (или мощность) эффективно учитывается, поэтому не имеет значения, являются ли числа такими, как 1,2, или 1,2e345678, или 0,00000123, или 1,2e-345678 (проблема, с которой вы обычно сталкиваетесь с абсолютными эпсилонами)

Минусы:

  • вы не контролируете точность, до которой вы «округляете» числа. F.e. в моей системе это 6 цифр после первой значащей (ненулевой) единицы в десятичном представлении числа (что достаточно для большинства моих случаев)

Вы должны выполнить эту обработку для сравнения с плавающей запятой, так как 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;
}

Смотрите также:

  1. Макро-формы некоторых из вышеперечисленных функций в моем репо здесь: utilities.h.
    1. ОБНОВЛЕНИЕ 29 НОЯБРЯ 2020: это незавершенная работа, и я собираюсь дать ему отдельный ответ, когда будет готов, но я создал лучшую, масштабированную эпсилон-версию всех функций на C в этом файле здесь : utilities.c. Взглянуть.
  2. Дополнительное чтение, которое я сделал нужно сделать: Пересмотр допусков с плавающей запятой, Кристер Эриксон

Другие вопросы по теме