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


524

Какой самый эффективный способ сравнить два 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);
}

Кажется, переработка отходов.

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


2
> Было бы эффективнее добавить ... в начале функции? <invoke Knuth>Преждевременная оптимизация - корень всего зла. </invoke Knuth>Просто используйте abs (ab) <EPS, как отмечено выше, это понятно и легко для понимания.
Эндрю Колсон


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

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

33
«Просто делать это не правильно» - это просто ерунда, конечно, использование ==может быть совершенно правильным, но это полностью зависит от контекста, не приведенного в вопросе. Пока этот контекст не известен, по- ==прежнему остается «наиболее эффективным способом» .
Кристиан Рау

Ответы:


459

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

Я потратил много времени на отслеживание ошибок в системе, которая предположительно, 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 ++ == для двойных значений дает правильные результаты.)

Как я уже сказал: все зависит от контекста и ожидаемого размера aи b.

Кстати, std::numeric_limits<double>::epsilon()это «машина эпсилон». Это разница между 1.0 и следующим значением, представленным двойным. Я предполагаю, что это можно использовать в функции сравнения, но только если ожидаемые значения меньше 1. (Это в ответ на ответ @ cdv ...)

Кроме того, если у вас в основном есть intарифметика doubles(здесь мы используем double для хранения значений int в некоторых случаях), ваша арифметика будет правильной. Например, 4.0 / 2.0 будет таким же, как 1.0 + 1.0. Это до тех пор, пока вы не делаете вещи, которые приводят к дробным частям (4.0 / 3.0) или не выходят за пределы размера int.


10
+1 за указание на очевидное (что часто игнорируется). Для универсального метода вы можете сделать эпсилон относительно, fabs(a)+fabs(b)но с компенсацией NaN, 0 суммы и переполнения, это становится довольно сложным.
peterchen

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

3
Подожди секунду. Это то, что я сказал, что ты имел в виду? Вы говорите, почему |a-b|<epsilon, это не правильно. Пожалуйста, добавьте эту ссылку к вашему ответу; если вы согласны с cygnus-software.com/papers/comparingfloats/comparingfloats.htm и я могу удалить мои глупые комментарии.
бесхитростный шум

3
Это очень длинный комментарий, а не ответ сам по себе. Существует ли (набор) канонических ответов для всех контекстов?
Мерлин Морган-Грэм

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

174

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

Вы должны немного изменить свою реализацию:

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

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


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

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

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

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

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

115

Я обнаружил, что Google C ++ Testing Framework содержит хорошую кроссплатформенную основанную на шаблонах реализацию NearEqual2sComplement, которая работает как с двойными, так и с плавающими числами. Учитывая, что он выпущен по лицензии BSD, использование его в вашем собственном коде не должно быть проблемой, если вы сохраняете лицензию. Я извлек приведенный ниже код из http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h https://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: wan@google.com (Zhanyong Wan), eefacm@gmail.com (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, а не ту, которую я вставил здесь.


3
+1: я согласен, что это правильно. Однако это не объясняет почему. Смотрите здесь: cygnus-software.com/papers/comparingfloats/comparingfloats.htm Я читаю это сообщение в блоге после того, как я оставила здесь свой комментарий о высшем балле; Я считаю, что это говорит то же самое и обеспечивает рациональное решение, которое реализовано выше. Поскольку кода так много, люди будут пропускать ответ.
бесхитростный шум

Есть несколько неприятных вещей, которые могут произойти, когда происходит неявное приведение, например, FloatPoint <double> fp (0.03f). Я сделал несколько изменений, чтобы предотвратить это. template <typename U> явный FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "Вы делаете неявное преобразование с FloatingPoint, Не "<< std :: endl; assert (typeid (U) .name () == typeid (RawType) .name ()); } u_.value_ = x; }
JeffCharter

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

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

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

111

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

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

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

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.


1
спасибо за публикацию, как определить, какое число меньше / больше!
Помидор

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

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

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

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

47

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

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

14
Каково предлагаемое значение maxUlps?
unj2

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

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

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

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

27

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

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

25

Портативный способ получить эпсилон в 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();
}

34
Вам, скорее всего, понадобится кратность этого эпсилона.
user7116

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

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

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

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

18

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

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

  1. 1e-8 примерно такой же, как 1e-16? Если вы смотрите на данные датчика с шумом, то, вероятно, да, но если вы делаете молекулярное моделирование, то, возможно, нет! Итог: вам всегда нужно думать о значении толерантности в контексте конкретного вызова функции, а не просто делать его общей жестко закодированной константой приложения.
  2. Для общих библиотечных функций по-прежнему хорошо иметь параметр с допуском по умолчанию . Типичный выборnumeric_limits::epsilon() же, как FLT_EPSILON в float.h. Это, однако, проблематично, потому что epsilon для сравнения значений, таких как 1.0, не совпадает с epsilon для значений, таких как 1E9. FLT_EPSILON определен для 1.0.
  3. Очевидная реализация для проверки, находится ли число в пределах допуска, fabs(a-b) <= epsilonоднако это не работает, потому что epsilon по умолчанию определен для 1.0. Нам нужно масштабировать эпсилон вверх или вниз с точки зрения a и b.
  4. Есть два решения этой проблемы: либо вы устанавливаете epsilon пропорционально, 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). Разве не имеет смысла проверять diff> терпимость в обоих случаях? Или, возможно, добавить orEqualToаргумент, который контролирует, должна ли приблизительная проверка на равенство возвращать true или нет.
Мэтт Чемберс

14

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

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

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

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

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

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

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

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

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


13

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


3
Помимо опечаток (s / - /, / отсутствует запятая в fmax ()), в этой реализации есть ошибка для чисел, близких к нулю, которые находятся в EPSILON, но еще не совсем VERYSMALL. Например, AreSame (1.0E-10, 1.0E-9) сообщает false, потому что относительная ошибка огромна. Вы становитесь героем в своей компании.
Brlcad

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

8

Как отмечали другие, использование эпсилона с фиксированной экспонентой (например, 0,0000001) будет бесполезным для значений, отличных от значения эпсилона. Например, если ваши два значения 10000.000977 и 10000, тогда НЕТ между этими двумя числами 32-битных значений с плавающей точкой - 10000 и 10000.000977 настолько близки, насколько это возможно, не будучи бит-идентичным. Здесь эпсилон менее 0,0009 не имеет смысла; Вы также можете использовать оператор прямого равенства.

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

Таким образом, попытка смешать число с фиксированной запятой, например 0,00001, со значениями с плавающей запятой (где показатель степени произвольный) является бессмысленным упражнением. Это будет работать только в том случае, если вы можете быть уверены, что значения операндов находятся в узкой области (т. Е. Близко к некоторому конкретному показателю степени), и если вы правильно выберете значение эпсилона для этого конкретного теста. Если вы вытаскиваете число из воздуха («Эй! 0,00001 - это мало, значит, это должно быть хорошо!»), Вы обречены на ошибки в цифрах. Я потратил много времени на отладку плохого числового кода, где некоторые плохие чмо бросают в случайные значения эпсилон, чтобы заставить работать еще один тестовый пример.

Если вы занимаетесь каким-либо числовым программированием и считаете, что вам нужно получить эпсилоны с фиксированной запятой, ПРОЧИТАЙТЕ СТАТЬЮ БРЮСА ПО СРАВНЕНИЮ Плавающих чисел .

Сравнение чисел с плавающей точкой


5

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

И вам могут понадобиться следующие функции, так как

Обратите внимание, что сравнение значений, где p1 или p2 равно 0.0, не будет работать, равно как и сравнение значений, где одно из значений - NaN или бесконечность. Если одно из значений всегда 0,0, используйте взамен qFuzzyIsNull. Если одно из значений может составлять 0,0, одним из решений является добавление 1,0 к обоим значениям.

static inline bool qFuzzyIsNull(double d)
{
    return qAbs(d) <= 0.000000000001;
}

static inline bool qFuzzyIsNull(float f)
{
    return qAbs(f) <= 0.00001f;
}

3

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


3

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

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


2
«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, вы правы, в то время я думал об этом, и я истолковал этот вопрос в этом свете.
Дон Реба

2

Мой класс основан на ранее опубликованных ответах. Очень похоже на код 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 );
                  }
};

2

Вот доказательство того, что с помощью 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.
Стив Холлаш

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

1

Нашел еще одну интересную реализацию на: 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";
}

0

Я был бы очень осторожен с любым из этих ответов, который включает вычитание с плавающей запятой (например, fabs (ab) <epsilon). Во-первых, числа с плавающей запятой становятся более разреженными при больших величинах и при достаточно больших значениях, где расстояние больше, чем эпсилон, вы можете просто сделать a == b. Во-вторых, вычитание двух очень близких чисел с плавающей запятой (как это обычно бывает, учитывая, что вы ищете близкое равенство) - это именно то, как вы получаете катастрофическое аннулирование .

Несмотря на то, что ответ не переносимый, я думаю, что ответ Грома делает все возможное, чтобы избежать этих проблем.


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

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

0

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

https://stackoverflow.com/a/10973098/1447411

Таким образом, вы не можете сказать, что «CompareDoubles1» является неправильным в целом.


На самом деле это очень убедительная ссылка на хороший ответ, хотя он очень специализирован, чтобы ограничить кого-либо, не имеющего опыта в области компьютерных вычислений или численного анализа (т.е. LAPACK, BLAS), чтобы он не понимал полноту. Или, другими словами, предполагается, что вы читали что-то вроде введения Числовые рецепты или Численный анализ от Burden & Faires.
mctylr

0

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

diff= a - b; return fabs(diff)<EPSILON;

как fabsправило, довольно быстро. Под довольно быстрым я подразумеваю, что это в основном побитовое И, так что лучше быть быстрым.

Целочисленные трюки для сравнения значений типа double и float хороши, но, как правило, затрудняют эффективную обработку различными конвейерами ЦП. И в наши дни это определенно не быстрее на определенных архитектурах по порядку из-за использования стека в качестве временного хранилища для значений, которые часто используются. (Load-hit-store для тех, кому не все равно.)


0

По шкале количеств:

Если 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;
}

0

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

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

2
Это не то epsilon, для чего.
Sneftel

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

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

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

0

Я пишу это для Java, но, возможно, вы найдете это полезным. Он использует длинные вместо двойных, но заботится о 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; }

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


0

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

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

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


Это не работает для 1.99999999 и 1.99999998
Мехди

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

-1
/// 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, и на этот раз я получаю ожидаемый результат.


-1

Это еще одно решение с лямбда:

#include <cmath>
#include <limits>

auto Compare = [](float a, float b, float epsilon = std::numeric_limits<float>::epsilon()){ return (std::fabs(a - b) <= epsilon); };

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

-2

Мой путь может быть не правильным, но полезным

Конвертируйте оба числа с плавающей точкой в ​​строки и затем сравнивайте строки

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

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

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

-2

Вы не можете сравнить два 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;
}

-2

В более общем виде:

template <typename T>
bool compareNumber(const T& a, const T& b) {
    return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}

4
Этот метод имеет много недостатков, например, если числа aи bони уже меньше, чем epsilon()разница, они все равно могут быть значительными. И наоборот, если числа очень большие, то даже несколько бит ошибки приведут к сбою сравнения, даже если вы хотите, чтобы числа считались равными. Этот ответ - именно тот тип «универсального» алгоритма сравнения, который вы хотите избежать.
SirGuy

-3

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


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

Я не думаю, что «сравнение эпсилон» возможно с использованием только ExOr (и одного или двух сравнений), даже ограниченного нормализованными представлениями в том же формате.
Седобород
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.