Cách hiệu quả nhất để nổi và so sánh kép là gì?


524

Điều gì sẽ là cách hiệu quả nhất để so sánh hai doublehoặc hai floatgiá trị?

Đơn giản chỉ cần làm điều này là không chính xác:

bool CompareDoubles1 (double A, double B)
{
   return A == B;
}

Nhưng một cái gì đó như:

bool CompareDoubles2 (double A, double B) 
{
   diff = A - B;
   return (diff < EPSILON) && (-diff < EPSILON);
}

Có vẻ để xử lý chất thải.

Có ai biết một so sánh nổi thông minh hơn?


2
> sẽ hiệu quả hơn nếu thêm ... vào đầu hàm? <invoke Knuth>Tối ưu hóa sớm là gốc rễ của mọi tội lỗi. </invoke Knuth>Chỉ cần đi với abs (ab) <EPS như đã lưu ý ở trên, nó rõ ràng và dễ hiểu.
Andrew Coleson

2
Đây là cách được triển khai trong Thư viện thử nghiệm Boost: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html
Alessandro Jacopson

2
Điều duy nhất không tối ưu về việc triển khai của người đăng ban đầu là nó có chứa một nhánh phụ tại &&. Câu trả lời của OJ là tối ưu. fabs là một nội tại là một hướng dẫn duy nhất trên x87 và tôi cho rằng trên hầu hết mọi thứ khác. Chấp nhận câu trả lời của OJ rồi!
3yE

3
Nếu bạn có thể, hãy thả điểm nổi và sử dụng các điểm cố định. Ví dụ: sử dụng {điểm cố định} milimet thay vì {dấu phẩy động} mét.
Thomas Matthews

33
"Đơn giản chỉ cần làm điều này là không chính xác" - Đây chỉ là rác rưởi, tất nhiên việc sử dụng ==có thể hoàn toàn chính xác, nhưng điều này hoàn toàn phụ thuộc vào bối cảnh không được đưa ra trong câu hỏi. Cho đến khi bối cảnh đó được biết đến, ==vẫn là "cách hiệu quả nhất" .
Christian Rau

Câu trả lời:


459

Hãy cực kỳ cẩn thận bằng cách sử dụng bất kỳ lời đề nghị nào khác. Tất cả phụ thuộc vào bối cảnh.

Tôi đã dành một thời gian dài để truy tìm một lỗi trong một hệ thống được cho là a==bnếu |a-b|<epsilon. Các vấn đề cơ bản là:

  1. Giả định ngầm định trong một thuật toán mà nếu a==bb==csau đó a==c.

  2. Sử dụng cùng một epsilon cho các dòng được đo bằng inch và các dòng được đo bằng mils (0,001 inch). Đó là a==bnhưng 1000a!=1000b. (Đây là lý do tại sao RecentEqual2sCompuity yêu cầu epsilon hoặc max ULPS).

  3. Việc sử dụng cùng một epsilon cho cả cosin của góc và độ dài của đường!

  4. Sử dụng chức năng so sánh như vậy để sắp xếp các mục trong bộ sưu tập. (Trong trường hợp này, sử dụng toán tử C ++ dựng sẵn == để nhân đôi kết quả chính xác.)

Giống như tôi đã nói: tất cả phụ thuộc vào bối cảnh và kích thước dự kiến ​​của ab.

BTW, std::numeric_limits<double>::epsilon()là "máy epsilon". Đó là sự khác biệt giữa 1.0 và giá trị tiếp theo được biểu thị bằng một gấp đôi. Tôi đoán rằng nó có thể được sử dụng trong chức năng so sánh nhưng chỉ khi các giá trị mong đợi nhỏ hơn 1. (Điều này phù hợp với câu trả lời của @ cv ...)

Ngoài ra, nếu về cơ bản bạn có intsố học trong doubles(ở đây chúng tôi sử dụng gấp đôi để giữ giá trị int trong một số trường hợp nhất định) thì số học của bạn sẽ chính xác. Ví dụ 4.0 / 2.0 sẽ giống như 1.0 + 1.0. Điều này miễn là bạn không làm những việc dẫn đến phân số (4.0 / 3.0) hoặc không vượt quá kích thước của một int.


10
+1 để chỉ ra điều hiển nhiên (thường bị bỏ qua). Đối với một phương thức chung, bạn có thể tạo ra epsilon tương đối fabs(a)+fabs(b)nhưng với bù NaN, 0 tổng và tràn, điều này trở nên khá phức tạp.
peterchen

4
Có điều gì đó tôi không hiểu. Điển hình float/ doubleMANTISSA x 2 ^ EXP . epsilonsẽ phụ thuộc vào số mũ. Ví dụ: nếu mantissa là 24 bit và số mũ được ký 8 bit, thì 1/(2^24)*2^127hoặc ~2^103epsiloncho một số giá trị; hoặc điều này đề cập đến một epsilon tối thiểu ?
tiếng ồn vô nghĩa

3
Chờ giây lát. Là những gì tôi nói những gì bạn có nghĩa là? Bạn đang nói tại sao |a-b|<epsilon, không đúng. Vui lòng thêm liên kết này vào câu trả lời của bạn; nếu bạn đồng ý cygnus-software.com/ersky/comparedfloats/comparedfloats.htm và tôi có thể xóa các bình luận ngu ngốc của mình.
tiếng ồn vô nghĩa

3
Đây là một nhận xét rất dài, bản thân nó không phải là một câu trả lời. Có một (bộ) câu trả lời chính tắc cho tất cả các bối cảnh không?
Merlyn Morgan-Graham

2
Liên kết cũ dường như đã lỗi thời, trang mới có ở đây Randomascii.wordpress.com/2012/02/11/iêu
Marson Mao

174

So sánh với giá trị epsilon là những gì hầu hết mọi người làm (ngay cả trong lập trình trò chơi).

Bạn nên thay đổi thực hiện của bạn một chút mặc dù:

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

Chỉnh sửa: Christer đã thêm một đống thông tin tuyệt vời về chủ đề này trên một bài đăng blog gần đây . Thưởng thức.


@OJ: có gì sai với mẫu mã đầu tiên? Tôi nghĩ vấn đề duy nhất là trong một tình huống như thế này: float a = 3.4; if(a == 3.4){...}tức là khi bạn đang so sánh một điểm nổi được lưu trữ với một chữ | Trong trường hợp này, cả hai số được lưu trữ, vì vậy chúng sẽ có cùng một đại diện, nếu bằng nhau, vậy tác hại của việc làm là gì a == b?
Lazer

11
@DonReba: Chỉ khi EPSILONđược định nghĩa là DBL_EPSILON. Thông thường nó sẽ là một giá trị cụ thể được chọn tùy thuộc vào độ chính xác cần thiết của phép so sánh.
Nemo157

7
EPSILONso sánh không hoạt động khi phao lớn, vì sự khác biệt giữa các phao liên tiếp cũng trở nên lớn. Xem bài viết này .
kevintodisco

22
Không có gì ngạc nhiên khi có Z-combat trong một số trò chơi khi kết cấu / vật thể ở xa nhấp nháy, như trong Battlefield 4. So sánh sự khác biệt với EPSILONkhá nhiều vô dụng. Bạn cần so sánh với một ngưỡng có ý nghĩa cho các đơn vị trong tầm tay. Ngoài ra, sử dụng std::absvì nó bị quá tải cho các loại dấu phẩy động khác nhau.
Maxim Egorushkin

11
Tôi đã bỏ qua vì mã ví dụ cho thấy lỗi whis điển hình được lặp lại bởi phần lớn các lập trình viên. Điểm nổi luôn là về các lỗi tương đối, vì đó là điểm nổi (không phải điểm cố định). Vì vậy, nó sẽ không bao giờ hoạt động chính xác với một lỗi cố định (epsilon).
dùng2261015

115

Tôi thấy rằng Khung kiểm tra Google C ++ chứa một triển khai nền tảng đa nền tảng đẹp mắt của MostEqual2sCompuity, hoạt động trên cả hai nhân đôi và số float. Cho rằng nó được phát hành theo giấy phép BSD, sử dụng nó trong mã của riêng bạn sẽ không có vấn đề gì, miễn là bạn giữ lại giấy phép. Tôi đã trích xuất mã dưới đây từ http://code.google.com.vn/p/googletest/source/browse/trunk/include/gtest/i INTERNal / gtest-i INTERNal.h https://github.com/google/googletest/blob /master/googletest/include/gtest/iternal/gtest-i INTERNal.h và thêm giấy phép lên trên.

Hãy chắc chắn #define GTEST_OS_WINDOWS thành một giá trị nào đó (hoặc để thay đổi mã nơi nó được sử dụng thành một cái gì đó phù hợp với cơ sở mã của bạn - rốt cuộc đó là BSD được cấp phép).

Ví dụ sử dụng:

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

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

Đây là mã:

// 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: Bài này là 4 tuổi. Nó có thể vẫn còn hiệu lực và mã rất hay, nhưng một số người đã tìm thấy những cải tiến. Tốt nhất hãy lấy phiên bản mới nhất AlmostEqualsngay từ mã nguồn Google Test chứ không phải phiên bản tôi đã dán ở đây.


3
+1: Tôi đồng ý điều này là chính xác. Tuy nhiên, nó không giải thích tại sao. Xem ở đây: cygnus-software.com/ersky/comparedfloats/comparedfloats.htm Tôi đã đọc bài đăng trên blog này sau khi tôi viết nhận xét của mình về điểm số cao nhất ở đây; Tôi tin rằng nó nói điều tương tự và cung cấp giải pháp hợp lý / được thực hiện ở trên. Bởi vì có quá nhiều mã, mọi người sẽ bỏ lỡ câu trả lời.
tiếng ồn vô nghĩa

Có một vài điều khó chịu có thể xảy ra khi diễn viên ngầm xảy ra khi nói FloatPoint <double> fp (0,03f). Tôi đã thực hiện một vài sửa đổi cho điều này để giúp ngăn chặn điều đó. template <typename U> FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "Bạn đang thực hiện chuyển đổi ngầm với FloatingPoint, Đừng "<< std :: endl; khẳng định (typeid (U) .name () == typeid (RawType) .name ()); } u_.value_ = x; }
JeffCharter

2
Tìm tốt Tôi đoán rằng tốt nhất là đóng góp chúng cho Google Test, tuy nhiên, nơi mã này đã bị đánh cắp. Tôi sẽ cập nhật bài viết để phản ánh rằng có lẽ có phiên bản mới hơn. Nếu những người Google hành động ngứa ngáy, bạn có thể đặt nó vào ví dụ như một ý chính của GitHub không? Tôi cũng sẽ liên kết với nó.
skrebbel

3
Đối với đoạn mã mới nhất, xem tại đâyđây .
Jaege

1
Tôi đã trích xuất các dòng cần thiết vào một tập tin chính. Bất cứ ai cũng có thể đạt được từ đây .
Yusuf Tarık Günaydın

111

So sánh số dấu phẩy động cho phụ thuộc vào bối cảnh. Vì ngay cả việc thay đổi thứ tự các thao tác có thể tạo ra các kết quả khác nhau, điều quan trọng là phải biết bạn muốn các số đó bằng nhau như thế nào.

So sánh số dấu phẩy động của Bruce Dawson là một nơi tốt để bắt đầu khi nhìn vào so sánh điểm nổi.

Các định nghĩa sau đây là từ Nghệ thuật lập trình máy tính của 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);
}

Tất nhiên, việc chọn epsilon phụ thuộc vào ngữ cảnh và xác định mức độ bạn muốn các số bằng nhau.

Một phương pháp khác để so sánh các số dấu phẩy động là xem xét ULP (đơn vị ở vị trí cuối cùng) của các số. Mặc dù không đề cập cụ thể đến việc so sánh, bài báo Điều mà mọi nhà khoa học máy tính nên biết về số dấu phẩy động là một nguồn tốt để hiểu cách hoạt động của dấu phẩy động và những cạm bẫy là gì, bao gồm cả ULP là gì.


1
cảm ơn vì đã đăng bài làm thế nào để xác định số nào nhỏ hơn / lớn hơn!
Cà chua

1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);đã cứu sống tôi LOL Lưu ý rằng phiên bản này (tôi cũng chưa kiểm tra nếu áp dụng cho các phiên bản khác) cũng xem xét sự thay đổi có thể xảy ra trong phần không thể thiếu của số dấu phẩy động (ví dụ: 2147352577.9999997616 == 2147352576.0000000000nơi bạn có thể thấy rõ rằng gần như có sự khác biệt của 2giữa hai số) khá đẹp! Điều này xảy ra khi lỗi làm tròn tích lũy vượt quá phần thập phân của số.
rbaleksandar

Bài viết rất hay và hữu ích của Bruce Dawson, cảm ơn!
BobMorane

2
Cho rằng câu hỏi này được gắn thẻ C ++, séc của bạn sẽ dễ đọc hơn khi được viết là std::max(std::abs(a), std::abs(b))(hoặc với std::min()); std::abstrong C ++ bị quá tải với kiểu float & double, vì vậy nó hoạt động tốt (bạn luôn có thể giữ fabscho dễ đọc).
Razakhel

1
Hóa ra vấn đề nằm ở mã của tôi, sự khác biệt giữa giá trị dự kiến ​​ban đầu và chuỗi được phân tích cú pháp.
mwpowellhtx

47

Để có cách tiếp cận sâu hơn, hãy đọc So sánh các số dấu phẩy động . Đây là đoạn mã từ liên kết đó:

// 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
Giá trị đề xuất của maxUlps là gì?
unj2

6
"" Sẽ *(int*)&A;vi phạm quy tắc răng cưa nghiêm ngặt?
osgx

3
Theo gtest (tìm kiếm ULP), 4 là một con số chấp nhận được.
Có thể vào ngày

4
Và đây là một vài cập nhật cho bài viết của Bruce Dawson (một trong số đó được liên kết trong phần giới thiệu của bài báo): Randomascii.wordpress.com/2012/02/26/ trênRandomascii.wordpress.com/2012/06/26/ Khăn
Michael Burr

3
Tôi đã mất một lúc để tìm hiểu về ULP là gì: Các đơn vị ở vị trí cuối cùng
JeffCharter

27

Nhận ra đây là một chủ đề cũ nhưng bài viết này là một trong những chủ đề thẳng nhất mà tôi đã tìm thấy khi so sánh các số dấu phẩy động và nếu bạn muốn khám phá thêm, nó cũng có nhiều tài liệu tham khảo chi tiết hơn và nó là trang web chính bao gồm một loạt các vấn đề xử lý số dấu phẩy động Hướng dẫn dấu phẩy động : So sánh .

Chúng ta có thể tìm thấy một bài viết thực tế hơn một chút trong các dung sai điểm nổi được xem xét lại và lưu ý rằng có một bài kiểm tra dung sai tuyệt đối , điều này làm rõ điều này trong C ++:

bool absoluteToleranceCompare(double x, double y)
{
    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon() ;
}

và thử nghiệm dung sai tương đối :

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

Bài báo lưu ý rằng thử nghiệm tuyệt đối thất bại khi xylớn và thất bại trong trường hợp tương đối khi chúng nhỏ. Giả sử anh ta dung sai tuyệt đối và tương đối giống như một bài kiểm tra kết hợp sẽ như thế nà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ách di động để có được epsilon trong C ++ là

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

Sau đó, chức năng so sánh trở thành

#include <cmath>
#include <limits>

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

34
Bạn sẽ muốn nhiều bội số đó rất có thể.
dùng7116

11
Bạn không thể sử dụng std :: abs? AFAIK, std :: abs cũng bị quá tải gấp đôi. Hãy cảnh báo tôi nếu tôi sai.
kolistivra

3
@kolistivra, bạn sai rồi. 'F' trong 'fabs' không có nghĩa là kiểu float. Có lẽ bạn đang nghĩ về các hàm C fabsf () và fabsl ().
jcoffland

9
Trên thực tế vì những lý do được nêu trong bài viết epsilon của Bruce thay đổi khi giá trị của dấu phẩy động trở nên lớn hơn. Xem phần mà anh ta nói "Đối với các số lớn hơn 2.0, khoảng cách giữa các số float sẽ lớn hơn và nếu bạn so sánh các số float bằng FLT_EPSILON thì bạn chỉ đang thực hiện kiểm tra công bằng đắt hơn và ít rõ ràng hơn."
bobobobo

5
Tôi biết điều này đã cũ nhưng std :: abs bị quá tải cho các loại dấu phẩy động trong cmath.
mholzmann

18

Cuối cùng tôi đã dành khá nhiều thời gian để xem qua tài liệu trong chủ đề tuyệt vời này. Tôi nghi ngờ tất cả mọi người muốn dành nhiều thời gian vì vậy tôi sẽ nhấn mạnh tóm tắt về những gì tôi đã học và giải pháp tôi đã thực hiện.

Tóm tắt nhanh

  1. Là 1e-8 gần giống với 1e-16? Nếu bạn đang xem dữ liệu cảm biến ồn ào thì có lẽ có nhưng nếu bạn đang thực hiện mô phỏng phân tử thì có thể không! Điểm mấu chốt: Bạn luôn cần nghĩ về giá trị dung sai trong ngữ cảnh của lệnh gọi hàm cụ thể và không chỉ làm cho nó trở thành hằng số mã hóa toàn ứng dụng chung.
  2. Đối với các chức năng thư viện chung, thật tuyệt khi có tham số với dung sai mặc định . Một lựa chọn điển hình numeric_limits::epsilon()giống như FLT_EPSILON trong float.h. Tuy nhiên, điều này có vấn đề vì epsilon để so sánh các giá trị như 1.0 không giống với epsilon cho các giá trị như 1E9. FLT_EPSILON được xác định cho 1.0.
  3. Việc triển khai rõ ràng để kiểm tra xem số có nằm trong dung sai hay không fabs(a-b) <= epsilontuy nhiên điều này không hoạt động vì epsilon mặc định được xác định cho 1.0. Chúng ta cần mở rộng epsilon lên hoặc xuống theo a và b.
  4. Có hai giải pháp cho vấn đề này: hoặc bạn đặt epsilon tỷ lệ thuận max(a,b)hoặc bạn có thể nhận được các số đại diện tiếp theo xung quanh a và sau đó xem b có rơi vào phạm vi đó không. Cái trước được gọi là phương thức "tương đối" và sau này được gọi là phương thức ULP.
  5. Cả hai phương pháp đều thực sự thất bại khi so sánh với 0. Trong trường hợp này, ứng dụng phải cung cấp dung sai chính xác.

Thực hiện các chức năng tiện ích (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;
}

isDefinitelyLessThankiểm tra diff < tolerance, có nghĩa là a và b gần như bằng nhau (và vì vậy a không chắc chắn nhỏ hơn b). Nó không có ý nghĩa hơn để kiểm tra diff> dung sai trong cả hai trường hợp? Hoặc có lẽ thêm một orEqualTođối số kiểm soát xem kiểm tra đẳng thức gần đúng có trở lại đúng hay không.
Matt Chambers

14

Mã bạn đã viết bị lỗi:

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

Mã chính xác sẽ là:

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

(... Và vâng, điều này khác)

Tôi tự hỏi nếu fabs sẽ không làm bạn mất đánh giá lười biếng trong một số trường hợp. Tôi muốn nói rằng nó phụ thuộc vào trình biên dịch. Bạn có thể muốn thử cả hai. Nếu chúng tương đương ở mức trung bình, hãy thực hiện với fabs.

Nếu bạn có một số thông tin về cái nào trong số hai float có khả năng lớn hơn cái kia, bạn có thể chơi theo thứ tự so sánh để tận dụng tốt hơn việc đánh giá lười biếng.

Cuối cùng, bạn có thể nhận được kết quả tốt hơn bằng cách nội tuyến chức năng này. Không có khả năng cải thiện nhiều mặc dù ...

Chỉnh sửa: OJ, cảm ơn vì đã sửa mã của bạn. Tôi đã xóa bình luận của tôi cho phù hợp


13

`fabs trở lại (a - b) <EPSILON;

Điều này tốt nếu:

  • thứ tự cường độ của đầu vào của bạn không thay đổi nhiều
  • số lượng rất nhỏ các dấu hiệu trái ngược có thể được coi là bằng nhau

Nhưng nếu không, nó sẽ dẫn bạn vào rắc rối. Số chính xác kép có độ phân giải khoảng 16 chữ số thập phân. Nếu hai số bạn đang so sánh có độ lớn lớn hơn EPSILON * 1.0E16, thì bạn cũng có thể nói:

return a==b;

Tôi sẽ kiểm tra một cách tiếp cận khác mà cho rằng bạn cần phải lo lắng về vấn đề đầu tiên và cho rằng cách thứ hai là ứng dụng của bạn ổn. Một giải pháp sẽ là một cái gì đó như:

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

Đây là đắt tiền tính toán, nhưng đôi khi nó được gọi là cho. Đây là những gì chúng tôi phải làm tại công ty của tôi vì chúng tôi làm việc với một thư viện kỹ thuật và đầu vào có thể thay đổi vài chục đơn đặt hàng lớn.

Dù sao, vấn đề là (và áp dụng cho thực tế mọi vấn đề lập trình): Đánh giá nhu cầu của bạn là gì, sau đó đưa ra giải pháp để giải quyết nhu cầu của bạn - đừng cho rằng câu trả lời dễ dàng sẽ giải quyết nhu cầu của bạn. Nếu sau khi đánh giá của bạn mà bạn thấy rằng fabs(a-b) < EPSILONsẽ đủ, hoàn hảo - sử dụng nó! Nhưng hãy chú ý đến những thiếu sót của nó và các giải pháp khả thi khác.


3
Ngoài các lỗi chính tả (s / - /, / thiếu dấu phẩy trong fmax ()), việc triển khai này có một lỗi đối với các số gần 0 nằm trong EPSILON, nhưng chưa hoàn toàn XÁC MINH. Ví dụ: AreSame (1.0E-10, 1.0E-9) báo cáo sai vì lỗi tương đối là rất lớn. Bạn có thể trở thành anh hùng tại công ty của bạn.
brlcad

1
@brlcad Bạn không nhận được điểm nổi . 1.0E-10 và 1.0E-9 khác nhau theo độ lớn của 10. Vì vậy, đúng là chúng không giống nhau. điểm nổi luôn luôn là về lỗi tương đối . Nếu bạn có một hệ thống coi 1.0E-10 và 1.0E-9 gần như bằng nhau, vì cả hai đều "gần bằng 0" (nghe có vẻ hợp lý với con người nhưng không có gì về mặt toán học), thì EPSILON cần phải được điều chỉnh cho phù hợp cho một hệ thống như vậy
dùng2261015

8

Như những người khác đã chỉ ra, sử dụng một epsilon có số mũ cố định (chẳng hạn như 0,0000001) sẽ vô dụng đối với các giá trị cách xa giá trị epsilon. Ví dụ: nếu hai giá trị của bạn là 10000.000977 và 10000, thì KHÔNG có có giá trị dấu phẩy động 32 bit nào giữa hai số này - 10000 và 10000.000977 gần như bạn có thể nhận được mà không giống nhau từng bit. Ở đây, một epsilon dưới 0,0009 là vô nghĩa; bạn cũng có thể sử dụng toán tử đẳng thức thẳng.

Tương tự, khi hai giá trị tiếp cận kích thước epsilon, sai số tương đối tăng lên 100%.

Do đó, cố gắng trộn một số điểm cố định, chẳng hạn như 0,00001 với các giá trị dấu phẩy động (trong đó số mũ là tùy ý) là một bài tập vô nghĩa. Điều này sẽ chỉ hoạt động nếu bạn có thể yên tâm rằng các giá trị toán hạng nằm trong một miền hẹp (nghĩa là gần với số mũ cụ thể) và nếu bạn chọn đúng giá trị epsilon cho thử nghiệm cụ thể đó. Nếu bạn kéo một số ra khỏi không khí ("Này! 0,00001 là nhỏ, do đó phải tốt!"), Bạn sẽ phải chịu những lỗi số. Tôi đã dành nhiều thời gian để gỡ lỗi mã số xấu trong đó một số schmuck kém đưa các giá trị epsilon ngẫu nhiên để làm cho một trường hợp thử nghiệm khác hoạt động.

Nếu bạn thực hiện lập trình số dưới bất kỳ hình thức nào và tin rằng bạn cần phải tiếp cận với các epsilon có điểm cố định, ĐỌC BÀI VIẾT CỦA BRUCE TRÊN SỐ SO SÁNH-ĐIỂM SỐ .

So sánh số dấu phẩy động


5

Qt thực hiện hai chức năng, có thể bạn có thể học hỏi từ chúng:

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

Và bạn có thể cần các chức năng sau, vì

Lưu ý rằng so sánh các giá trị trong đó p1 hoặc p2 là 0,0 sẽ không hoạt động, cũng như không so sánh các giá trị trong đó một trong các giá trị là NaN hoặc vô cùng. Nếu một trong các giá trị luôn là 0,0, thay vào đó hãy sử dụng qFuzzyIsNull. Nếu một trong các giá trị có khả năng là 0,0, một giải pháp là thêm 1.0 cho cả hai giá trị.

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

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

3

So sánh mục đích chung của các số dấu phẩy động nói chung là vô nghĩa. Làm thế nào để so sánh thực sự phụ thuộc vào một vấn đề trong tầm tay. Trong nhiều vấn đề, các con số đủ rời rạc để cho phép so sánh chúng trong một dung sai cho trước. Thật không may, có rất nhiều vấn đề, trong đó thủ thuật như vậy không thực sự hiệu quả. Ví dụ, hãy xem xét làm việc với chức năng Heaviside (bước) của một số đang được đề cập (tùy chọn cổ phiếu kỹ thuật số xuất hiện trong tâm trí) khi các quan sát của bạn rất gần với rào cản. Thực hiện so sánh dựa trên dung sai sẽ không làm được gì nhiều, vì nó sẽ chuyển vấn đề từ rào cản ban đầu sang hai vấn đề mới một cách hiệu quả. Một lần nữa, không có giải pháp đa năng cho các vấn đề như vậy và giải pháp cụ thể có thể yêu cầu đi xa hơn là thay đổi phương pháp số để đạt được sự ổn định.


3

Thật không may, ngay cả mã "lãng phí" của bạn là không chính xác. EPSILON là giá trị nhỏ nhất có thể được thêm vào 1.0 và thay đổi giá trị của nó. Giá trị 1.0 rất quan trọng - số lớn hơn không thay đổi khi được thêm vào EPSILON. Bây giờ, bạn có thể chia tỷ lệ giá trị này thành các số bạn đang so sánh để cho biết chúng có khác nhau hay không. Biểu thức đúng để so sánh hai nhân đôi là:

if (fabs(a - b) <= DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

Đây là mức tối thiểu. Tuy nhiên, nói chung, bạn sẽ muốn tính toán nhiễu trong tính toán của mình và bỏ qua một vài bit có trọng số thấp nhất, do đó, một so sánh thực tế hơn sẽ như sau:

if (fabs(a - b) <= 16 * DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

Nếu hiệu suất so sánh rất quan trọng đối với bạn và bạn biết phạm vi giá trị của mình, thì bạn nên sử dụng các số điểm cố định thay thế.


2
EPSILON là giá trị nhỏ nhất có thể được thêm vào 1.0 và thay đổi giá trị của nó. Thật ra, vinh dự này thuộc về người kế vị 0,5 * EPSILON (ở chế độ từ tròn đến gần nhất mặc định). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq

Tại sao bạn nghĩ rằng EPSILONtrong câu hỏi là DBL_EPSILONhay FLT_EPSILON? Vấn đề nằm ở trí tưởng tượng của chính bạn, nơi bạn thay thế DBL_EPSILON(mà thực sự sẽ là lựa chọn sai) thành mã không sử dụng nó.
Ben Voigt

@BenVoigt, bạn nói đúng, đó là điều gì đó trong tâm trí tôi lúc đó và tôi đã giải thích câu hỏi dưới ánh sáng đó.
Don Reba

2

Lớp học của tôi dựa trên câu trả lời được đăng trước đó. Rất giống với mã của Google nhưng tôi sử dụng xu hướng đẩy tất cả các giá trị NaN lên trên 0xFF000000. Điều đó cho phép kiểm tra NaN nhanh hơn.

Mã này có nghĩa là để chứng minh khái niệm, không phải là một giải pháp chung. Mã của Google đã chỉ ra cách tính tất cả các giá trị cụ thể của nền tảng và tôi không muốn sao chép tất cả các giá trị đó. Tôi đã thực hiện kiểm tra giới hạn về mã này.

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

Đây là bằng chứng cho thấy sử dụng std::numeric_limits::epsilon() không phải là câu trả lời - thất bại đối với các giá trị lớn hơn một:

Bằng chứng nhận xét của tôi ở trên:

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

Chạy mang lại sản lượng này:

0.5000000000000000 and 0.5000000000000001 are  close.
1.0000000000000000 and 1.0000000000000002 are not close.

Lưu ý rằng trong trường hợp thứ hai (một và chỉ lớn hơn một), hai giá trị đầu vào gần nhất có thể và vẫn so sánh là không đóng. Do đó, đối với các giá trị lớn hơn 1.0, bạn cũng có thể chỉ cần sử dụng kiểm tra tính bằng. Các epsilon cố định sẽ không giúp bạn tiết kiệm khi so sánh các giá trị dấu phẩy động.


Tôi tin rằng return *(reinterpret_cast<double*>(&x));mặc dù nó thường hoạt động, nhưng thực tế là hành vi không xác định.
Jaap Versteegh

Điểm công bằng, mặc dù mã này chỉ mang tính minh họa, nhưng đủ để chứng minh vấn đề cho numeric_limits<>::epsilonvà điểm sàn của IEEE 754.
Steve Hollasch

Cũng là một điểm công bằng, nhưng không phải là imho khôn ngoan để đăng lên stack stack mong đợi loại hiểu biết đó. Mã sẽ được sao chép một cách mù quáng khiến cho việc xóa bỏ mô hình rất phổ biến này trở nên khó khăn hơn - cùng với thủ thuật hợp nhất - điều cần tránh là tất cả UD nên làm.
Jaap Versteegh

1

Tìm thấy một triển khai thú vị khác trên: 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

Tôi sẽ rất cảnh giác với bất kỳ câu trả lời nào liên quan đến phép trừ điểm nổi (ví dụ: fabs (ab) <epsilon). Đầu tiên, các số dấu phẩy động trở nên thưa thớt hơn ở cường độ lớn hơn và ở cường độ đủ cao trong đó khoảng cách lớn hơn epsilon, bạn cũng có thể thực hiện a == b. Thứ hai, trừ hai số dấu phẩy động rất gần (vì chúng sẽ có xu hướng, với điều kiện bạn đang tìm kiếm sự bằng nhau) chính xác là cách bạn có được sự hủy bỏ thảm khốc .

Mặc dù không có khả năng di động, tôi nghĩ rằng câu trả lời của grom thực hiện công việc tốt nhất để tránh những vấn đề này.


1
+1 cho thông tin tốt. Tuy nhiên, tôi không thấy làm thế nào bạn có thể làm rối tung sự so sánh bình đẳng bằng cách tăng sai số tương đối; IMHO lỗi chỉ trở nên đáng kể trong kết quả của phép trừ, tuy nhiên thứ tự cường độ của nó so với hai toán hạng bị trừ vẫn phải đủ tin cậy để đánh giá sự bằng nhau. Trừ khi độ phân giải cần phải cao hơn về tổng thể, nhưng trong trường hợp đó, giải pháp duy nhất là chuyển sang biểu diễn dấu phẩy động với các bit quan trọng hơn trong lớp phủ.
sehe

Trừ hai số gần bằng nhau KHÔNG dẫn đến hủy bỏ thảm khốc - thực tế, nó không đưa ra bất kỳ lỗi nào cả (Định lý của qv Sterbenz). Hủy bỏ thảm khốc xảy ra trước đó, trong quá trình tính toán abchính họ. Hoàn toàn không có vấn đề gì với việc sử dụng phép trừ dấu phẩy động như là một phần của so sánh mờ (mặc dù như những người khác đã nói, giá trị epsilon tuyệt đối có thể hoặc không phù hợp với trường hợp sử dụng nhất định.)
Sneftel

0

Thực tế, có những trường hợp trong phần mềm số mà bạn muốn kiểm tra xem hai số dấu phẩy động có chính xác bằng nhau không. Tôi đã đăng bài này lên một câu hỏi tương tự

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

Vì vậy, nói chung, bạn không thể nói rằng "So sánh đôi" là sai.


Trên thực tế, một tài liệu tham khảo rất chắc chắn cho một câu trả lời hay, mặc dù nó rất chuyên biệt để giới hạn bất cứ ai không có máy tính khoa học hoặc nền tảng phân tích số (Ie LAPACK, BLAS) để không hiểu sự hoàn chỉnh. Hay nói cách khác, nó giả định rằng bạn đã đọc một cái gì đó như giới thiệu Công thức số hoặc Phân tích số của Burden & Faires.
mctylr

0

Nó phụ thuộc vào mức độ chính xác mà bạn muốn so sánh. Nếu bạn muốn so sánh cho chính xác cùng một số, thì chỉ cần đi với ==. (Bạn gần như không bao giờ muốn làm điều này trừ khi bạn thực sự muốn chính xác cùng một số.) Trên bất kỳ nền tảng tử tế nào, bạn cũng có thể làm như sau:

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

như fabscó xu hướng khá nhanh. Bởi khá nhanh, ý tôi là về cơ bản là một chút VÀ, vì vậy tốt hơn là nên nhanh.

Và các thủ thuật số nguyên để so sánh các nhân đôi và số float là tốt nhưng có xu hướng làm cho các đường ống CPU khác nhau khó xử lý hiệu quả hơn. Và nó chắc chắn không nhanh hơn trên các kiến ​​trúc theo thứ tự nhất định ngày nay do sử dụng ngăn xếp làm khu vực lưu trữ tạm thời cho các giá trị đang được sử dụng thường xuyên. (Load-hit-store cho những ai quan tâm.)


0

Về quy mô số lượng:

Nếu epsilonlà phần nhỏ của độ lớn của đại lượng (tức là giá trị tương đối) trong một số ý nghĩa vật lý ABloại nhất định có thể so sánh theo cùng một nghĩa, theo tôi nghĩ, điều sau đây là hoàn toàn chính xác:

#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

Tôi sử dụng mã này:

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

2
Đó không phải epsilonlà những gì dành cho.
Sneftel

1
Tại sao không? Bạn có thể giải thích nó được không?
mắt

2
@debuti epsilonchỉ đơn thuần là khoảng cách giữa 1 và số đại diện tiếp theo sau 1. Tốt nhất, mã đó chỉ đang cố kiểm tra xem hai số có chính xác với nhau không, nhưng vì các số không có lũy thừa của 2 được nhân với epsilon, nó thậm chí không làm điều đó một cách chính xác.
Sneftel

2
Ồ, và std::fabs(std::min(v1, v2))không chính xác - đối với các đầu vào tiêu cực, nó chọn một đầu vào có cường độ lớn hơn.
Sneftel

0

Tôi viết cái này cho java, nhưng có lẽ bạn thấy nó hữu ích. Nó sử dụng dài thay vì tăng gấp đôi, nhưng chăm sóc NaN, subnormals, v.v.

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

Hãy nhớ rằng sau một số phép toán dấu phẩy động, số có thể rất khác so với những gì chúng ta mong đợi. Không có mã để sửa lỗi đó.


0

Còn cái này thì sao?

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

Tôi đã thấy nhiều cách tiếp cận khác nhau - nhưng chưa bao giờ thấy điều này, vì vậy tôi cũng tò mò muốn nghe về bất kỳ bình luận nào!


Điều này không hoạt động cho 1.99999999 và 1.99999998
Mehdi

@Mehdi Tôi vừa thử với repl.it/repls/SvelteSimpleNumerator#main.cpp và nó dường như hoạt động như mong đợi - nhưng có lẽ bạn có một triển khai trình biên dịch cụ thể để tham chiếu mà không làm điều này?
derke

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

Tôi đã sử dụng chức năng này cho dự án nhỏ của mình và nó hoạt động, nhưng lưu ý những điều sau:

Lỗi chính xác kép có thể tạo ra một bất ngờ cho bạn. Giả sử epsilon = 1.0e-6, sau đó 1.0 và 1.000001 KHÔNG nên được coi là bằng nhau theo mã trên, nhưng trên máy của tôi, hàm coi chúng là bằng nhau, điều này là do 1.000001 không thể được dịch chính xác sang định dạng nhị phân, nó có lẽ là 1.0000009xxx. Tôi kiểm tra nó với 1.0 và 1.0000011 và lần này tôi nhận được kết quả như mong đợi.


-1

Đây là một giải pháp khác với lambda:

#include <cmath>
#include <limits>

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

Điều này giống hệt như nhiều câu trả lời khác ngoại trừ đó là lambda và không có lời giải thích, vì vậy điều này không mang lại nhiều giá trị như một câu trả lời.
stijn

-2

Cách của tôi có thể không đúng nhưng hữu ích

Chuyển đổi cả float sang chuỗi và sau đó thực hiện so sánh chuỗi

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;

}

Việc ghi đè toán tử cũng có thể được thực hiện


+1: này, tôi sẽ không làm lập trình trò chơi với điều này, nhưng ý tưởng về những chiếc phao tròn đã xuất hiện nhiều lần trong blog của Bruce Dawson (chuyên luận ?: D) về vấn đề này và nếu bạn bị mắc kẹt một căn phòng và ai đó đặt một khẩu súng lên đầu bạn và nói "này, bạn phải so sánh hai chiếc phao với trong số X có ý nghĩa, bạn có 5 phút, ĐI!" đây có lẽ là một để xem xét ;)
Shelleybutoston

@shelleybutoston Sau đó, một lần nữa câu hỏi là cách hiệu quả nhất để so sánh hai số dấu phẩy động.
Tommy Andersen

@TommyA lol có lẽ, nhưng tôi cá là việc vấp ngã đã bị hạ thấp vì những lý do không liên quan đến hiệu quả. Mặc dù trực giác của tôi là nó sẽ không hiệu quả so với toán học fp CT nhưng cũng nói rằng các algoritihms trong phần mềm fp ít nhất không có sự khác biệt lớn nhất. Tôi háo hức chờ đợi phân tích mà bạn đã thể hiện mối quan tâm về hiệu quả trong trường hợp đó là rất đáng kể. Bên cạnh đó, đôi khi ít tối ưu hơn vẫn có thể là một câu trả lời có giá trị, và vì nó đã bị hạ thấp mặc dù là một kỹ thuật hợp lệ thậm chí được blog của Dawson đề cập đến về chủ đề này, vì vậy tôi nghĩ rằng nó xứng đáng được nâng cấp.
Shelleybutoston

-2

Bạn không thể so sánh hai double với một cố định EPSILON. Tùy thuộc vào giá trị của double, EPSILONkhác nhau.

Một so sánh kép tốt hơn sẽ là:

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

Nói một cách chung chung hơn:

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

4
Phương pháp này có nhiều điểm yếu, như nếu các con số abđã nhỏ hơn mức epsilon()chênh lệch có thể vẫn còn đáng kể. Ngược lại, nếu các số rất lớn thì thậm chí một vài bit lỗi sẽ khiến việc so sánh thất bại ngay cả khi bạn muốn các số đó được coi là bằng nhau. Câu trả lời này chính xác là loại thuật toán so sánh "chung chung" mà bạn muốn tránh.
SirGuy

-3

Tại sao không thực hiện XOR bitwise? Hai số dấu phẩy động bằng nhau nếu các bit tương ứng của chúng bằng nhau. Tôi nghĩ rằng, quyết định đặt các bit số mũ trước khi mantissa được đưa ra để tăng tốc độ so sánh hai phao. Tôi nghĩ rằng, nhiều câu trả lời ở đây đang thiếu điểm so sánh epsilon. Giá trị Epsilon chỉ phụ thuộc vào số lượng dấu phẩy động chính xác được so sánh. Ví dụ, sau khi thực hiện một số số học với số float, bạn nhận được hai số: 2.5642943554342 và 2.5642943554345. Chúng không bằng nhau, nhưng đối với giải pháp chỉ có 3 chữ số thập phân quan trọng nên chúng bằng nhau: 2.564 và 2.564. Trong trường hợp này, bạn chọn epsilon bằng 0,001. So sánh Epsilon cũng có thể với XOR bitwise. Đúng nếu tôi đã sai lầm.


Vui lòng không thêm cùng một câu trả lời cho nhiều câu hỏi. Trả lời câu hỏi hay nhất và đánh dấu phần còn lại là trùng lặp. Xem meta.stackexchange.com/questions/104227/ Cách
Bhargav Rao

Tôi không nghĩ rằng "so sánh epsilon" có thể chỉ sử dụng ExOr (và một hoặc hai so sánh), thậm chí bị hạn chế đối với các biểu diễn được chuẩn hóa trong cùng định dạng.
greybeard
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.