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

423

Каким будет наиболее эффективный способ сравнить два значения 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
    Вот как это реализовано в Boost Test Library: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html
Показать ещё 5 комментариев
Теги:
optimization
floating-point
algorithm

27 ответов

373

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

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

  • Неявная презумпция в алгоритме, если a==b и b==c, затем a==c.

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

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

  • Использование такой функции сравнения для сортировки элементов в коллекции. (В этом случае использование встроенного С++-оператора == для двойников дает правильные результаты.)

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

BTW, 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.

  • 7
    +1 за указание на очевидное (что часто игнорируется). Для универсального метода вы можете сделать эпсилон относительно fabs(a)+fabs(b) но с компенсацией NaN, суммы 0 и переполнения, это становится довольно сложным.
  • 4
    Должно быть что-то, чего я не понимаю. Типичное число с float / double равно MANTISSA x 2 ^ EXP . epsilon будет зависеть от показателя. Например, если мантисса 24 бит, а показатель степени подписан 8 бит, то 1/(2^24)*2^127 или ~2^103 является epsilon для некоторых значений; или это относится к минимальному эпсилону ?
Показать ещё 7 комментариев
155

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

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

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

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

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

Я обнаружил, что Google C++ Testing Framework содержит хорошую кросс-платформенную реализацию на основе шаблонов AlmostEqual2sComplement, которая работает как с удвоением, так и с плавающей точкой. Учитывая, что он выпущен под лицензией 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 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 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 (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 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 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 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_;
};

EDIT: Это сообщение 4 года. Это, вероятно, все еще актуально, и код хорош, но некоторые люди нашли улучшения. Лучше всего получить последнюю версию AlmostEquals прямо из исходного кода Google Test, а не тот, который я вставил здесь.

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

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

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

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

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

Конечно, выбор epsilon зависит от контекста и определяет, насколько равны вы хотите, чтобы номера были.

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

  • 1
    спасибо за публикацию, как определить, какое число меньше / больше!
  • 0
    fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); спас мою жизнь LOL Обратите внимание, что эта версия (я не проверял, подходит ли она и для других) также учитывает изменения, которые могут произойти в неотъемлемой части числа с плавающей запятой (пример: 2147352577.9999997616 == 2147352576.0000000000 где вы можете четко видеть, что есть почти разница в 2 между двумя числами), что довольно приятно! Это происходит, когда накопленная ошибка округления переполняет десятичную часть числа.
Показать ещё 4 комментария
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;    
}
  • 11
    Каково предлагаемое значение maxUlps?
  • 4
    Будет ли " *(int*)&A; " нарушать строгое правило псевдонимов?
Показать ещё 5 комментариев
27

Портативный способ получить epsilon в С++ -

#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();
}
  • 1
    напишите std::fabs вместо fabs если хотите оставаться последовательным.
  • 27
    Вам, скорее всего, понадобится кратность этого эпсилона.
Показать ещё 7 комментариев
23

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

Мы можем найти несколько более практичную статью в пересмотренных допусках с плавающей точкой и отмечает, что существует абсолютный тест толерантности, который сводится к этому в С++:

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

`return fabs (a - b) < Эпсилон;

Это нормально, если:

  • порядок величин ваших входов существенно не изменяется.
  • очень малые числа противоположных знаков можно считать равными

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

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

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

Правильный код:

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

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

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

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

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

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

7

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

Краткая сводка

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

Реализация служебных функций (С++ 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;
}
7

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

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

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

Если вы выполняете численное программирование любого типа и считаете, что вам нужно достичь эпсилонов с фиксированной точкой, СТАТЬЯ READ BRUCE ПО СРАВНЕНИЮ НОМЕРОВ ПЛАВНОЙ ТОЧКИ.

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

3

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

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 );
                  }
};
  • 3
    Вы забыли добавить обширные юнит-тесты.
2

К сожалению, даже ваш "расточительный" код неверен. 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
  • 0
    Почему вы думаете, что EPSILON в этом вопросе DBL_EPSILON или FLT_EPSILON ? Проблема в вашем собственном воображении, где вы подставили DBL_EPSILON (что действительно было бы неправильным выбором) в код, который его не использовал.
Показать ещё 1 комментарий
1

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

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

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

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

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

0

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

0

Я пишу это для java, но, возможно, вы сочтете это полезным. Он использует longs вместо двухлокальных, но заботится о NaNs, subnormals и т.д.

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

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

bool AlmostEqual(double v1, double v2)
    {
        return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
    }
  • 1
    epsilon не для этого.
  • 1
    Почему бы и нет? Вы можете это объяснить?
0
/// 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, и на этот раз я получаю ожидаемый результат.

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

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

https://stackoverflow.com/questions/21265/comparing-ieee-floats-and-doubles-for-equality

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

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

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

Хотя он не переносимый, я думаю, что ответ grom лучше справляется с этими проблемами.

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

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

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() разница может быть существенной. И наоборот, если числа очень большие, то даже несколько бит ошибки приведут к сбою сравнения, даже если вы хотите, чтобы числа считались равными. Этот ответ является именно тем типом «универсального» алгоритма сравнения, который вы хотите избежать.
-1

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

Преобразуйте как float в строки, а затем выполните сравнение строк

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;

}
Оператор overlaoding также может быть выполнен
  • 0
    +1: эй, я не собираюсь заниматься игровым программированием с этим, но идея кругового плавания всплыла несколько раз в блоге Брюса Доусона (трактат?: D) по этому вопросу, и если вы попали в ловушку комната, и кто-то приставляет пистолет к вашей голове и говорит: «Эй, вы должны сравнить два числа с точностью до X значащих цифр, у вас есть 5 минут, ИДТИ!» это, вероятно, один, чтобы рассмотреть. ;)
  • 0
    @shelleybutterfly Опять же, вопрос заключался в наиболее эффективном способе сравнения двух чисел с плавающей запятой.
Показать ещё 1 комментарий
-2

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

  • 0
    Пожалуйста, не добавляйте один и тот же ответ на несколько вопросов. Ответьте лучшим и отметьте остальные как дубликаты. См. Meta.stackexchange.com/questions/104227/…
  • 0
    Я не думаю, что «сравнение эпсилон» возможно с использованием только ExOr (и одного или двух сравнений), даже ограниченного нормализованными представлениями в том же формате.
-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;
}
Сообщество Overcoder
Наверх
Меню