Apa cara paling efektif untuk perbandingan mengambang dan ganda?

524

Apa cara yang paling efisien untuk membandingkan dua doubleatau dua floatnilai?

Melakukan hal ini tidak benar:

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

Tetapi sesuatu seperti:

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

Tampaknya pengolahan limbah.

Adakah yang tahu komparator float yang lebih pintar?

Alex
sumber
2
> apakah akan lebih efisien untuk menambahkan ... di awal fungsi? <invoke Knuth>Optimalisasi prematur adalah akar dari semua kejahatan. </invoke Knuth>Pergilah dengan abs (ab) <EPS seperti disebutkan di atas, jelas dan mudah dimengerti.
Andrew Coleson
2
Ini dia cara penerapannya di 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
Satu-satunya hal yang tidak optimal tentang implementasi poster asli adalah bahwa ia berisi cabang tambahan di &&. Jawaban OJ optimal. fabs adalah intrinsik yang merupakan instruksi tunggal pada x87, dan saya kira pada hampir semua hal lain juga. Terima jawaban OJ!
3ya
3
Jika Anda bisa, jatuhkan titik mengambang dan gunakan titik tetap. Contoh, gunakan {titik tetap} milimeter alih-alih {titik mengambang} meter.
Thomas Matthews
33
"Cukup melakukan ini tidak benar" - Ini hanyalah sampah, tentu saja menggunakan ==bisa sepenuhnya benar, tetapi ini sepenuhnya tergantung pada konteks yang tidak diberikan dalam pertanyaan. Sampai konteks itu diketahui, ==tetaplah "cara paling efisien" .
Christian Rau

Jawaban:

459

Berhati-hatilah dalam menggunakan saran lainnya. Itu semua tergantung konteks.

Saya telah menghabiskan waktu yang lama untuk melacak bug dalam sistem yang mungkin a==bjika |a-b|<epsilon. Masalah yang mendasarinya adalah:

  1. Anggapan implisit dalam suatu algoritma bahwa jika a==bdan b==ckemudian a==c.

  2. Menggunakan epsilon yang sama untuk garis yang diukur dalam inci dan garis yang diukur dalam mils (0,001 inci). Itulah a==btapi 1000a!=1000b. (Inilah sebabnya AlmostEqual2sComplement meminta epsilon atau ULPS maks).

  3. Penggunaan epsilon yang sama untuk kosinus sudut dan panjang garis!

  4. Menggunakan fungsi perbandingan untuk mengurutkan item dalam koleksi. (Dalam hal ini menggunakan operator C ++ bawaan == untuk ganda menghasilkan hasil yang benar.)

Seperti saya katakan: semuanya tergantung pada konteks dan ukuran yang diharapkan adan b.

BTW, std::numeric_limits<double>::epsilon()adalah "mesin epsilon". Ini adalah perbedaan antara 1,0 dan nilai selanjutnya yang diwakili oleh suatu ganda. Saya kira itu bisa digunakan dalam fungsi bandingkan tetapi hanya jika nilai yang diharapkan kurang dari 1. (Ini sebagai respons terhadap jawaban @ cdv ...)

Juga, jika pada dasarnya Anda memiliki intaritmatika doubles( di sini kami menggunakan ganda untuk menyimpan nilai int dalam kasus-kasus tertentu) aritmatika Anda akan benar. Misalnya 4.0 / 2.0 akan sama dengan 1.0 + 1.0. Ini selama Anda tidak melakukan hal-hal yang menghasilkan pecahan (4.0 / 3.0) atau tidak melampaui ukuran int.

Andrew Stein
sumber
10
+1 untuk menunjukkan yang sudah jelas (yang sering diabaikan). Untuk metode generik, Anda dapat membuat epsilon relatif fabs(a)+fabs(b)tetapi dengan mengkompensasi NaN, 0 jumlah dan melimpah, ini menjadi sangat kompleks.
peterchen
4
Pasti ada sesuatu yang tidak saya mengerti. Khas float/ doubleadalah mantissa x 2 ^ EXP . epsilonakan tergantung pada eksponen. Sebagai contoh jika mantissa adalah 24bits dan eksponen ditandatangani 8bit, maka 1/(2^24)*2^127atau ~2^103adalah epsilonuntuk beberapa nilai; atau ini mengacu pada epsilon minimum ?
kebisingan seni
3
Tunggu sebentar. Apakah yang saya katakan adalah apa yang Anda maksudkan? Anda mengatakan mengapa |a-b|<epsilon, itu tidak benar. Harap tambahkan tautan ini ke jawaban Anda; jika Anda setuju cygnus-software.com/papers/comparingfloats/comparingfloats.htm dan saya dapat menghapus komentar bodoh saya.
kebisingan seni
3
Ini adalah komentar yang sangat panjang, bukan jawaban dalam dirinya sendiri. Apakah ada (set) jawaban kanonik untuk semua konteks?
Merlyn Morgan-Graham
2
Tautan lama tampaknya sudah usang, halaman baru ada di sini randomascii.wordpress.com/2012/02/25/…
Marson Mao
174

Perbandingan dengan nilai epsilon adalah apa yang dilakukan kebanyakan orang (bahkan dalam pemrograman game).

Anda harus mengubah implementasi Anda sedikit:

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

Sunting: Christer telah menambahkan setumpuk info hebat tentang topik ini pada posting blog terbaru . Nikmati.

OJ.
sumber
@ OJ: apakah ada yang salah dengan contoh kode pertama? Saya pikir satu-satunya masalah adalah dalam situasi seperti ini: float a = 3.4; if(a == 3.4){...}yaitu ketika Anda membandingkan floating point yang disimpan dengan | literal Dalam hal ini, kedua nomor disimpan, sehingga mereka akan memiliki representasi yang sama, jika sama, jadi apa salahnya melakukan a == b?
Lazer
11
@ DonReba: Hanya jika EPSILONdidefinisikan sebagai DBL_EPSILON. Biasanya itu akan menjadi nilai spesifik yang dipilih tergantung pada keakuratan perbandingan yang diperlukan.
Nemo157
7
EPSILONperbandingan tidak berfungsi ketika mengapung besar, karena perbedaan antara mengapung berturut-turut juga menjadi besar. Lihat artikel ini .
kevintodisco
22
Tidak heran ada pertempuran Z di beberapa game ketika tekstur / objek jauh berkedip, seperti di Battlefield 4. Membandingkan perbedaan dengan EPSILONcukup banyak tidak berguna. Anda perlu membandingkan dengan ambang batas yang masuk akal untuk unit yang ada. Juga, gunakan std::abskarena kelebihan beban untuk tipe floating point yang berbeda.
Maxim Egorushkin
11
Saya downvoted karena kode contoh menunjukkan tip bug khas diulangi oleh mayoritas programmer. Floating point selalu tentang kesalahan relatif, karena itu adalah floating point (bukan fixed point). Jadi itu tidak akan pernah berfungsi dengan benar dengan kesalahan tetap (epsilon).
user2261015
115

Saya menemukan bahwa Kerangka Pengujian Google C ++ berisi implementasi templat berbasis-platform bagus dari AlmostEqual2sComplement yang bekerja pada doubles dan float. Mengingat bahwa itu dirilis di bawah lisensi BSD, menggunakannya dalam kode Anda sendiri seharusnya tidak menjadi masalah, selama Anda mempertahankan lisensi tersebut. Saya mengekstrak kode di bawah ini dari 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 dan menambahkan lisensi di atas.

Pastikan untuk #define GTEST_OS_WINDOWS ke beberapa nilai (atau untuk mengubah kode di mana ia digunakan untuk sesuatu yang sesuai dengan basis kode Anda - itu BSD dilisensikan setelah semua).

Contoh penggunaan:

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

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

Berikut kodenya:

// 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: [email protected] (Zhanyong Wan), [email protected] (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: Posting ini berumur 4 tahun. Mungkin masih valid, dan kodenya bagus, tetapi beberapa orang menemukan peningkatan. Best go dapatkan versi terbaru AlmostEqualslangsung dari kode sumber Google Test, dan bukan yang saya tempel di sini.

skrebbel
sumber
3
+1: Saya setuju ini benar. Namun, itu tidak menjelaskan alasannya. Lihat di sini: cygnus-software.com/papers/comparingfloats/comparingfloats.htm Saya membaca posting blog ini setelah saya menulis komentar saya di skor teratas di sini; Saya percaya itu mengatakan hal yang sama dan memberikan rasional / solusi yang diterapkan di atas. Karena ada begitu banyak kode, orang akan kehilangan jawabannya.
kebisingan seni
Ada beberapa hal buruk yang dapat terjadi ketika gips implisit terjadi katakanlah FloatPoint <double> fp (0.03f). Saya membuat beberapa modifikasi untuk mencegah hal ini. template <typename U> eksplisit FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "Anda sedang melakukan konversi implisit dengan FloatingPoint, Don't "<< std :: endl; assert (typeid (U) .name () == typeid (RawType) .name ()); } u_.value_ = x; }
JeffCharter
2
Bagus temukan! Saya kira akan lebih baik untuk berkontribusi mereka ke Google Test, di mana kode ini dicuri. Saya akan memperbarui pos untuk mencerminkan bahwa mungkin ada versi yang lebih baru. Jika orang-orang Google bertindak gatal, dapatkah Anda memasukkannya, misalnya, inti GitHub? Saya akan menautkan itu juga.
skrebbel
3
Untuk cuplikan kode terbaru, lihat di sini dan di sini .
Jaege
1
Saya telah mengekstrak baris yang diperlukan ke file inti. Siapa pun dapat mencapai dari sini .
Yusuf Tarık Günaydın
111

Membandingkan angka floating point tergantung pada konteksnya. Karena bahkan mengubah urutan operasi dapat menghasilkan hasil yang berbeda, penting untuk mengetahui seberapa "sama" Anda inginkan angka-angkanya.

Membandingkan angka floating point oleh Bruce Dawson adalah tempat yang baik untuk memulai ketika melihat perbandingan floating point.

Definisi-definisi berikut berasal dari Seni pemrograman komputer oleh 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);
}

Tentu saja, memilih epsilon tergantung pada konteksnya, dan menentukan seberapa setara angka yang Anda inginkan.

Metode lain untuk membandingkan angka floating point adalah dengan melihat ULP (unit di tempat terakhir) dari angka-angka tersebut. Meskipun tidak berurusan secara khusus dengan perbandingan, makalah Apa yang harus diketahui oleh setiap ilmuwan komputer tentang angka floating point adalah sumber yang bagus untuk memahami bagaimana floating point bekerja dan apa jebakannya, termasuk apa ULP itu.

mch
sumber
1
terima kasih telah memposting cara menentukan nomor mana yang lebih kecil / lebih besar!
Tomat
1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);menyelamatkan hidupku. LOL Perhatikan bahwa versi ini (saya belum memeriksa apakah berlaku untuk yang lain juga) juga mempertimbangkan perubahan yang mungkin terjadi di bagian integral dari angka floating point (contoh: di 2147352577.9999997616 == 2147352576.0000000000mana Anda dapat dengan jelas melihat bahwa hampir ada perbedaan dari 2antara dua angka) yang cukup bagus! Ini terjadi ketika kesalahan pembulatan yang diakumulasikan melebihi bagian desimal dari angka tersebut.
rbaleksandar
Artikel yang sangat bagus dan bermanfaat oleh Bruce Dawson, terima kasih!
BobMorane
2
Mengingat bahwa pertanyaan ini ditandai C ++, cek Anda akan lebih mudah dibaca ditulis sebagai std::max(std::abs(a), std::abs(b))(atau dengan std::min()); std::absdi C ++ kelebihan beban dengan float & double type, jadi berfungsi dengan baik (Anda selalu bisa menjaga fabsketerbacaannya).
Razakhel
1
Ternyata masalahnya ada di kode saya, perbedaan antara nilai yang diharapkan asli dan string diurai.
mwpowellhtx
47

Untuk pendekatan yang lebih mendalam, baca Membandingkan angka floating point . Berikut ini cuplikan kode dari tautan itu:

// 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;    
}
grom
sumber
14
Apa nilai yang disarankan dari maxUlps?
unj2
6
Akan " *(int*)&A;" melanggar aturan aliasing yang ketat?
osgx
3
Menurut gtest (mencari ULP), 4 adalah angka yang dapat diterima.
Mei Oakes
4
Dan berikut adalah beberapa pembaruan untuk makalah Bruce Dawson (salah satunya terhubung dalam intro makalah): randomascii.wordpress.com/2012/02/25/… dan randomascii.wordpress.com/2012/06/26/…
Michael Burr
3
Butuh beberapa saat untuk mencari tahu apa yang ada di ULP: Unit di Tempat Terakhir
JeffCharter
27

Menyadari ini adalah utas lama tetapi artikel ini adalah salah satu yang paling lurus ke depan yang saya temukan dalam membandingkan angka floating point dan jika Anda ingin menjelajahi lebih banyak ia memiliki referensi yang lebih rinci juga dan itu situs utama mencakup berbagai masalah lengkap berurusan dengan angka floating point Panduan Floating-Point: Perbandingan .

Kita dapat menemukan artikel yang agak lebih praktis dalam toleransi titik Teraput ditinjau kembali dan mencatat ada tes toleransi absolut , yang bermuara pada ini di C ++:

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

dan uji toleransi relatif :

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

Artikel tersebut mencatat bahwa tes absolut gagal ketika xdan ybesar dan gagal dalam kasus relatif ketika mereka kecil. Dengan asumsi ia mutlak dan relatif toleran sama tes gabungan akan terlihat seperti ini:

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 ;
}
Shafik Yaghmour
sumber
25

Cara portabel untuk mendapatkan epsilon di C ++ adalah

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

Maka fungsi perbandingan menjadi

#include <cmath>
#include <limits>

bool AreSame(double a, double b) {
    return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}
Chris de Vries
sumber
34
Anda mungkin menginginkan kelipatan epsilon itu.
user7116
11
Tidak bisakah Anda menggunakan std :: abs? AFAIK, std :: abs kelebihan beban untuk ganda juga. Tolong peringatkan saya jika saya salah.
kolistivra
3
@ Kolistivra, Anda salah. The 'f' in 'fabs' tidak berarti tipe float. Anda mungkin berpikir tentang fungsi C fabsf () dan fabsl ().
jcoffland
9
Sebenarnya karena alasan yang diuraikan dalam artikel Bruce perubahan epsilon sebagai nilai floating point semakin besar. Lihat bagian di mana ia berkata, "Untuk angka yang lebih besar dari 2,0, kesenjangan antara pelampung bertambah besar dan jika Anda membandingkan pelampung menggunakan FLT_EPSILON, maka Anda hanya melakukan pemeriksaan kesetaraan yang lebih mahal dan tidak terlalu jelas."
bobobobo
5
saya tahu ini sudah tua tapi std :: abs kelebihan beban untuk tipe floating point di cmath.
mholzmann
18

Saya akhirnya menghabiskan cukup banyak waktu untuk menelusuri materi di utas hebat ini. Saya ragu semua orang ingin menghabiskan begitu banyak waktu sehingga saya akan menyoroti ringkasan dari apa yang saya pelajari dan solusi yang saya terapkan.

Ringkasan Cepat

  1. Apakah 1e-8 kira-kira sama dengan 1e-16? Jika Anda melihat data sensor bising maka mungkin ya tetapi jika Anda melakukan simulasi molekuler maka mungkin tidak! Intinya: Anda selalu perlu memikirkan nilai toleransi dalam konteks pemanggilan fungsi khusus dan tidak hanya membuatnya konstan-kode generik seluruh aplikasi.
  2. Untuk fungsi perpustakaan umum, masih bagus untuk memiliki parameter dengan toleransi default . Pilihan tipikal adalah numeric_limits::epsilon()yang sama dengan FLT_EPSILON di float.h. Namun ini bermasalah karena epsilon untuk membandingkan nilai seperti 1.0 tidak sama dengan epsilon untuk nilai seperti 1E9. FLT_EPSILON didefinisikan untuk 1.0.
  3. Implementasi yang jelas untuk memeriksa apakah angka dalam toleransi adalah fabs(a-b) <= epsilonnamun ini tidak berfungsi karena default epsilon didefinisikan untuk 1.0. Kita perlu meningkatkan epsilon ke atas atau ke bawah dalam hal a dan b.
  4. Ada dua solusi untuk masalah ini: Anda menetapkan epsilon proporsional max(a,b)atau Anda bisa mendapatkan angka representatif berikutnya di sekitar a dan kemudian melihat apakah b termasuk dalam kisaran itu. Yang pertama disebut metode "relatif" dan kemudian disebut metode ULP.
  5. Kedua metode ini sebenarnya gagal ketika membandingkan dengan 0. Dalam hal ini, aplikasi harus memberikan toleransi yang benar.

Implementasi Fungsi Utilitas (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;
}
Shital Shah
sumber
isDefinitelyLessThancek diff < tolerance, yang berarti a dan b hampir sama (dan jadi tidak pasti kurang dari b). Bukankah lebih masuk akal untuk memeriksa toleransi yang berbeda di kedua kasus? Atau mungkin menambahkan orEqualToargumen yang mengontrol apakah perkiraan pemeriksaan kesetaraan harus kembali benar atau tidak.
Matt Chambers
14

Kode yang Anda tulis disadap:

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

Kode yang benar adalah:

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

(... dan ya ini berbeda)

Saya ingin tahu apakah hebat tidak akan membuat Anda kehilangan evaluasi malas dalam beberapa kasus. Saya akan mengatakan itu tergantung pada kompiler. Anda mungkin ingin mencoba keduanya. Jika rata-rata setara, ambil implementasinya dengan fab.

Jika Anda memiliki beberapa info yang mana dari kedua float lebih cenderung lebih besar daripada yang lain, Anda dapat bermain di urutan perbandingan untuk mengambil keuntungan lebih baik dari evaluasi malas.

Akhirnya, Anda mungkin mendapatkan hasil yang lebih baik dengan menguraikan fungsi ini. Tidak mungkin untuk meningkatkan banyak ...

Sunting: OJ, terima kasih telah memperbaiki kode Anda. Saya menghapus komentar saya sesuai

fulmicoton
sumber
13

`return fabs (a - b) <EPSILON;

Ini bagus jika:

  • urutan besarnya input Anda tidak banyak berubah
  • jumlah yang sangat kecil dari tanda yang berlawanan dapat diperlakukan sama

Tetapi sebaliknya itu akan membawa Anda ke dalam masalah. Angka presisi ganda memiliki resolusi sekitar 16 desimal. Jika dua angka yang Anda bandingkan besarnya lebih besar daripada EPSILON * 1.0E16, maka Anda mungkin juga mengatakan:

return a==b;

Saya akan memeriksa pendekatan yang berbeda yang menganggap Anda perlu khawatir tentang masalah pertama dan menganggap yang kedua baik-baik saja aplikasi Anda. Sebuah solusi akan menjadi seperti:

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

Ini mahal secara komputasi, tetapi kadang-kadang itulah yang disebut. Inilah yang harus kita lakukan di perusahaan saya karena kita berurusan dengan perpustakaan teknik dan input dapat bervariasi dengan beberapa lusin pesanan besar.

Pokoknya, intinya adalah ini (dan berlaku untuk hampir semua masalah pemrograman): Mengevaluasi apa kebutuhan Anda, kemudian datang dengan solusi untuk memenuhi kebutuhan Anda - jangan menganggap jawaban mudah akan memenuhi kebutuhan Anda. Jika setelah evaluasi Anda menemukan itu fabs(a-b) < EPSILONsudah cukup, sempurna - gunakan! Namun waspadai kekurangannya dan kemungkinan solusi lainnya juga.


sumber
3
Selain dari kesalahan ketik (s / - /, / hilang koma di fmax ()), implementasi ini memiliki bug untuk angka mendekati nol yang berada di dalam EPSILON, tetapi belum cukup SANGAT SANGAT. Misalnya, AreSame (1.0E-10, 1.0E-9) melaporkan false karena kesalahan relatif sangat besar. Anda bisa menjadi pahlawan di perusahaan Anda.
brlcad
1
@ brlcad Anda tidak mendapatkan titik floating point. 1.0E-10 dan 1.0E-9 berbeda dengan besarnya 10. Jadi memang benar bahwa keduanya tidak sama. floating point selalu tentang kesalahan relatif . Jika Anda memiliki sistem yang menganggap 1.0E-10 dan 1.0E-9 hampir sama, karena keduanya "hampir mendekati nol" (yang terdengar masuk akal bagi manusia tetapi tidak ada artinya secara matematis), maka EPSILON perlu disesuaikan sesuai kebutuhan. untuk sistem seperti itu.
user2261015
8

Seperti yang telah ditunjukkan orang lain, menggunakan epsilon eksponen tetap (seperti 0,0000001) tidak akan berguna untuk nilai yang jauh dari nilai epsilon. Sebagai contoh, jika dua nilai Anda adalah 10.000.000977 dan 10000, maka ada nilai floating-point NO 32-bit antara dua angka ini - 10000 dan 10000.000977 sedekat yang Anda bisa dapatkan tanpa bit-for-bit identik. Di sini, epsilon kurang dari 0,0009 tidak ada artinya; Anda mungkin juga menggunakan operator kesetaraan lurus.

Demikian juga, ketika kedua nilai mendekati ukuran epsilon, kesalahan relatif tumbuh hingga 100%.

Dengan demikian, mencoba untuk mencampur nomor titik tetap seperti 0,00001 dengan nilai-nilai floating-point (di mana eksponen sewenang-wenang) adalah latihan sia-sia. Ini hanya akan bekerja jika Anda dapat yakin bahwa nilai operan terletak di dalam domain sempit (yaitu, dekat dengan beberapa eksponen tertentu), dan jika Anda memilih nilai epsilon dengan benar untuk tes tertentu. Jika Anda mengeluarkan angka dari udara ("Hei! 0,00001 kecil, jadi pasti bagus!"), Anda akan mengalami kesalahan numerik. Saya telah menghabiskan banyak waktu men-debug kode numerik yang buruk di mana beberapa bodoh bodoh melemparkan nilai-nilai epsilon acak untuk membuat lagi kasus uji bekerja.

Jika Anda melakukan pemrograman numerik dalam bentuk apa pun dan yakin Anda perlu meraih epsil titik tetap, BACA PASAL BRUCE PADA PERBANDINGAN NOMOR TITIK .

Membandingkan Nomor Floating Point

Steve Hollasch
sumber
5

Qt mengimplementasikan dua fungsi, mungkin Anda bisa belajar darinya:

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

Dan Anda mungkin perlu fungsi-fungsi berikut, karena

Perhatikan bahwa membandingkan nilai di mana p1 atau p2 adalah 0,0 tidak akan berfungsi, juga tidak membandingkan nilai di mana salah satu nilai adalah NaN atau infinity. Jika salah satu nilai selalu 0,0, gunakan qFuzzyIsNull sebagai gantinya. Jika salah satu nilai cenderung 0,0, satu solusi adalah menambahkan 1,0 ke kedua nilai.

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

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

Perbandingan tujuan umum angka floating-point umumnya tidak ada artinya. Cara membandingkan sangat tergantung pada masalah yang dihadapi. Dalam banyak masalah, angka cukup didiskritisasi untuk memungkinkan membandingkannya dalam toleransi yang diberikan. Sayangnya, ada banyak masalah, di mana trik seperti itu tidak berhasil. Sebagai satu contoh, pertimbangkan bekerja dengan fungsi (langkah) Heaviside dari sejumlah pertanyaan (opsi stok digital muncul di benak) ketika pengamatan Anda sangat dekat dengan penghalang. Melakukan perbandingan berbasis toleransi tidak akan banyak gunanya, karena secara efektif akan mengubah masalah dari penghalang asli ke dua yang baru. Sekali lagi, tidak ada solusi tujuan umum untuk masalah-masalah seperti itu dan solusi khusus mungkin perlu sejauh mengubah metode numerik untuk mencapai stabilitas.


sumber
3

Sayangnya, bahkan kode "boros" Anda salah. EPSILON adalah nilai terkecil yang dapat ditambahkan ke 1.0 dan mengubah nilainya. Nilai 1.0 sangat penting - angka yang lebih besar tidak berubah ketika ditambahkan ke EPSILON. Sekarang, Anda dapat mengatur nilai ini ke angka yang Anda bandingkan untuk mengetahui apakah mereka berbeda atau tidak. Ekspresi yang benar untuk membandingkan dua ganda adalah:

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

Ini minimal. Namun, secara umum, Anda ingin memperhitungkan kebisingan dalam perhitungan Anda dan mengabaikan beberapa bit yang paling tidak signifikan, sehingga perbandingan yang lebih realistis akan terlihat seperti:

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

Jika kinerja perbandingan sangat penting bagi Anda dan Anda tahu rentang nilai Anda, maka Anda harus menggunakan angka titik tetap.

Don Reba
sumber
2
“EPSILON adalah nilai terkecil yang dapat ditambahkan ke 1.0 dan mengubah nilainya”: Sebenarnya, kehormatan ini jatuh pada penerus 0,5 * EPSILON (dalam mode standar round-to-terdekat). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq
Menurut Anda mengapa EPSILONdalam pertanyaannya adalah DBL_EPSILONatau FLT_EPSILON? Masalahnya adalah dalam imajinasi Anda sendiri, di mana Anda mengganti DBL_EPSILON(yang memang akan menjadi pilihan yang salah) menjadi kode yang tidak menggunakannya.
Ben Voigt
@ BenVoigt, Anda benar, itu ada sesuatu di pikiran saya saat itu, dan saya menafsirkan pertanyaan itu dengan terang.
Don Reba
2

Kelas saya berdasarkan jawaban yang diposting sebelumnya. Sangat mirip dengan kode Google tetapi saya menggunakan bias yang mendorong semua nilai NaN di atas 0xFF000000. Itu memungkinkan pemeriksaan yang lebih cepat untuk NaN.

Kode ini dimaksudkan untuk menunjukkan konsep, bukan menjadi solusi umum. Kode Google sudah menunjukkan cara menghitung semua nilai spesifik platform dan saya tidak ingin menduplikasi semua itu. Saya telah melakukan pengujian terbatas pada kode ini.

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 );
                  }
};
WaterbugDesign
sumber
2

Inilah bukti bahwa menggunakan std::numeric_limits::epsilon()bukanlah jawabannya - gagal untuk nilai yang lebih dari satu:

Bukti komentar saya di atas:

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

Menjalankan menghasilkan output ini:

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

Perhatikan bahwa dalam kasus kedua (satu dan hanya lebih besar dari satu), dua nilai input sedekat mungkin, dan masih membandingkan sebagai tidak dekat. Dengan demikian, untuk nilai yang lebih besar dari 1,0, Anda mungkin juga menggunakan tes kesetaraan. Epsilon yang diperbaiki tidak akan menyelamatkan Anda saat membandingkan nilai floating-point.

Steve Hollasch
sumber
Saya percaya return *(reinterpret_cast<double*>(&x));meskipun biasanya berhasil, sebenarnya perilaku tidak terdefinisi.
Jaap Versteegh
Poin yang adil, meskipun kode ini ilustratif, sehingga cukup untuk menunjukkan masalah numeric_limits<>::epsilondan titik lantai IEEE 754.
Steve Hollasch
Juga poin yang adil, tetapi tidak bijaksana untuk memposting di stack overflow mengharapkan wawasan semacam itu. Kode tersebut akan disalin secara membabi buta sehingga semakin sulit untuk memberantas pola yang sangat umum ini - bersama dengan trik serikat pekerja - yang seharusnya dihindari seperti semua UD seharusnya.
Jaap Versteegh
1

Menemukan implementasi lain yang menarik di: 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";
}
Prashant Nidgunde
sumber
0

Saya akan sangat waspada terhadap salah satu jawaban ini yang melibatkan pengurangan floating point (mis., Fabs (ab) <epsilon). Pertama, angka floating point menjadi lebih jarang pada magnitudo lebih besar dan pada magnitudo cukup tinggi di mana jarak lebih besar dari epsilon, Anda mungkin juga melakukan a == b. Kedua, mengurangi dua angka floating point yang sangat dekat (karena ini akan cenderung, mengingat bahwa Anda sedang mencari kesetaraan dekat) adalah persis bagaimana Anda mendapatkan pembatalan bencana .

Meskipun tidak portabel, saya pikir jawaban grom melakukan pekerjaan terbaik untuk menghindari masalah ini.

Boojum
sumber
1
+1 untuk informasi yang baik. Namun, saya gagal melihat bagaimana Anda dapat mengacaukan perbandingan kesetaraan dengan meningkatkan kesalahan relatif; IMHO kesalahan menjadi signifikan hanya dalam hasil pengurangan, namun urutan besarnya relatif terhadap dua operan yang dikurangkan masih harus cukup andal untuk menilai kesetaraan. Kecuali jika resolusi perlu lebih tinggi secara keseluruhan, tetapi dalam hal ini satu-satunya solusi adalah pindah ke representasi floating point dengan bit yang lebih signifikan di mantissa.
lihat
Mengurangi dua angka yang hampir sama TIDAK menyebabkan pembatalan besar - pada kenyataannya, itu tidak menyebabkan kesalahan sama sekali (teorema Sterbenz). Pembatalan katastropik terjadi lebih awal, selama perhitungan adan bsendiri. Sama sekali tidak ada masalah dengan menggunakan pengurangan floating point sebagai bagian dari perbandingan fuzzy (meskipun seperti yang orang lain katakan, nilai epsilon absolut mungkin atau mungkin tidak sesuai untuk kasus penggunaan tertentu.)
Sneftel
0

Sebenarnya ada kasus dalam perangkat lunak numerik di mana Anda ingin memeriksa apakah dua angka floating point persis sama. Saya memposting ini pada pertanyaan serupa

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

Jadi Anda tidak bisa mengatakan bahwa "CompareDoubles1" salah secara umum.

Michael Lehn
sumber
Sebenarnya referensi yang sangat solid untuk jawaban yang baik, meskipun sangat khusus untuk membatasi siapa pun tanpa komputasi ilmiah atau latar belakang analisis numerik (yaitu LAPACK, BLAS) untuk tidak memahami kelengkapannya. Atau dengan kata lain, itu mengasumsikan Anda telah membaca sesuatu seperti pengantar Resep Numerik atau Analisis Numerik oleh Burden & Faires.
mctylr
0

Itu tergantung pada seberapa tepat perbandingan yang Anda inginkan. Jika Anda ingin membandingkan dengan angka yang persis sama, maka cukup dengan ==. (Anda hampir tidak pernah ingin melakukan ini kecuali Anda benar-benar menginginkan nomor yang sama persis.) Pada platform yang layak Anda juga dapat melakukan hal berikut:

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

karena fabscenderung cukup cepat. Maksud saya cukup cepat pada dasarnya adalah bitwise AND, jadi lebih baik cepat.

Dan trik integer untuk membandingkan ganda dan float bagus tetapi cenderung membuatnya lebih sulit untuk menangani berbagai pipa CPU secara efektif. Dan tentu saja ini tidak lebih cepat pada arsitektur pesanan tertentu karena menggunakan stack sebagai area penyimpanan sementara untuk nilai-nilai yang sering digunakan. (Load-hit-store untuk mereka yang peduli.)

Mat Noguchi
sumber
0

Dalam hal skala jumlah:

Jika epsilonsebagian kecil dari besaran kuantitas (yaitu nilai relatif) dalam beberapa pengertian Adan Bjenis fisik tertentu dapat dibandingkan dalam arti yang sama, daripada yang saya pikir, bahwa yang berikut ini cukup benar:

#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;
}
Tomilov Anatoliy
sumber
0

Saya menggunakan kode ini:

bool AlmostEqual(double v1, double v2)
    {
        return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
    }
Chunde Huang
sumber
2
Bukan itu untuk apa epsilon.
Sneftel
1
Kenapa tidak? Bisakah Anda menjelaskannya?
debuti
2
@debuti epsilonhanyalah jarak antara 1 dan angka representatif berikutnya setelah 1. Paling-paling, kode itu hanya mencoba untuk memeriksa apakah kedua angka itu persis sama satu sama lain, tetapi karena non-kekuatan 2 sedang dikalikan dengan epsilon, itu bahkan tidak melakukan itu dengan benar.
Sneftel
2
Oh, dan std::fabs(std::min(v1, v2))tidak benar - untuk input negatif memilih input yang besarnya lebih besar.
Sneftel
0

Saya menulis ini untuk java, tetapi mungkin Anda merasa berguna. Ia menggunakan long bukannya double, tetapi merawat NaN, subnormal, dll.

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

Ingatlah bahwa setelah beberapa operasi floating-point, angka bisa sangat berbeda dari yang kita harapkan. Tidak ada kode untuk memperbaikinya.

Bunglon
sumber
0

Bagaimana dengan ini?

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

Saya telah melihat berbagai pendekatan - tetapi tidak pernah melihat ini, jadi saya penasaran ingin mendengar komentar juga!

derke
sumber
Ini tidak berfungsi untuk 1.99999999 dan 1.99999998
Mehdi
@Mehdi Saya baru saja mencoba dengan repl.it/repls/SvelteSimpleNumerator#main.cpp dan tampaknya berperilaku seperti yang diharapkan - tetapi mungkin Anda memiliki implementasi kompiler tertentu untuk referensi yang tidak melakukan ini?
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;
}

Saya menggunakan fungsi ini untuk proyek kecil saya dan berfungsi, tetapi perhatikan hal berikut:

Kesalahan presisi ganda dapat membuat kejutan bagi Anda. Katakanlah epsilon = 1.0e-6, maka 1.0 dan 1.000001 TIDAK boleh dianggap sama dengan kode di atas, tetapi pada mesin saya fungsinya menganggap mereka sama, ini karena 1.000001 tidak dapat secara tepat diterjemahkan ke format biner, mungkin 1,0000009xxx. Saya mengujinya dengan 1.0 dan 1.0000011 dan kali ini saya mendapatkan hasil yang diharapkan.

Murphy78
sumber
-1

Ini adalah solusi lain dengan lambda:

#include <cmath>
#include <limits>

auto Compare = [](float a, float b, float epsilon = std::numeric_limits<float>::epsilon()){ return (std::fabs(a - b) <= epsilon); };
Amir Saniyan
sumber
Ini persis sama dengan banyak jawaban lain kecuali bahwa itu adalah lambda dan tidak memiliki penjelasan, jadi ini tidak menambah banyak nilai sebagai jawaban.
stijn
-2

Cara saya mungkin tidak benar tetapi bermanfaat

Konversikan kedua float menjadi string lalu lakukan perbandingan string

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;

}

overlay operator juga bisa dilakukan

Vijay
sumber
+1: hei, saya tidak akan melakukan pemrograman game dengan ini, tetapi gagasan float trip-tripping muncul beberapa kali di blog Bruce Dawson (risalah?: D) tentang masalah ini, dan jika Anda terjebak dalam sebuah ruangan dan seseorang menodongkan pistol ke kepala Anda dan berkata "hei Anda harus membandingkan dua pelampung ke dalam angka-angka penting X, Anda memiliki 5 menit, GO!" ini mungkin satu untuk dipertimbangkan. ;)
shelleybutterfly
@shelleybutterfly Kemudian pertanyaannya adalah cara paling efisien untuk membandingkan dua angka floating point.
Tommy Andersen
@TommyA lol mungkin, tapi saya bertaruh round-tripping diturunkan karena alasan yang tidak terkait efisiensi. Meskipun intuisi saya adalah bahwa itu akan menjadi agak tidak efisien dibandingkan dengan HW fp matematika tetapi juga mengatakan bahwa algoritma dalam perangkat lunak fp tidak mungkin memiliki perbedaan bigO setidaknya. Dengan penuh semangat saya menunggu analisis yang Anda lakukan menunjukkan masalah efisiensi dalam kasus itu penting. Selain itu, kadang-kadang kurang optimal masih bisa menjadi jawaban yang berharga, dan karena itu diturunkan - meskipun merupakan teknik yang valid yang bahkan disebutkan oleh blog Dawson tentang masalah ini, jadi saya pikir itu pantas mendapat upvote.
shelleybutterfly
-2

Anda tidak dapat membandingkan dua doubledengan yang diperbaiki EPSILON. Tergantung pada nilai double, EPSILONbervariasi.

Perbandingan ganda yang lebih baik adalah:

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;
}
Daniel Laügt
sumber
-2

Dengan cara yang lebih umum:

template <typename T>
bool compareNumber(const T& a, const T& b) {
    return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}
beroso
sumber
4
Metode ini memiliki banyak kelemahan, seperti jika angkanya adan bsudah lebih kecil dari epsilon()sana perbedaannya mungkin masih signifikan. Sebaliknya jika angkanya sangat besar maka bahkan beberapa bit kesalahan akan membuat perbandingan gagal bahkan jika Anda ingin angkanya dianggap sama. Jawaban ini persis jenis algoritma perbandingan "generik" yang ingin Anda hindari.
SirGuy
-3

Mengapa tidak melakukan XOR bitwise? Dua angka floating point sama jika bit yang sesuai sama. Saya pikir, keputusan untuk menempatkan bit eksponen sebelum mantissa dibuat untuk mempercepat perbandingan dua pelampung. Saya pikir, banyak jawaban di sini tidak memiliki titik perbandingan epsilon. Nilai Epsilon hanya tergantung pada angka presisi floating point mana yang dibandingkan. Misalnya, setelah melakukan beberapa aritmatika dengan pelampung Anda mendapatkan dua angka: 2.5642943554342 dan 2.5642943554345. Mereka tidak sama, tetapi untuk solusinya hanya 3 angka desimal sehingga mereka sama: 2.564 dan 2.564. Dalam hal ini Anda memilih epsilon sama dengan 0,001. Perbandingan Epsilon juga dimungkinkan dengan bitor XOR. Koreksi saya jika saya salah.

Pengiriman Radio
sumber
Tolong jangan tambahkan jawaban yang sama untuk beberapa pertanyaan. Jawab yang terbaik dan tandai sisanya sebagai duplikat. Lihat meta.stackexchange.com/questions/104227/...
Bhargav Rao
Saya tidak berpikir "perbandingan epsilon" mungkin menggunakan hanya ExOr (dan satu atau dua perbandingan), bahkan terbatas pada representasi yang dinormalisasi dalam format yang sama.
greybeard