วิธีที่มีประสิทธิภาพมากที่สุดสำหรับการเปรียบเทียบแบบลอยและแบบคู่คืออะไร


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 ตามที่ระบุไว้ข้างต้นมันชัดเจนและเข้าใจง่าย
Andrew Coleson

2
นี่คือวิธีการใช้งานใน Boost Test Library: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html
Alessandro Jacopson

2
สิ่งเดียวที่ไม่ดีเกี่ยวกับการนำไปใช้ของโปสเตอร์ต้นฉบับคือมีสาขาพิเศษที่ && คำตอบของ OJ นั้นเหมาะสมที่สุด fabs คือเนื้อแท้ซึ่งเป็นคำสั่งเดียวใน x87 และฉันก็คิดว่าเกือบทุกอย่างจะเหมือนกัน ยอมรับคำตอบของ OJ แล้ว!
3yE

3
หากทำได้ให้ดรอปจุดลอยตัวและใช้คะแนนคงที่ ตัวอย่างเช่นใช้ {fixed point} มิลลิเมตรแทน {floating point} เมตร
โทมัสแมตทิวส์

33
"การทำแบบนี้ไม่ถูกต้อง" - นี่เป็นเพียงขยะมูลฝอยแน่นอนว่าการใช้==สามารถแก้ไขได้อย่างสมบูรณ์แบบ แต่สิ่งนี้ขึ้นอยู่กับบริบทที่ไม่ได้ให้ไว้ จนกระทั่งบริบทที่เป็นที่รู้จักกัน==ยังคงที่ยังใช้"วิธีที่มีประสิทธิภาพมากที่สุด"
Christian Rau

คำตอบ:


459

ระมัดระวังเป็นอย่างยิ่งโดยใช้คำแนะนำอื่น ๆ ทุกอย่างขึ้นอยู่กับบริบท

ฉันได้ใช้เวลานานในการติดตามข้อบกพร่องในระบบที่สันนิษฐานถ้าa==b |a-b|<epsilonปัญหาพื้นฐานคือ:

  1. สันนิษฐานนัยในขั้นตอนวิธีว่าถ้าa==bและแล้วb==ca==c

  2. การใช้ epsilon เดียวกันสำหรับเส้นที่วัดเป็นนิ้วและเส้นที่วัดเป็นไมล์ (.001 นิ้ว) นั่นคือแต่a==b 1000a!=1000b(นี่คือเหตุผลที่ BeforeEqual2sComplement ถาม epsilon หรือ ULPS สูงสุด)

  3. การใช้ epsilon เดียวกันสำหรับทั้งมุมโคไซน์และความยาวของเส้น!

  4. ใช้ฟังก์ชั่นเปรียบเทียบเพื่อเรียงลำดับรายการในคอลเลกชัน (ในกรณีนี้ใช้ตัวดำเนินการ C ++ ในตัว == สำหรับคู่ที่สร้างผลลัพธ์ที่ถูกต้อง)

เหมือนที่ผมกล่าวว่าทั้งหมดขึ้นอยู่กับบริบทและขนาดที่คาดหวังของและab

BTW std::numeric_limits<double>::epsilon()คือ "machine epsilon" มันคือความแตกต่างระหว่าง 1.0 และค่าถัดไปที่สามารถแทนได้ด้วย double ฉันเดาว่ามันสามารถใช้ในฟังก์ชั่นการเปรียบเทียบ แต่ถ้าค่าที่คาดหวังน้อยกว่า 1 (นี่คือการตอบสนองต่อคำตอบของ @ cdv ... )

นอกจากนี้หากคุณมีintเลขคณิตเป็นหลักdoubles(ที่นี่เราใช้คู่ผสมเพื่อเก็บค่า int ในบางกรณี) เลขคณิตของคุณจะถูกต้อง ตัวอย่างเช่น 4.0 / 2.0 จะเหมือนกับ 1.0 + 1.0 นี่คือตราบใดที่คุณไม่ทำสิ่งที่ทำให้เศษส่วน (4.0 / 3.0) หรือไม่ออกไปนอกขนาดของ int


10
+1 สำหรับการชี้ให้เห็นอย่างชัดเจน (ซึ่งมักถูกมองข้าม) สำหรับวิธีทั่วไปคุณสามารถทำให้ epsilon สัมพันธ์กับfabs(a)+fabs(b)แต่ด้วยการชดเชย NaN, 0 sum และ overflow สิ่งนี้ค่อนข้างซับซ้อน
peterchen

4
ต้องมีบางอย่างที่ฉันไม่เข้าใจ ทั่วไปfloat/ doubleเป็นMantissa x 2 ^ EXP epsilonจะขึ้นอยู่กับเลขชี้กำลัง ตัวอย่างเช่นหากmantissaเป็น 24 บิตและเลขชี้กำลังมีการลงนาม 8 บิตดังนั้น1/(2^24)*2^127หรือ~2^103เป็นepsilonค่าบางค่า หรือนี่หมายถึงเอปไซลอนขั้นต่ำ?
เสียงอึกทึกครึกครื้น

3
รอสักครู่. ฉันพูดในสิ่งที่คุณหมายถึงอะไร คุณจะบอกว่าทำไม|a-b|<epsilonเป็นไม่ถูกต้อง กรุณาเพิ่มลิงค์นี้ไปยังคำตอบของคุณ; หากคุณเห็นด้วยcygnus-software.com/papers/comparingfloats/comparingfloats.htmและฉันสามารถลบความคิดเห็นที่เป็นใบ้ได้
เสียงอึกทึกครึกครื้น

3
นี่เป็นความคิดเห็นที่ยาวมากไม่ใช่คำตอบในตัวมันเอง มีคำตอบที่ยอมรับ (ชุด) สำหรับบริบททั้งหมดหรือไม่
Merlyn Morgan-Graham

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คืออะไร?
Lazer

11
@DonReba: เฉพาะในกรณีที่ถูกกำหนดให้เป็นEPSILON DBL_EPSILONโดยปกติจะเป็นค่าเฉพาะที่เลือกขึ้นอยู่กับความถูกต้องที่ต้องการของการเปรียบเทียบ
Nemo157

7
EPSILONการเปรียบเทียบไม่ทำงานเมื่อลอยมีขนาดใหญ่เนื่องจากความแตกต่างระหว่างลอยต่อเนื่องกันจะมีขนาดใหญ่ ดูบทความนี้
kevintodisco

22
ไม่น่าแปลกใจที่มีการต่อสู้กับ Z ในบางเกมเมื่อพื้นผิว / วัตถุที่อยู่ห่างไกลสั่นไหวเช่นเดียวกับใน Battlefield 4 การเปรียบเทียบความแตกต่างกับEPSILONมันไร้ประโยชน์มาก คุณต้องเปรียบเทียบกับเกณฑ์ที่เหมาะสมสำหรับหน่วยในมือ นอกจากนี้ยังใช้std::absเนื่องจากมีการโอเวอร์โหลดสำหรับประเภทจุดลอยตัวที่แตกต่างกัน
Maxim Egorushkin

11
ฉันลงเนื่องจากโค้ดตัวอย่างแสดงข้อผิดพลาดทั่วไปที่โปรแกรมเมอร์ส่วนใหญ่ทำซ้ำ จุดลอยตัวมักเกี่ยวกับข้อผิดพลาดที่สัมพันธ์กันเนื่องจากเป็นจุดลอยตัว (ไม่ใช่จุดคงที่) ดังนั้นจะไม่ทำงานอย่างถูกต้องกับข้อผิดพลาดคงที่ (epsilon)
2261015

115

ฉันพบว่ากรอบการทดสอบของ Google C ++มีการใช้เทมเพลตข้ามแพลตฟอร์มที่ดีของการใช้งานของ เนื่องจากมีการเผยแพร่ภายใต้ใบอนุญาต 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 เป็นค่าบางส่วน (หรือเพื่อเปลี่ยนรหัสที่ใช้กับบางสิ่งที่เหมาะกับ codebase ของคุณ - เป็นสิทธิ์การใช้งาน BSD หลังจากนั้นทั้งหมด)

ตัวอย่างการใช้งาน:

double left  = // something
double right = // something
const FloatingPoint<double> lhs(left), rhs(right);

if (lhs.AlmostEquals(rhs)) {
  //they're equal!
}

นี่คือรหัส:

// Copyright 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee)
//
// The Google C++ Testing Framework (Google Test)


// This template class serves as a compile-time function from size to
// type.  It maps a size in bytes to a primitive type with that
// size. e.g.
//
//   TypeWithSize<4>::UInt
//
// is typedef-ed to be unsigned int (unsigned integer made up of 4
// bytes).
//
// Such functionality should belong to STL, but I cannot find it
// there.
//
// Google Test uses this class in the implementation of floating-point
// comparison.
//
// For now it only handles UInt (unsigned int) as that's all Google Test
// needs.  Other types can be easily added in the future if need
// arises.
template <size_t size>
class TypeWithSize {
 public:
  // This prevents the user from using TypeWithSize<N> with incorrect
  // values of N.
  typedef void UInt;
};

// The specialization for size 4.
template <>
class TypeWithSize<4> {
 public:
  // unsigned int has size 4 in both gcc and MSVC.
  //
  // As base/basictypes.h doesn't compile on Windows, we cannot use
  // uint32, uint64, and etc here.
  typedef int Int;
  typedef unsigned int UInt;
};

// The specialization for size 8.
template <>
class TypeWithSize<8> {
 public:
#if GTEST_OS_WINDOWS
  typedef __int64 Int;
  typedef unsigned __int64 UInt;
#else
  typedef long long Int;  // NOLINT
  typedef unsigned long long UInt;  // NOLINT
#endif  // GTEST_OS_WINDOWS
};


// This template class represents an IEEE floating-point number
// (either single-precision or double-precision, depending on the
// template parameters).
//
// The purpose of this class is to do more sophisticated number
// comparison.  (Due to round-off error, etc, it's very unlikely that
// two floating-points will be equal exactly.  Hence a naive
// comparison by the == operation often doesn't work.)
//
// Format of IEEE floating-point:
//
//   The most-significant bit being the leftmost, an IEEE
//   floating-point looks like
//
//     sign_bit exponent_bits fraction_bits
//
//   Here, sign_bit is a single bit that designates the sign of the
//   number.
//
//   For float, there are 8 exponent bits and 23 fraction bits.
//
//   For double, there are 11 exponent bits and 52 fraction bits.
//
//   More details can be found at
//   http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
//
// Template parameter:
//
//   RawType: the raw floating-point type (either float or double)
template <typename RawType>
class FloatingPoint {
 public:
  // Defines the unsigned integer type that has the same size as the
  // floating point number.
  typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;

  // Constants.

  // # of bits in a number.
  static const size_t kBitCount = 8*sizeof(RawType);

  // # of fraction bits in a number.
  static const size_t kFractionBitCount =
    std::numeric_limits<RawType>::digits - 1;

  // # of exponent bits in a number.
  static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;

  // The mask for the sign bit.
  static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);

  // The mask for the fraction bits.
  static const Bits kFractionBitMask =
    ~static_cast<Bits>(0) >> (kExponentBitCount + 1);

  // The mask for the exponent bits.
  static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);

  // How many ULP's (Units in the Last Place) we want to tolerate when
  // comparing two numbers.  The larger the value, the more error we
  // allow.  A 0 value means that two numbers must be exactly the same
  // to be considered equal.
  //
  // The maximum error of a single floating-point operation is 0.5
  // units in the last place.  On Intel CPU's, all floating-point
  // calculations are done with 80-bit precision, while double has 64
  // bits.  Therefore, 4 should be enough for ordinary use.
  //
  // See the following article for more details on ULP:
  // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm.
  static const size_t kMaxUlps = 4;

  // Constructs a FloatingPoint from a raw floating-point number.
  //
  // On an Intel CPU, passing a non-normalized NAN (Not a Number)
  // around may change its bits, although the new value is guaranteed
  // to be also a NAN.  Therefore, don't expect this constructor to
  // preserve the bits in x when x is a NAN.
  explicit FloatingPoint(const RawType& x) { u_.value_ = x; }

  // Static methods

  // Reinterprets a bit pattern as a floating-point number.
  //
  // This function is needed to test the AlmostEquals() method.
  static RawType ReinterpretBits(const Bits bits) {
    FloatingPoint fp(0);
    fp.u_.bits_ = bits;
    return fp.u_.value_;
  }

  // Returns the floating-point number that represent positive infinity.
  static RawType Infinity() {
    return ReinterpretBits(kExponentBitMask);
  }

  // Non-static methods

  // Returns the bits that represents this number.
  const Bits &bits() const { return u_.bits_; }

  // Returns the exponent bits of this number.
  Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }

  // Returns the fraction bits of this number.
  Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }

  // Returns the sign bit of this number.
  Bits sign_bit() const { return kSignBitMask & u_.bits_; }

  // Returns true iff this is NAN (not a number).
  bool is_nan() const {
    // It's a NAN if the exponent bits are all ones and the fraction
    // bits are not entirely zeros.
    return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
  }

  // Returns true iff this number is at most kMaxUlps ULP's away from
  // rhs.  In particular, this function:
  //
  //   - returns false if either number is (or both are) NAN.
  //   - treats really large numbers as almost equal to infinity.
  //   - thinks +0.0 and -0.0 are 0 DLP's apart.
  bool AlmostEquals(const FloatingPoint& rhs) const {
    // The IEEE standard says that any comparison operation involving
    // a NAN must return false.
    if (is_nan() || rhs.is_nan()) return false;

    return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
        <= kMaxUlps;
  }

 private:
  // The data type used to store the actual floating-point number.
  union FloatingPointUnion {
    RawType value_;  // The raw floating-point number.
    Bits bits_;      // The bits that represent the number.
  };

  // Converts an integer from the sign-and-magnitude representation to
  // the biased representation.  More precisely, let N be 2 to the
  // power of (kBitCount - 1), an integer x is represented by the
  // unsigned number x + N.
  //
  // For instance,
  //
  //   -N + 1 (the most negative number representable using
  //          sign-and-magnitude) is represented by 1;
  //   0      is represented by N; and
  //   N - 1  (the biggest number representable using
  //          sign-and-magnitude) is represented by 2N - 1.
  //
  // Read http://en.wikipedia.org/wiki/Signed_number_representations
  // for more details on signed number representations.
  static Bits SignAndMagnitudeToBiased(const Bits &sam) {
    if (kSignBitMask & sam) {
      // sam represents a negative number.
      return ~sam + 1;
    } else {
      // sam represents a positive number.
      return kSignBitMask | sam;
    }
  }

  // Given two numbers in the sign-and-magnitude representation,
  // returns the distance between them as an unsigned number.
  static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
                                                     const Bits &sam2) {
    const Bits biased1 = SignAndMagnitudeToBiased(sam1);
    const Bits biased2 = SignAndMagnitudeToBiased(sam2);
    return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
  }

  FloatingPointUnion u_;
};

แก้ไข: โพสต์นี้มีอายุ 4 ปี มันอาจยังใช้ได้อยู่และรหัสก็ดี แต่บางคนพบว่ามีการปรับปรุง ดีที่สุดไปเลยรับเวอร์ชั่นล่าสุดของAlmostEqualsสิทธิ์จากซอร์สโค้ดของ Google Test ไม่ใช่ที่ฉันวางที่นี่


3
+1: ฉันยอมรับว่าสิ่งนี้ถูกต้อง อย่างไรก็ตามมันไม่ได้อธิบายว่าทำไม ดูที่นี่: cygnus-software.com/papers/comparingfloats/comparingfloats.htm ฉันอ่านโพสต์บล็อกนี้หลังจากที่ฉันเขียนความคิดเห็นของฉันในคะแนนสูงสุดที่นี่; ฉันเชื่อว่ามันพูดในสิ่งเดียวกันและให้เหตุผล / วิธีแก้ปัญหาที่นำมาใช้ด้านบน เนื่องจากมีรหัสมากผู้คนจะพลาดคำตอบ
เสียงอึกทึกครึกครื้น

มีสิ่งที่น่ารังเกียจสองอย่างที่สามารถเกิดขึ้นได้เมื่อมีการปลดเปลื้องโดยปริยายโดยการพูด FloatPoint <double> fp (0.03f) ฉันทำการแก้ไขสองสามสิ่งนี้เพื่อช่วยป้องกันสิ่งนั้น แม่แบบ <typename U> อย่างชัดเจน FloatingPoint (const U & x) {ถ้า (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "คุณกำลังทำการแปลงโดยนัยด้วย FloatingPoint ไม่ "<< std :: endl; ยืนยัน (typeid (U) .name () == typeid (RawType) .name ()); } u_.value_ = x; }
JeffCharter

2
หาดี! ฉันเดาว่ามันจะเป็นการดีที่สุดที่จะร่วมให้ข้อมูลกับ Google Test ซึ่งรหัสนี้ถูกขโมยไป ฉันจะอัปเดตโพสต์เพื่อแสดงว่ามีเวอร์ชันใหม่กว่า ถ้าพวก Google แสดงอาการคันคุณสามารถเอามันไปใส่ใน GitHub gist ได้ไหม? ฉันจะเชื่อมโยงกับสิ่งนั้นเช่นกัน
skrebbel

3
สำหรับข้อมูลโค้ดใหม่ล่าสุดดูที่นี่และที่นี่
Jaege

1
ฉันได้แยกบรรทัดที่จำเป็นไปยังไฟล์ส่วนสำคัญ ทุกคนสามารถเข้าถึงได้จากที่นี่
Yusuf TarıkGünaydın

111

การเปรียบเทียบตัวเลขจุดลอยตัวสำหรับขึ้นอยู่กับบริบท เนื่องจากแม้การเปลี่ยนลำดับการดำเนินการสามารถให้ผลลัพธ์ที่แตกต่างกันได้มันเป็นสิ่งสำคัญที่จะต้องรู้ว่า "เท่ากัน" คุณต้องการให้ตัวเลขเป็นอย่างไร

การเปรียบเทียบตัวเลขจุดลอยตัวโดย Bruce Dawson เป็นจุดเริ่มต้นที่ดีเมื่อดูการเปรียบเทียบจุดลอย

คำจำกัดความต่อไปนี้มาจากศิลปะการเขียนโปรแกรมคอมพิวเตอร์โดย Knuth :

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

บทความที่ดีและเป็นประโยชน์โดย Bruce Dawson ขอบคุณ!
BobMorane

2
เนื่องจากคำถามนี้ถูกแท็ก C ++ เช็คของคุณจะง่ายต่อการอ่านที่เขียนเป็นstd::max(std::abs(a), std::abs(b))(หรือมีstd::min()) std::absใน C ++ นั้นเต็มไปด้วย float & double type ดังนั้นมันจึงใช้ได้ดี (คุณสามารถfabsอ่านได้เสมอ)
Razakhel

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 เป็นหมายเลขที่ยอมรับได้
อาจเกิด

4
และนี่คือการอัปเดตสองถึงกระดาษของ Bruce Dawson (หนึ่งในนั้นเชื่อมโยงในบทนำของกระดาษ): randomascii.wordpress.com/2012/02/25//และrandomascii.wordpress.com/2012/06/26/ ......
Michael Burr

3
ฉันใช้เวลา
พอสมควรที่

27

การตระหนักถึงสิ่งนี้เป็นหัวข้อเก่า แต่บทความนี้เป็นหนึ่งในสิ่งที่ตรงไปตรงมาที่สุดที่ฉันได้พบในการเปรียบเทียบจำนวนจุดลอยตัวและถ้าคุณต้องการสำรวจมากขึ้นก็มีการอ้างอิงรายละเอียดเพิ่มเติมเช่นกันและเว็บไซต์หลักครอบคลุมประเด็นที่ครบถ้วน การจัดการกับตัวเลขทศนิยมลอยจุด Guide:

เราสามารถหาบทความเชิงปฏิบัติที่ค่อนข้างใช้งานได้มากขึ้นในการตรวจสอบจุดผ่อนปรนจุดลอยตัวและบันทึกที่มีการทดสอบความอดทนแบบสัมบูรณ์ซึ่งจะลดลงไปถึงสิ่งนี้ใน 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

วิธีการพกพาเพื่อรับ epsilon ใน C ++ คือ

#include <limits>
std::numeric_limits<double>::epsilon()

จากนั้นฟังก์ชั่นการเปรียบเทียบจะกลายเป็น

#include <cmath>
#include <limits>

bool AreSame(double a, double b) {
    return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}

34
คุณจะต้องการ epsilon หลายตัวที่เป็นไปได้มากที่สุด
user7116

11
คุณใช้ std :: abs ไม่ได้เหรอ? AFAIK, std :: abs มีการโหลดมากเกินไปสำหรับคู่เช่นกัน โปรดเตือนฉันถ้าฉันผิด
kolistivra

3
@kolistivra คุณผิด 'f' ใน 'fabs' ไม่ได้หมายถึงประเภทลอย คุณอาจนึกถึงฟังก์ชั่น C fabsf () และ fabsl ()
jcoffland

9
ที่จริงแล้วด้วยเหตุผลที่อธิบายไว้ในบทความ epsilon ของ Bruce เปลี่ยนแปลงเมื่อค่าจุดลอยตัวใหญ่ขึ้น ดูส่วนที่เขาพูดว่า"สำหรับตัวเลขที่มากกว่า 2.0 ช่องว่างระหว่างการลอยตัวจะใหญ่ขึ้นและถ้าคุณเปรียบเทียบการลอยตัวโดยใช้ FLT_EPSILON คุณต้องทำการตรวจสอบความเท่าเทียมกันที่มีราคาแพงและไม่ชัดเจน"
bobobobo

5
ฉันรู้ว่านี่เก่า แต่ std :: abs มากเกินไปสำหรับประเภทจุดลอยใน cmath
mholzmann

18

ฉันใช้เวลาค่อนข้างมากในการอ่านเนื้อหาในหัวข้อที่ดีนี้ ฉันสงสัยว่าทุกคนต้องการที่จะใช้เวลามากดังนั้นฉันจะเน้นที่สรุปของสิ่งที่ฉันเรียนรู้และวิธีแก้ปัญหาที่ฉันใช้

สรุปด่วน

  1. 1e-8 ประมาณเหมือนกับ 1e-16 หรือไม่ หากคุณกำลังมองหาข้อมูลเซ็นเซอร์ที่มีเสียงดังอาจเป็นไปได้ แต่ถ้าคุณกำลังทำการจำลองระดับโมเลกุลอาจไม่ใช่! Bottom line: คุณจำเป็นต้องนึกถึงค่าความอดทนในบริบทของการเรียกใช้ฟังก์ชั่นที่เฉพาะเจาะจงและไม่เพียง แต่ทำให้ค่าคงที่รหัสตายตัวทั้งแอปทั่วไป
  2. สำหรับการทำงานห้องสมุดทั่วไปก็ยังคงมีความสุขที่จะมีพารามิเตอร์ที่มีความอดทนเริ่มต้น ตัวเลือกทั่วไปคือnumeric_limits::epsilon()ซึ่งเหมือนกับ FLT_EPSILON ใน float.h อย่างไรก็ตามนี่เป็นปัญหาเนื่องจาก epsilon สำหรับการเปรียบเทียบค่าเช่น 1.0 ไม่เหมือนกับ epsilon สำหรับค่าเช่น 1E9 FLT_EPSILON ถูกกำหนดไว้สำหรับ 1.0
  3. การนำไปใช้ที่ชัดเจนเพื่อตรวจสอบว่าหมายเลขนั้นอยู่ในเกณฑ์ที่ยอมรับได้หรือไม่fabs(a-b) <= epsilonอย่างไรก็ตามนี่ไม่ได้ผลเนื่องจาก epsilon เริ่มต้นถูกกำหนดไว้ที่ 1.0 เราจำเป็นต้องปรับขนาดเอปไซลอนขึ้นหรือลงในแง่ของ a และ b
  4. มีวิธีแก้ไขปัญหานี้สองวิธี: คุณตั้งค่า epsilon ตามสัดส่วนmax(a,b)หรือคุณสามารถหาตัวเลขที่แทนได้ถัดไป a แล้วดูว่า b อยู่ในช่วงนั้นหรือไม่ อดีตเรียกว่า "ญาติ" วิธีการและภายหลังเรียกว่าวิธี ULP
  5. ทั้งสองวิธีล้มเหลวจริง ๆ แล้วเมื่อเปรียบเทียบกับ 0 ในกรณีนี้แอปพลิเคชันต้องให้ความอดทนที่ถูกต้อง

การใช้งานฟังก์ชั่นยูทิลิตี้ (C ++ 11)

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

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

    return false;
}

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


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

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

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

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

    return false;
}

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

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

isDefinitelyLessThanเช็คdiff < toleranceซึ่งหมายความว่า a และ b เกือบเท่ากัน (และดังนั้น a ไม่น้อยกว่า b) การตรวจสอบความแตกต่างของความอดทนในทั้งสองกรณีนั้นสมเหตุสมผลหรือไม่ หรืออาจเพิ่มorEqualToอาร์กิวเมนต์ที่ควบคุมว่าการตรวจสอบความเท่าเทียมกันโดยประมาณควรกลับมาจริงหรือไม่
Matt Chambers

14

รหัสที่คุณเขียนนั้นถูกบั๊ก:

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

รหัสที่ถูกต้องจะเป็น:

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

(... และใช่มันแตกต่างกัน)

ฉันสงสัยว่า fabs จะไม่ทำให้คุณสูญเสียการประเมินที่ขี้เกียจในบางกรณี ฉันจะบอกว่ามันขึ้นอยู่กับคอมไพเลอร์ คุณอาจต้องการลองทั้งสองอย่าง หากพวกเขาโดยเฉลี่ยเทียบเท่าใช้ fabs

หากคุณมีข้อมูลบางอย่างเกี่ยวกับการลอยตัวของทั้งสองมีแนวโน้มที่จะใหญ่กว่าที่อื่น ๆ คุณสามารถเล่นตามลำดับของการเปรียบเทียบเพื่อใช้ประโยชน์จากการประเมินที่ขี้เกียจ

ในที่สุดคุณอาจได้รับผลลัพธ์ที่ดีขึ้นโดยการใส่ฟังก์ชั่นนี้ ไม่น่าจะดีขึ้นมาก ...

แก้ไข: OJ ขอบคุณสำหรับการแก้ไขรหัสของคุณ ฉันลบความคิดเห็นของฉันตามนั้น


13

`ส่งคืน 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
นอกเหนือจาก typos (s / - /, / จุลภาคที่หายไปใน fmax ()) การใช้งานนี้มีข้อผิดพลาดสำหรับตัวเลขที่อยู่ใกล้ศูนย์ที่อยู่ใน EPSILON แต่ยังไม่ VERYSMALL เช่น AreSame (1.0E-10, 1.0E-9) รายงานเท็จเนื่องจากข้อผิดพลาดสัมพัทธ์มีขนาดใหญ่มาก คุณจะได้เป็นฮีโร่ใน บริษัท ของคุณ
brlcad

1
@brlcad คุณไม่ได้รับจุดของลอยจุด 1.0E-10 และ 1.0E-9 ต่างกันตามขนาด 10 ดังนั้นจึงเป็นความจริงที่ว่ามันไม่เหมือนกัน ลอยจุดอยู่เสมอเกี่ยวกับญาติข้อผิดพลาด หากคุณมีระบบที่พิจารณา 1.0E-10 และ 1.0E-9 เกือบเท่ากันเนื่องจากทั้งคู่นั้นค่อนข้าง "ใกล้กับศูนย์" (ซึ่งฟังดูสมเหตุสมผลกับมนุษย์ แต่ไม่มีอะไรทางคณิตศาสตร์) ดังนั้น EPSILON นั้นจำเป็นต้องปรับตามความเหมาะสม สำหรับระบบดังกล่าว
user2261015

8

ตามที่คนอื่น ๆ ชี้ให้เห็นการใช้ epsilon แบบ exponent แบบคงที่ (เช่น 0.0000001) จะไม่มีประโยชน์สำหรับค่าที่อยู่ห่างจากค่า epsilon ตัวอย่างเช่นหากค่าสองค่าของคุณคือ 10,000.000977 และ 10,000 ดังนั้นจะมีค่าเลขทศนิยมไม่ 32 บิตระหว่างตัวเลขสองตัวนี้ - 10,000 และ 10,000.000977 ใกล้เคียงที่สุดเท่าที่คุณจะได้รับโดยไม่ต้องบิตแบบบิตต่อบิต ที่นี่ epsilon น้อยกว่า 0.0009 นั้นไร้ความหมาย คุณอาจใช้โอเปอเรเตอร์ความเสมอภาคโดยตรงเช่นกัน

ในทำนองเดียวกันเมื่อค่าทั้งสองเข้าใกล้ขนาด epsilon ข้อผิดพลาดสัมพัทธ์จะเพิ่มขึ้นเป็น 100%

ดังนั้นการพยายามผสมจำนวนจุดคงที่เช่น 0.00001 กับค่าจุดลอยตัว (โดยที่เลขชี้กำลังเป็นเลขชี้กำลัง) เป็นแบบฝึกหัดที่ไม่มีจุดหมาย สิ่งนี้จะใช้งานได้ต่อเมื่อคุณมั่นใจได้ว่าค่าตัวถูกดำเนินการอยู่ภายในโดเมนแคบ ๆ (นั่นคือใกล้กับเลขชี้กำลังบางตัว) และหากคุณเลือกค่า epsilon สำหรับการทดสอบนั้นอย่างถูกต้อง หากคุณดึงตัวเลขออกมาจากอากาศ ("เฮ้! 0.00001 มีขนาดเล็กดังนั้นต้องดี!") คุณจะได้รับข้อผิดพลาดที่เป็นตัวเลข ฉันใช้เวลามากมายในการดีบักรหัสตัวเลขที่ไม่ดีที่ schmuck ที่ไม่ดีบางตัวโยนค่า epsilon แบบสุ่มเพื่อทำกรณีทดสอบอีกอัน

ถ้าคุณทำเช่นการเขียนโปรแกรมเชิงตัวเลขของชนิดใด ๆ และเชื่อว่าคุณต้องเข้าถึงสำหรับ Epsilons คงจุดอ่าน BRUCE บทความเกี่ยวกับการเปรียบเทียบจำนวนจุดลอยตัว

การเปรียบเทียบตัวเลขทศนิยม


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 (ขั้นตอน) ของตัวเลขที่เป็นปัญหา (ตัวเลือกสต็อคดิจิทัลอยู่ในใจ) เมื่อการสังเกตของคุณอยู่ใกล้กับสิ่งกีดขวาง การเปรียบเทียบตามความอดทนนั้นทำได้ไม่ดีนักเนื่องจากมันจะเปลี่ยนปัญหาจากอุปสรรคเดิมเป็นสองใหม่อย่างมีประสิทธิภาพ อีกครั้งไม่มีวิธีแก้ปัญหาทั่วไปสำหรับปัญหาดังกล่าวและวิธีแก้ปัญหาเฉพาะอาจต้องการไปถึงการเปลี่ยนวิธีเชิงตัวเลขเพื่อให้เกิดความมั่นคง


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
Pascal Cuoq

ทำไมคุณถึงคิดว่าEPSILONในคำถามDBL_EPSILONหรือFLT_EPSILON? ปัญหาอยู่ในจินตนาการของคุณเองซึ่งคุณได้แทนที่DBL_EPSILON(ซึ่งแน่นอนว่าจะเป็นตัวเลือกที่ผิด) ในโค้ดที่ไม่ได้ใช้
Ben Voigt

@ BenVoigt คุณพูดถูกมันเป็นสิ่งที่อยู่ในใจของฉันในเวลานั้นและฉันก็ตีความคำถามในแง่นั้น
Don Reba

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 คุณอาจใช้การทดสอบความเท่าเทียมกัน epsilons แบบคงที่จะไม่บันทึกคุณเมื่อเปรียบเทียบค่าทศนิยม


ฉันเชื่อว่าreturn *(reinterpret_cast<double*>(&x));แม้ว่าจะใช้งานได้ตามปกติ แต่ในความเป็นจริงแล้วพฤติกรรมที่ไม่ได้กำหนด
Jaap Versteegh

จุดประสงค์ถึงแม้ว่ารหัสนี้เป็นตัวอย่าง แต่เพียงพอที่จะแสดงให้เห็นถึงปัญหาสำหรับnumeric_limits<>::epsilonจุดปูพื้นและ IEEE 754
Steve Hollasch

ยังเป็นจุดที่ยุติธรรม แต่ก็ไม่ควรที่จะโพสต์บนกองล้นที่คาดว่าจะมีความเข้าใจด้านนั้น รหัสจะถูกคัดลอกสุ่มสี่สุ่มห้าทำให้ยากต่อการกำจัดรูปแบบที่พบบ่อยมากนี้พร้อมกับเคล็ดลับการรวมที่ควรหลีกเลี่ยงตามที่ 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 ข้อผิดพลาดจะมีนัยสำคัญเฉพาะในผลลัพธ์ของการลบอย่างไรก็ตามลำดับความสำคัญเมื่อเทียบกับตัวถูกดำเนินการสองตัวที่ถูกลบควรยังเชื่อถือได้เพียงพอที่จะตัดสินความเท่าเทียมกัน เว้นแต่ความละเอียดจะต้องสูงขึ้นโดยรวม แต่ในกรณีนั้นทางออกเดียวคือการย้ายไปที่การแสดงจุดลอยที่มีบิตที่สำคัญกว่าในแมนทิสซา
sehe

การลบจำนวนเกือบสองเท่าไม่ได้นำไปสู่การยกเลิกหายนะอันที่จริงแล้วมันไม่ได้แนะนำข้อผิดพลาดใด ๆ เลย (ทฤษฎีบทของ qv Sterbenz) การยกเลิกภัยพิบัติเกิดขึ้นก่อนหน้านี้ในระหว่างการคำนวณaและbตัวเอง ไม่มีปัญหากับการใช้การลบจุดลอยตัวซึ่งเป็นส่วนหนึ่งของการเปรียบเทียบแบบคลุมเครือ (แม้ว่าอย่างที่คนอื่นพูดแล้วค่าเอปไซลอนแบบสัมบูรณ์อาจหรืออาจไม่เหมาะสมสำหรับกรณีการใช้งานที่กำหนด)
Sneftel

0

มีจริงกรณีที่มีซอฟต์แวร์ที่เป็นตัวเลขที่คุณต้องการตรวจสอบว่าทั้งสองตัวเลขทศนิยมเป็นว่าที่เท่าเทียมกัน ฉันโพสต์สิ่งนี้ในคำถามที่คล้ายกัน

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

ดังนั้นคุณไม่สามารถพูดได้ว่า "CompareDoubles1" โดยทั่วไปผิด


จริงๆแล้วการอ้างอิงที่แข็งแกร่งมากกับคำตอบที่ดีแม้ว่าจะมีความเชี่ยวชาญเป็นพิเศษในการ จำกัด ใครก็ตามที่ไม่มีการคำนวณทางวิทยาศาสตร์หรือพื้นหลังการวิเคราะห์เชิงตัวเลข (Ie LAPACK, BLAS) เพื่อไม่เข้าใจความสมบูรณ์ หรือกล่าวอีกอย่างหนึ่งก็คือสมมติว่าคุณอ่านอะไรบางอย่างเช่นการแนะนำสูตรอาหารเชิงตัวเลขหรือการวิเคราะห์เชิงตัวเลขโดยภาระและ 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 และหมายเลข representable ถัดไปหลังจากที่ 1. ที่ดีที่สุดรหัสที่เป็นเพียงการพยายามที่จะตรวจสอบว่าทั้งสองตัวเลขตรงเท่ากับแต่ละอื่น ๆ แต่เพราะไม่ใช่อำนาจของ 2 จะถูกคูณด้วยepsilonมัน แม้แต่ทำอย่างไม่ถูกต้อง
Sneftel

2
โอ้และstd::fabs(std::min(v1, v2))ไม่ถูกต้อง - สำหรับอินพุตเชิงลบมันจะเลือกอันที่มีขนาดใหญ่กว่า
Sneftel

0

ฉันเขียนสิ่งนี้สำหรับ java แต่บางทีคุณอาจพบว่ามีประโยชน์ มันใช้ longs แทนที่จะเป็น double แต่จะดูแล 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

แล้วเรื่องนี้ล่ะ

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

ฉันเคยเห็นวิธีการต่าง ๆ แต่ไม่เคยเห็นสิ่งนี้ดังนั้นฉันอยากรู้อยากเห็นความคิดเห็นใด ๆ เช่นกัน!


สิ่งนี้ใช้ไม่ได้กับ 1.99999999 และ 1.99999998
Mehdi

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

ตรงกับคำตอบอื่น ๆ ทั้งหมดยกเว้นว่ามันเป็นแลมบ์ดาและไม่มีคำอธิบายดังนั้นสิ่งนี้จึงไม่ได้เพิ่มคุณค่ามากเท่ากับคำตอบ
stijn

-2

ทางของฉันอาจไม่ถูกต้อง แต่มีประโยชน์

แปลงทั้งทศนิยมให้เป็นสตริงจากนั้นทำการเปรียบเทียบสตริง

bool IsFlaotEqual(float a, float b, int decimal)
{
    TCHAR form[50] = _T("");
    _stprintf(form, _T("%%.%df"), decimal);


    TCHAR a1[30] = _T(""), a2[30] = _T("");
    _stprintf(a1, form, a);
    _stprintf(a2, form, b);

    if( _tcscmp(a1, a2) == 0 )
        return true;

    return false;

}

นอกจากนี้ยังสามารถทำได้


+1: เฮ้ฉันจะไม่เขียนโปรแกรมเกมกับสิ่งนี้ แต่ความคิดเกี่ยวกับการปัดเศษขึ้นมาหลายครั้งในบล็อกของ Bruce Dawson (บทความ?: D) เกี่ยวกับปัญหาและหากคุณติดอยู่ใน ห้องและบางคนวางปืนไว้ที่หัวของคุณแล้วพูดว่า "เฮ้คุณต้องเปรียบเทียบสองลอยไปภายใน X บุคคลสำคัญคุณมีเวลา 5 นาทีไป!" นี่อาจเป็นสิ่งที่ต้องพิจารณา ;)
shelleybutterfly

@ shelleybutterfly แล้วคำถามก็คือวิธีที่มีประสิทธิภาพมากที่สุดในการเปรียบเทียบตัวเลขทศนิยมสองตัว
Tommy Andersen

@ ทอมมี่ฮ่า ๆ ๆ ๆ ๆ ๆ ๆ ๆ ๆ ๆ ๆ ๆ ๆ แต่ฉันคิดว่าการปัดเศษการปัดเศษนั้นมีสาเหตุมาจากเหตุผลที่ไม่เกี่ยวกับประสิทธิภาพ แม้ว่าสัญชาตญาณของฉันคือมันจะค่อนข้างไม่มีประสิทธิภาพเมื่อเทียบกับคณิตศาสตร์ HW fp แต่ยังบอกว่า algoritihms ใน fp ซอฟต์แวร์ไม่น่าจะมีความแตกต่างใหญ่อย่างน้อย ฉันรอการวิเคราะห์ที่คุณแสดงความกังวลเกี่ยวกับประสิทธิภาพในกรณีนั้นอย่างมีนัยสำคัญ นอกจากนี้บางครั้งน้อยกว่าที่ดีที่สุดยังสามารถเป็นคำตอบที่มีคุณค่าและเป็น downvoted - แม้จะเป็นเทคนิคที่ถูกต้องซึ่งถูกกล่าวถึงโดยบล็อกของ Dawson ในเรื่องดังนั้นฉันคิดว่ามันสมควรได้รับ upvote
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 ในระดับบิต ตัวเลขทศนิยมสองตัวมีค่าเท่ากันหากบิตที่สอดคล้องกันมีค่าเท่ากัน ฉันคิดว่าการตัดสินใจที่จะวางบิตเลขชี้กำลังก่อนที่ mantissa จะถูกทำให้เร็วขึ้นเพื่อเปรียบเทียบการลอยสองอัน ฉันคิดว่าคำตอบมากมายที่นี่ขาดจุดเปรียบเทียบเอปไซลอน ค่าเอปไซลอนขึ้นอยู่กับความแม่นยำของตัวเลขทศนิยมที่ถูกเปรียบเทียบ ตัวอย่างเช่นหลังจากทำการคำนวณทางคณิตศาสตร์ด้วยการลอยคุณจะได้รับสองตัวเลข: 2.5642943554342 และ 2.5642943554345 พวกเขาไม่เท่ากัน แต่สำหรับวิธีแก้ปัญหามีเพียงทศนิยม 3 หลักเท่านั้นดังนั้นพวกเขาจะเท่ากับ: 2.564 และ 2.564 ในกรณีนี้คุณเลือก epsilon เท่ากับ 0.001 การเปรียบเทียบเอปไซลอนยังสามารถทำได้ด้วย bitwise XOR ถูกต้องฉันถ้าฉันผิด


โปรดอย่าเพิ่มคำตอบเดียวกันในหลายคำถาม ตอบคำถามที่ดีที่สุดและทำเครื่องหมายส่วนที่เหลือเป็นรายการซ้ำ ดูmeta.stackexchange.com/questions/104227/ …
Bhargav Rao

ฉันไม่คิดว่า "การเปรียบเทียบ epsilon" เป็นไปได้โดยใช้เพียงแค่ ExOr (และเปรียบเทียบหนึ่งหรือสองครั้ง) แม้จะถูก จำกัด ให้มีการนำเสนอแบบปกติในรูปแบบเดียวกัน
greybeard
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.