Що є найефективнішим способом порівняння з поплавком та подвійним порівнянням?


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
Ось такий спосіб реалізований у тестовій бібліотеці Boost: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comppare.html
Алессандро Джекопсон,

2
Єдине, що неоптимально стосується реалізації оригінального плаката, - це те, що він містить додаткову гілку на &&. Відповідь ОВ оптимальна. fabs - це невід'ємна інформація, яка є єдиною інструкцією на x87, і я думаю, що майже нічого іншого теж. Прийміть відповідь ОВ вже!
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.

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.


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

4
Має бути щось, чого я не розумію. Типовим float/ doubleє MANTISSA x 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/…
Marson Mao

174

Порівняння зі значенням epsilon - це те, що робить більшість людей (навіть у програмах ігор).

Слід трохи змінити реалізацію:

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

Редагувати: Крістер додав групу чудової інформації на цю тему в нещодавньому дописі в блозі . Насолоджуйтесь.


@OJ: чи щось не так з першим зразком коду? Я подумав, що єдиною проблемою була така ситуація: float a = 3.4; if(a == 3.4){...}тобто, коли ви порівнюєте збережену плаваючу крапку з буквальною | У цьому випадку обидва числа зберігаються, тому вони матимуть однакове представлення, якщо вони рівні, тож яка шкода при цьому a == b?
Лазер

11
@DonReba: Тільки якщо EPSILONвизначено як DBL_EPSILON. Зазвичай це буде конкретне значення, вибране залежно від необхідної точності порівняння.
Nemo157

7
EPSILONпорівняння не працює, коли поплавці великі, оскільки різниця між послідовними плавцями також стає великою. Дивіться цю статтю .
kevintodisco

22
Недарма в деяких іграх є Z-бої, коли текстури / об'єкти далеко мерехтять, як у Battlefield 4. Порівнювати різницю з EPSILONсуттєво марно. Порівнювати потрібно з порогом, який має сенс для одиниць, що знаходяться під рукою. Також використовуйте, std::absоскільки вона перевантажена для різних типів з плаваючою точкою.
Максим Єгорушкін

11
Я спровокував, оскільки приклад коду показує, що типова помилка wh повторюється більшістю програмістів. Плаваюча точка завжди стосується відносних помилок, оскільки це плаваюча точка (не фіксована точка). Таким чином, він ніколи не буде правильно працювати з виправленою помилкою (epsilon).
користувач2261015

115

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

EDIT: Цій посаді 4 роки. Це, мабуть, все ще дійсно, і код хороший, але деякі люди знайшли вдосконалення. Найкраще дістаньте останню версію AlmostEqualsправа прямо з вихідного коду Google Test, а не тієї, яку я вставив тут.


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

Є кілька неприємних речей, які можуть статися, коли трапляються неявні касти, скажімо, FloatPoint <double> fp (0,03f). Я вніс кілька змін до цього, щоб запобігти цьому. шаблон <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, однак, звідки цей код був викрадений. Я оновлю публікацію, щоб відобразити, що, ймовірно, є нова версія. Якщо хлопці з Google відчувають свербіж, чи можете ви поставити це, наприклад, у GitHub суть? Я тоді також посилатимусь на це.
skrebbel

3
Найновіший фрагмент коду дивіться тут і тут .
Яеге

1
Я витягнув потрібні рядки до файлу суті. Звідси може доїхати кожен .
Юсуф Тарик Гюнайдін

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

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

3
@kolistivra, ти помилився. 'F' у 'fabs' не означає float типу. Ви, мабуть, думаєте про функції C: fabsf () і fabsl ().
jcoffland

9
Насправді з причин, викладених у статті Брюса, епсилон змінюється у міру збільшення значення плаваючої точки. Дивіться частину, де він говорить: "Для чисел, більших за 2,0, розрив між плавцями збільшується, і якщо ви порівнюєте поплавці за допомогою FLT_EPSILON, то ви просто робите більш дорогу і менш очевидну перевірку рівності".
бобобобо

5
я знаю, що це старе, але std :: abs перевантажений для типів з плаваючою комою в сантиметрах.
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. Є два варіанти вирішення цієї проблеми: або ви встановите епсілон пропорційним max(a,b) або можете отримати наступні представні числа навколо а, а потім побачити, чи b потрапляє в цей діапазон. Перший називається "відносним" методом, а пізніше називається методом ULP.
  5. Обидва способи фактично не спрацьовують у порівнянні з 0. У цьому випадку програма повинна забезпечити правильну толерантність.

Впровадження функцій утиліти (C ++ 11)

//implements relative method - do not use for comparing with zero
//use this most of the time, tolerance needs to be meaningful in your context
template<typename TReal>
static bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = std::fabs(a - b);
    if (diff <= tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//supply tolerance that is meaningful in your context
//for example, default tolerance may not work if you are comparing double with float
template<typename TReal>
static bool isApproximatelyZero(TReal a, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    if (std::fabs(a) <= tolerance)
        return true;
    return false;
}


//use this when you want to be on safe side
//for example, don't start rover unless signal is above 1
template<typename TReal>
static bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff < tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}
template<typename TReal>
static bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff > tolerance)
        return true;

    if (diff > std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//implements ULP method
//use this when you are only concerned about floating point precision issue
//for example, if you want to see if a is 1.0 by checking if its within
//10 closest representable floating point numbers around 1.0.
template<typename TReal>
static bool isWithinPrecisionInterval(TReal a, TReal b, unsigned int interval_size = 1)
{
    TReal min_a = a - (a - std::nextafter(a, std::numeric_limits<TReal>::lowest())) * interval_size;
    TReal max_a = a + (std::nextafter(a, std::numeric_limits<TReal>::max()) - a) * interval_size;

    return min_a <= b && max_a >= b;
}

isDefinitelyLessThanчеки diff < tolerance, що означає, що a і b майже рівні (і тому a точно не менше b). Чи не має сенсу перевіряти різницю> толерантність в обох випадках? Або, можливо, додати orEqualToаргумент, який контролює, чи повинна приблизна перевірка рівності повертати істину чи ні.
Метт Чемберс

14

Код, який ви написали, помиляється:

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

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

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

(... і так, це різне)

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

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

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

Редагувати: ОВ, дякую за виправлення коду. Я відповідно стерв свій коментар


13

`return fabs (a - b) <EPSILON;

Це добре, якщо:

  • порядок величини ваших входів не сильно змінюється
  • дуже мала кількість протилежних знаків можна трактувати як рівну

Але в іншому випадку це призведе вас до неприємностей. Подвійні точні числа мають роздільну здатність близько 16 знаків після коми. Якщо два числа, які ви порівнюєте, за величиною більше, ніж EPSILON * 1.0E16, то ви можете також сказати:

return a==b;

Я вивчу інший підхід, який передбачає, що вам потрібно турбуватися про перший випуск, і припускаю, що другий - це добре ваша заявка. Рішення буде чимось на зразок:

#define VERYSMALL  (1.0E-150)
#define EPSILON    (1.0E-8)
bool AreSame(double a, double b)
{
    double absDiff = fabs(a - b);
    if (absDiff < VERYSMALL)
    {
        return true;
    }

    double maxAbs  = max(fabs(a) - fabs(b));
    return (absDiff/maxAbs) < EPSILON;
}

Це обчислювально дорого, але іноді те, що вимагається. Це те, що ми маємо робити в моїй компанії, оскільки ми маємо справу з інженерною бібліотекою, і вхід може змінюватися на кілька десятків порядків.

У будь-якому разі, справа в цьому (і стосується практично кожної проблеми програмування): оцініть, у чому полягають ваші потреби, а потім придумайте рішення для задоволення своїх потреб - не припускайте, що проста відповідь буде відповідати вашим потребам. Якщо після вашої оцінки ви виявите, що цього fabs(a-b) < EPSILONбуде достатньо, ідеально - використовуйте це! Але будьте в курсі його недоліків та інших можливих рішень.


3
Крім помилок друку (s / - /, / пропущена кома у fmax ()), ця реалізація має помилку для цифр, що знаходяться поблизу нуля, які знаходяться в межах EPSILON, але ще не зовсім ВЕРИЗМАЛЬНІ. Наприклад, AreSame (1.0E-10, 1.0E-9) повідомляє неправдиво, оскільки відносна помилка величезна. Ви станете героєм у вашій компанії.
brlcad

1
@brlcad Ви не отримали точки плаваючої точки. 1,0E-10 і 1,0E-9 відрізняються за величиною 10. Тож правда, що вони не однакові. плаваюча точка завжди стосується відносних помилок. Якщо у вас є система, яка вважає 1.0E-10 і 1.0E-9 майже рівними, оскільки обидві "досить близькі до нуля" (це здається людям розумним, але нічого математично), то EPSILON потрібно відрегулювати як слід для такої системи.
користувач2261015

8

Як зазначали інші, використання епсилону з фіксованим експонентом (наприклад, 0,0000001) буде марним для значень, віддалених від значення епсілона. Наприклад, якщо ваші два значення - 10000.000977 і 10000, то між цими двома номерами НІ 32-бітні значення з плаваючою комою - 10000 та 10000.000977 є настільки близькими, наскільки ви можете їх отримати, не будучи біт-біт-ідентичними. Тут епсилон менше 0,0009 є безглуздим; Ви також можете скористатися оператором прямої рівності.

Так само, як два значення наближаються до епсилону за розміром, відносна похибка зростає до 100%.

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

Якщо ви займаєтеся чисельним програмуванням будь-якого виду і вважаєте, що вам потрібно дотягнутися до епілонів з фіксованою точкою, ЧИТАЙТЕ СТАТТЮ БРУКСА НА ПОРІВНЯННІ ЧИСЛА З ПЛЮСЬКИМИ ПОКАЗАМИ .

Порівняння чисел з плаваючою точкою


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

Порівняння загального призначення чисел з плаваючою комою взагалі безглуздо. Як порівняти насправді, залежить від проблеми. У багатьох проблемах цифри є достатньо дискретні, щоб можна було порівнювати їх у межах заданої допуску. На жаль, існує стільки ж проблем, де такий трюк насправді не працює. Для прикладу розгляньте роботу з функцією Heaviside (step) відповідного числа (цифрові параметри запасів приходять у голову), коли ваші спостереження дуже близькі до бар'єру. Проведення порівняння на основі толерантності не принесло б великої користі, оскільки воно фактично змістить проблему з початкового бар'єру на два нові. Знову ж таки, загальних цільових рішень для подібних проблем не існує, і конкретне рішення може зажадати змінити числовий метод для досягнення стабільності.


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));що, як правило, це працює, насправді є невизначеною поведінкою.
Jaap Versteegh

Справедливий момент, хоча цей код є ілюстративним, настільки достатнім, щоб продемонструвати питання щодо numeric_limits<>::epsilonточки підлоги IEEE 754.
Стів Холлаш

Також справедливий момент, але не розумно імхо розміщувати переповнення стека, очікуючи такого виду. Код буде сліпо скопійовано, що ускладнить викорінення цього дуже поширеного шаблону - разом із фокусом об'єднання - якого слід уникати, як і всі UD.
Jaap Versteegh

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). По-перше, числа з плаваючою точкою стають більш рідкими при більших величинах і при досить великих величинах, де відстань більша, ніж епсілон, ви також можете просто робити = = b. По-друге, віднімання двох дуже близьких чисел з плаваючою комою (як це буде, як правило, з огляду на те, що ви шукаєте майже рівність) - саме так ви отримуєте катастрофічне скасування .

Хоча не портативний, я вважаю, що відповідь Grom найкраще справляється з уникненням цих питань.


1
+1 за гарну інформацію. Однак я не бачу, як ви могли зіпсувати порівняння рівності, збільшивши відносну помилку; IMHO помилка стає суттєвою лише в результаті віднімання, однак її порядок величини відносно віднімання двох операндів все одно повинен бути досить надійним, щоб оцінити рівність. Якщо роздільна здатність не повинна бути вищою в цілому, але в цьому випадку єдиним рішенням є перехід до зображення з плаваючою комою з більш значущими бітами в мантісі.
вересень

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

0

Насправді є випадки чисельного програмного забезпечення, коли ви хочете перевірити, чи точно два числа з плаваючою точкою рівні. Я розмістив це на подібному питанні

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

Тож ви не можете сказати, що "ПорівнятиДублі1" взагалі неправильно.


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

0

Це залежить від того, наскільки точно ви хочете зробити порівняння. Якщо ви хочете порівняти точно таке ж число, то просто перейдіть з ==. (Ви майже ніколи не хочете цього робити, якщо ви насправді не хочете точно такого ж числа.) На будь-якій гідній платформі ви також можете зробити наступне:

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

як fabsправило, досить швидко. Досить швидким я маю на увазі, що це в основному побіжно І, тому краще бути швидким.

І цілі трюки для порівняння парних і поплавців є приємними, але, як правило, ускладнюють ефективні роботи різних конвеєрів процесора. І це, безумовно, не швидше в певних замовлених архітектурах в наші дні через використання стека як тимчасової області зберігання значень, які часто використовуються. (Завантажте магазин для тих, хто небайдужий.)


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, але, можливо, ви вважаєте це корисним. Він використовує longs замість подвійних, але піклується про NaNs, піднормах тощо.

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
Мехді

-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

Мій шлях може бути не правильним, але корисним

Перетворити обидва 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;

}

перекриття оператора також може бути виконано


+1: ей, я не збираюся цим займатися ігровим програмуванням, але ідея круглих поплавців з'явилася кілька разів у блозі Брюса Доусона (трактат?: D) з цього питання, і якщо ви потрапили в пастку. кімната, а хтось кладе на голову пістолет і каже: "Ей, ти повинен порівняти два поплавця з X значущими цифрами, у тебе є 5 хвилин, ПЕРЕХОД!" це, мабуть, варто врахувати. ;)
shelleybutterfly

@shelleybutterfly Потім знову було питання про найефективніший спосіб порівняння двох чисел з плаваючою комою.
Томмі Андерсен

@TommyA lol, можливо, але я ставлю на облік, що круглі відключення були зняті з причин, не пов'язаних з ефективністю. Хоча моя інтуїція полягає в тому, що це було б досить неефективно в порівнянні з математикою HW fp, але також говорить, що алгоритми програмного забезпечення fp навряд чи матимуть різницю bigO принаймні. Я з нетерпінням чекаю, що аналіз, який ви зробили, показує, що питання щодо ефективності в цьому випадку є важливими. Крім того, іноді менш оптимальне може все-таки бути цінною відповіддю, і як це було оскаржено - незважаючи на те, що це дійсна техніка, про яку навіть згадував блог Доусона на цю тему, тож я вважав, що це заслуговує на надбавку.
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? Два числа з плаваючою комою рівні, якщо їх відповідні біти рівні. Я думаю, що рішення про розміщення бітів-експонентів до мантіси було прийнято для прискорення порівняння двох поплавків. Думаю, багато відповідей тут не вистачає точки порівняння епсілону. Значення Epsilon залежить лише від того, з якою точністю порівнюються числа з плаваючою комою. Наприклад, виконавши деяку арифметику з поплавками, ви отримаєте два числа: 2.5642943554342 та 2.5642943554345. Вони не рівні, але для рішення мають значення лише 3 десяткових цифри, тож вони рівні: 2,564 та 2,564. У цьому випадку ви вибираєте епсилон, рівний 0,001. Порівняння епсилону також можливе з бітовим XOR. Виправте мене, якщо я помиляюся.


Не додайте однакову відповідь на кілька запитань. Дайте відповідь найкращому, а решту позначте як дублікати. Дивіться meta.stackexchange.com/questions/104227/…
Bhargav Rao

Я не думаю, що "порівняння epsilon" можливе за допомогою лише ExOr (та одного чи двох порівнянь), навіть обмежених нормалізованими поданнями в тому ж форматі.
сіра борода
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.