Şamandıra ve çift karşılaştırmanın en etkili yolu nedir?


524

İki doubleveya ikisini karşılaştırmanın en etkili yolu ne olurdufloat değeri nedir?

Bunu yapmak doğru değil:

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

Ama şöyle bir şey:

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

Atık işleme gibi görünüyor.

Daha akıllı şamandıra karşılaştırıcısı bilen var mı?


2
> işlevin başına ... eklemek daha verimli olur mu? <invoke Knuth>Erken optimizasyon tüm kötülüklerin köküdür. </invoke Knuth>Sadece yukarıda belirtildiği gibi abs (ab) <EPS ile gidin, anlaşılır ve anlaşılması kolaydır.
Andrew Coleson


2
Orijinal posterin uygulanmasıyla ilgili uygunsuz olan tek şey, && adresinde fazladan bir şube içermesidir. OJ'nin cevabı en uygunudur. fabs x87 üzerinde tek bir talimat olan bir gerçek olduğunu ve hemen hemen her şey de varsayalım. OJ'nin cevabını zaten kabul et!
3yE

3
Yapabiliyorsanız, kayan noktayı düşürün ve sabit noktaları kullanın. Örnek olarak, {kayan nokta} metre yerine {sabit nokta} milimetre kullanın.
Thomas Matthews

33
"Sadece bunu yapmak doğru değildir" - Bu sadece saçmalıktır, elbette kullanmak ==tamamen doğru olabilir, ancak bu tamamen soruda belirtilmeyen bağlama bağlıdır. Bu bağlam bilinene kadar ==hala "en etkili yol" dur .
Christian Rau

Yanıtlar:


459

Diğer önerilerden herhangi birini kullanırken son derece dikkatli olun. Her şey bağlama bağlıdır.

Ben tahmin bir sistemde bir hata izleme uzun zaman geçirdim a==beğer |a-b|<epsilon. Temel sorunlar:

  1. Bir algoritmada örtük varsayım, eğer a==bve b==csonra a==c.

  2. İnç olarak ölçülen çizgiler ve mil (.001 inç) olarak ölçülen çizgiler için aynı epsilon kullanılarak. Öyle a==bama 1000a!=1000b. (Bu yüzden AlmostEqual2sComplement epsilon veya max ULPS ister).

  3. Hem açıların kosinüsü hem de çizgilerin uzunluğu için aynı epsilon kullanımı!

  4. Bir koleksiyondaki öğeleri sıralamak için böyle bir karşılaştırma işlevi kullanma. (Bu durumda çiftler için yerleşik C ++ operatörü == kullanmak doğru sonuçlar üretti.)

Dediğim gibi: tüm bağlam ve beklenen büyüklüğüne bağlıdır ave b.

BTW, std::numeric_limits<double>::epsilon() "makine epsilon" dur. 1.0 ile bir çiftin temsil edebileceği bir sonraki değer arasındaki farktır. Karşılaştırma işlevinde kullanılabileceğini tahmin ediyorum ama sadece beklenen değerler 1'den küçükse. (Bu @ cdv'nin cevabına karşılık ...)

Ayrıca, temelde intaritmetiğiniz varsa doubles(burada bazı durumlarda int değerlerini tutmak için çiftler kullanıyoruz) aritmetiğiniz doğru olacaktır. Örneğin 4.0 / 2.0, 1.0 + 1.0 ile aynı olacaktır. Bu, kesirler (4.0 / 3.0) ile sonuçlanan veya bir int boyutunun dışına çıkmayan şeyler yapmadığınız sürece.


10
Açık olanı işaret etmek için +1 (genellikle göz ardı edilir). Genel bir yöntem için, epsilonu göreceli yapabilirsiniz fabs(a)+fabs(b)ancak NaN, 0 toplamı ve taşma dengelemesi ile bu oldukça karmaşık hale gelir.
peterchen

4
Anlamadığım bir şey olmalı. Tipik float/ doubleolup Mantissa x 2 ^ EXP . epsilonüsse bağlı olacaktır. Örneğin, eğer mantis 24bits ve üstel 8bit imzalanır, o zaman 1/(2^24)*2^127ya da ~2^103bir olduğu epsilonbazı değerler için; ya da bu minimum bir epsilon mu?
Mart'ta sanatsız gürültü

3
Bir saniye bekle. Ne demek istediğini mi söyledim? Neden olduğunu söylüyorsun |a-b|<epsilon, doğru değil . Lütfen bu bağlantıyı cevabınıza ekleyin; Eğer cygnus-software.com/papers/comparingfloats/comparingfloats.htm adresini kabul ederseniz ve aptalca yorumlarımı kaldırabilirim.
Mart'ta sanatsız gürültü

3
Bu çok uzun bir yorum, kendi başına bir cevap değil. Tüm bağlamlar için (set) kanonik cevap (lar) var mı?
Merlyn Morgan-Graham

2
Eski bağlantı eski gibi görünüyor, yeni sayfa burada randomascii.wordpress.com/2012/02/25/…
Marson Mao

174

Bir epsilon değeri ile karşılaştırma çoğu insanın yaptığı şeydir (oyun programlamasında bile).

Uygulamanızı biraz değiştirmelisiniz:

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

Düzenleme: Christer, son bir blog yayınında bu konu hakkında büyük bir bilgi yığını ekledi . Zevk almak.


@OJ: İlk kod örneğinde bir sorun mu var? Tek sorun böyle bir durumda olduğunu düşündüm: float a = 3.4; if(a == 3.4){...}yani depolanan bir kayan noktayı bir değişmez ile karşılaştırırken | Bu durumda, her iki sayı da saklanır, bu yüzden eşitse aynı temsile sahip olurlar, bu yüzden yapmanın zararı a == bnedir?
Lazer

11
@DonReba: Yalnızca EPSILONolarak tanımlanırsa DBL_EPSILON. Normalde, karşılaştırmanın gerekli doğruluğuna bağlı olarak seçilen belirli bir değer olacaktır.
Nemo157

7
EPSILONbirbirini takip eden şamandıralar arasındaki fark da arttığından karşılaştırma şamandıralar büyük olduğunda çalışmaz. Bkz bu yazıyı .
kevintodisco

22
Battlefield 4'teki gibi dokular / nesneler çok titrediğinde bazı oyunlarda Z-dövüşü olması şaşırtıcı değil. Farkı karşılaştırmak EPSILONneredeyse işe yaramaz. Eldeki birimler için anlamlı bir eşik ile karşılaştırmanız gerekir. Ayrıca, std::absfarklı kayan nokta türleri için aşırı yüklendiğinden kullanın .
Maxim Egorushkin

11
Örnek kod, programcıların çoğunluğu tarafından tekrarlanan tipik bir hata gösterdiğinden aşağı indirdim. Kayan nokta her zaman göreli hatalarla ilgilidir, çünkü kayan noktadır (sabit nokta değildir). Böylece hiçbir zaman sabit bir hata (epsilon) ile doğru çalışmayacaktır.
user2261015

115

Ben bulundu tarihinde C ++ Test Çerçeve çiftlerde ve yüzen hem çalışır AlmostEqual2sComplement güzel çapraz platform şablon tabanlı uygulama içeriyor. BSD lisansı altında serbest bırakıldığı göz önüne alındığında, lisansı koruduğunuz sürece kendi kodunuzda kullanmak sorun olmamalıdır. Aşağıdaki kodu http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h https://github.com/google/googletest/blob adresinden aldım. /master/googletest/include/gtest/internal/gtest-internal.h ve lisansı en üste ekledi.

# GTEST_OS_WINDOWS değerini belirli bir değere tanımladığınızdan emin olun (veya kodun tabanınıza uyan bir şeye kullanıldığı kodu değiştirin - sonuçta BSD lisanslıdır).

Kullanım örneği:

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

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

İşte kod:

// 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: Bu yazı 4 yaşında. Muhtemelen hala geçerli ve kod güzel, ancak bazı insanlar iyileştirmeler buldu. En iyisi AlmostEquals, buraya yapıştırdığım koddan değil, Google Test kaynak kodundan en son sürümü edinin .


3
+1: Bunun doğru olduğuna katılıyorum. Ancak nedenini açıklamıyor. Buraya bakın: cygnus-software.com/papers/comparingfloats/comparingfloats.htm Bu blog yazısını, en yüksek puana yorumumu yazdıktan sonra okudum; Aynı şeyi söylediğine ve yukarıda uygulanan rasyonel / çözümü sağladığına inanıyorum. Çok fazla kod olduğu için insanlar cevabı kaçırırlar.
sanatsız gürültü

FloatPoint <double> fp (0.03f) derken örtük kastlar oluştuğunda meydana gelebilecek birkaç kötü şey vardır. Bunu önlemeye yardımcı olmak için birkaç değişiklik yaptım. şablon <typename U> açık FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "ile örtük bir dönüşüm yapıyorsunuz FloatingPoint, Yapma "<< std :: endl; assert (typeid (U) .name () == typeid (RawType) .name ()); } u_.değer_ = x; }
JeffCharter

2
İyi bulmak! Yine de, bu kodun çalındığı Google Testine katkıda bulunmak en iyisi olurdu. Muhtemelen daha yeni bir sürüm olduğunu yansıtacak şekilde yazıyı güncelleyeceğim. Google adamları kaşınıyorsa, örneğin bir GitHub özeti koyabilir misiniz? Ben de buna bağlayacağım.
skrebbel

3
En yeni kod snippet'i için buraya ve buraya bakın .
Jaege

1
Gerekli hatları bir gist dosyasına çıkardım. Buradan herkes ulaşabilir .
Yusuf Tarık Günaydın

111

İçin kayan nokta sayılarının karşılaştırılması bağlama bağlıdır. Operasyonların sırasını değiştirmek bile farklı sonuçlar üretebileceğinden, sayıların ne kadar "eşit" olmasını istediğinizi bilmek önemlidir.

Kayan nokta sayılarını Bruce Dawson ile karşılaştırmak, kayan nokta karşılaştırmasına bakarken başlamak için iyi bir yerdir.

Aşağıdaki tanımlar gelmektedir Knuth tarafından bilgisayar programlama sanatı :

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

Tabii ki, epsilon seçimi bağlama bağlıdır ve sayıların ne kadar eşit olmasını istediğinizi belirler.

Kayan nokta sayılarını karşılaştırmanın başka bir yöntemi de sayıların ULP'sine (son yerdeki birimler) bakmaktır. Özellikle karşılaştırmalarla ilgilenmemekle birlikte, her bilgisayar bilim insanının kayan nokta sayıları hakkında bilmesi gereken , kayan noktanın nasıl çalıştığını ve ULP'nin de dahil olduğu tuzakların ne olduğunu anlamak için iyi bir kaynaktır.


1
hangi sayının daha küçük / büyük olduğunu nasıl belirleyeceğinizi gönderdiğiniz için teşekkür ederiz!
Domates

1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);hayatımı kurtardı. LOL Bu sürümün (diğerleri için de geçerli olup olmadığını kontrol etmedim) ayrıca kayan nokta numarasının ayrılmaz kısmında meydana gelebilecek değişikliği de dikkate aldığını unutmayın (örnek: 2147352577.9999997616 == 2147352576.0000000000burada neredeyse bir fark olduğunu açıkça görebilirsiniz 2iki sayı arasında) oldukça güzel! Bu, biriken yuvarlama hatası sayının ondalık kısmını aştığında olur.
rbaleksandar

Bruce Dawson tarafından çok güzel ve yararlı makale, teşekkürler!
BobMorane

2
Bu sorunun C ++ olarak etiketlendiği göz önüne alındığında, çeklerinizin std::max(std::abs(a), std::abs(b))(veya) ile yazılması daha kolay olacaktır std::min(); std::absC ++, float ve çift tiplerle aşırı yüklenmiştir, bu yüzden iyi çalışır (her zaman fabsokunabilirlik için saklayabilirsiniz ).
Razakhel

1
Sorunun kodumda olduğu, beklenen orijinal değer ile ayrıştırılan dize arasındaki fark olduğu ortaya çıktı.
mwpowellhtx

47

Daha derinlemesine bir yaklaşım için Kayan nokta sayılarını karşılaştırma konusunu okuyun . İşte bu linkten kod snippet'i:

// Usable AlmostEqual function    
bool AlmostEqual2sComplement(float A, float B, int maxUlps)    
{    
    // Make sure maxUlps is non-negative and small enough that the    
    // default NAN won't compare as equal to anything.    
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);    
    int aInt = *(int*)&A;    
    // Make aInt lexicographically ordered as a twos-complement int    
    if (aInt < 0)    
        aInt = 0x80000000 - aInt;    
    // Make bInt lexicographically ordered as a twos-complement int    
    int bInt = *(int*)&B;    
    if (bInt < 0)    
        bInt = 0x80000000 - bInt;    
    int intDiff = abs(aInt - bInt);    
    if (intDiff <= maxUlps)    
        return true;    
    return false;    
}

14
MaxUlps için önerilen değer nedir?
unj2

6
" *(int*)&A;" Katı takma adlandırma kuralını ihlal edecek mi?
osgx

3
Gteste göre (ULP araması), 4 kabul edilebilir bir sayıdır.
Mayıs Oakes

4
Ve Bruce Dawson'un makalesinde (biri kağıdın girişinde bağlantılı olan) birkaç güncelleme var: randomascii.wordpress.com/2012/02/25/… ve randomascii.wordpress.com/2012/06/26/…
Michael Burr

3
ULP'de

27

Bunun eski bir iş parçacığı olduğunu fark etmekle birlikte, bu makale kayan nokta sayılarını karşılaştırırken bulduğum en düz ileri olanlardan biridir ve daha fazlasını keşfetmek istiyorsanız daha ayrıntılı referanslara da sahiptir ve ana site tüm sorunları kapsamaktadır. kayan nokta sayıları ile ilgili Kayan Noktalı Kılavuz: Karşılaştırma .

Kayan nokta toleranslarında yeniden incelenen biraz daha pratik bir makale bulabiliriz ve C ++ 'da buna dayanan mutlak tolerans testine dikkat çeker:

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

ve bağıl tolerans testi:

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

Makale notları mutlak testi başarısız olduğunda o xve ybüyük ve küçük iken göreli durumda başarısız olur. Mutlak ve bağıl toleransın aynı olduğunu varsayarsak, birleşik bir test şöyle görünecektir:

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 ++ 'da epsilon almanın taşınabilir yolu

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

Sonra karşılaştırma fonksiyonu olur

#include <cmath>
#include <limits>

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

34
Büyük olasılıkla bu epsilonun bir katını isteyeceksiniz.
user7116

11
Sadece std :: abs kullanamaz mısınız? AFAIK, std :: abs çiftler için de aşırı yüklenmiş. Lütfen yanılıyorsam beni uyar.
kolistivra

3
@kolistivra, yanılıyorsun. 'Fabs' içindeki 'f', float türü anlamına gelmez. Muhtemelen C fonksiyonları fabsf () ve fabsl () düşünüyorsunuz.
jcoffland

9
Aslında Bruce'un makalesinde özetlenen nedenlerden dolayı kayan nokta değeri büyüdükçe değişir . Diyor kısmını Bkz "2.0 daha büyük numaraları için yüzen arasındaki boşluk daha büyük büyür ve size sadece daha pahalı ve daha az belirgin eşitliği denetimini yapıyoruz sonra FLT_EPSILON kullanarak yüzen karşılaştırırsanız."
Ocak'ta bobobobo

5
Bu eski olduğunu biliyorum ama std :: abs cmath kayan nokta türleri için aşırı yüklenmiş.
mholzmann

18

Ben bu büyük iplik malzeme geçmekte oldukça zaman harcama sona erdi. Herkesin çok fazla zaman harcamak istediğinden şüpheliyim, böylece öğrendiklerimin ve uyguladığım çözümün özetini vurgulayacağım.

Hızlı özet

  1. 1e-8 yaklaşık 1e-16 ile aynı mı? Gürültülü sensör verilerine bakıyorsanız, muhtemelen evet ama moleküler simülasyon yapıyorsanız, olmayabilir! Alt satır: Her zaman tolerans değerini belirli bir işlev çağrısı bağlamında düşünmeniz ve onu uygulama genelinde genel sabit kodlu sabit yapmakla kalmamalısınız.
  2. Genel kütüphane fonksiyonları için, varsayılan toleranslı bir parametrenin olması hala iyidir . Tipik bir seçim numeric_limits::epsilon()float.h dosyasındaki FLT_EPSILON ile aynıdır. Ancak bu sorunludur, çünkü 1.0 gibi değerleri karşılaştırmak için epsilon, 1E9 gibi değerler için epsilon ile aynı değildir. FLT_EPSILON 1.0 için tanımlanmıştır.
  3. Sayının tolerans dahilinde olup olmadığını kontrol etmek için bariz uygulama, fabs(a-b) <= epsilonvarsayılan epsilon 1.0 için tanımlandığından, bu işe yaramaz. EPSilon'u a ve b cinsinden yukarı veya aşağı ölçeklendirmemiz gerekir.
  4. Bu sorunun iki çözümü vardır: ya epsilon'u orantılı olarak ayarlarsınız ya da a'nın max(a,b)etrafında bir sonraki temsili sayıları alabilir ve b'nin o aralığa girip girmediğini görebilirsiniz. İlki "göreceli" yöntem, daha sonra ULP yöntemi olarak adlandırılır.
  5. Her iki yöntem de 0 ile karşılaştırıldığında gerçekten başarısız olur. Bu durumda, uygulama doğru tolerans sağlamalıdır.

Yardımcı Program İşlevleri Uygulaması (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;
}

isDefinitelyLessThankontroller diff < tolerance, yani a ve b neredeyse eşittir (ve böylece a kesinlikle b'den küçük değildir). Her iki durumda da tolerans toleransını kontrol etmek daha anlamlı değil mi? Ya da belki de orEqualToyaklaşık eşitlik kontrolünün doğru dönüp dönmeyeceğini kontrol eden bir argüman ekleyin .
Matt Chambers

14

Yazdığınız kod hatalandı:

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

Doğru kod:

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

(... ve evet bu farklı)

Fabs bazı durumlarda tembel değerlendirmeyi kaybettirmez mi merak ediyorum. Derleyiciye bağlı olduğunu söyleyebilirim. Her ikisini de denemek isteyebilirsiniz. Ortalama olarak eşdeğerlerse, uygulamayı fabs ile alın.

İki şamandıranın hangisinin diğerinden daha büyük olma olasılığı hakkında bazı bilgileriniz varsa, tembel değerlendirmeden daha iyi yararlanmak için karşılaştırmanın sırasına göre oynayabilirsiniz.

Son olarak, bu işlevi satır içine alarak daha iyi sonuç elde edebilirsiniz. Yine de çok fazla gelişme olasılığı yok ...

Edit: OJ, kodunuzu düzelttiğiniz için teşekkürler. Yorumumu buna göre sildim


13

`dönüş fabs (a - b) <EPSILON;

Aşağıdaki durumlarda bu iyidir:

  • girdilerinizin büyüklük sırası fazla değişmez
  • çok az sayıda zıt işaret eşit kabul edilebilir

Ama aksi takdirde sizi belaya sokar. Çift kesinlikli sayılar yaklaşık 16 ondalık basamağa sahiptir. Karşılaştırma yaptığınız iki sayı EPSILON * 1.0E16'dan daha büyükse, o zaman şöyle de olabilirsiniz:

return a==b;

İlk sorun hakkında endişelenmeniz gerektiğini ve ikincisinin uygulamanızda iyi olduğunu varsayarak farklı bir yaklaşımı inceleyeceğim. Bir çözüm şöyle olabilir:

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

Bu hesaplama açısından pahalıdır, ancak bazen bunun için çağrılan şeydir. Şirketimde yapmamız gereken budur çünkü bir mühendislik kütüphanesi ile uğraşırız ve girdiler birkaç düzine büyüklükte değişebilir.

Her neyse, mesele şudur (ve neredeyse her programlama problemi için geçerlidir): İhtiyaçlarınızın ne olduğunu değerlendirin, ardından ihtiyaçlarınızı karşılayacak bir çözüm bulun - kolay cevabın ihtiyaçlarınızı karşılayacağını varsaymayın. Değerlendirmenizden sonra fabs(a-b) < EPSILONbunun yeterli olacağını düşünüyorsanız, mükemmel - kullanın! Ancak eksikliklerinin ve diğer olası çözümlerinin de farkında olun.


3
Yazım hataları (fmax () içindeki s / - /, / eksik virgül) bir yana, bu uygulama EPSILON içinde olan, ancak henüz VERYSMALL değil, sıfıra yakın sayılar için bir hata var. Örneğin, AreSame (1.0E-10, 1.0E-9), göreceli hata çok büyük olduğu için yanlış bildiriyor. Şirketinizde kahraman olursunuz.
brlcad

1
@brlcad Kayan noktayı anlamadınız . 1.0E-10 ve 1.0E-9, 10 büyüklüğüne göre farklılık gösterir. Dolayısıyla, aynı olmadıkları doğrudur. kayan nokta her zaman göreli hatalarla ilgilidir. 1.0E-10 ve 1.0E-9'u neredeyse eşit olarak gören bir sisteminiz varsa, her ikisi de "sıfıra oldukça yakın" (bu insanlar için makul, ancak matematiksel bir şey değil) olduğundan, EPSILON'un uygun şekilde ayarlanması gerekir böyle bir sistem için.
user2261015

8

Diğerlerinin de belirttiği gibi, sabit bir epsilon (0.0000001 gibi) kullanmak , epsilon değerinden uzak değerler için işe yaramaz . Örneğin, iki değeriniz 10000.000977 ve 10000 ise, HAYIR bu iki sayı arasında 32 bit kayan nokta değeri vardır - 10000 ve 10000.000977, bit için aynı bit olmadan alabileceğiniz kadar yakındır. Burada, 0.0009'dan küçük bir epsilon anlamsızdır; düz eşitlik operatörünü de kullanabilirsiniz.

Benzer şekilde, iki değer epsilon boyutuna yaklaştıkça, göreceli hata% 100'e çıkar.

Bu nedenle, 0.00001 gibi sabit bir nokta sayısını kayan nokta değerleriyle (üssü keyfi olduğu yerde) karıştırmaya çalışmak anlamsız bir alıştırmadır. Bu, yalnızca işlenen değerlerinin dar bir etki alanında (yani, belirli bir üsse yakın) yer aldığından emin olabilirseniz ve söz konusu test için uygun bir epsilon değeri seçtiğinizde işe yarayacaktır. Bir numarayı havadan çekerseniz ("Hey! 0.00001 küçük, bu yüzden iyi olmalı!"), Sayısal hatalara mahkum olursunuz. Bazı kötü schmuck rastgele epsilon değerleri başka bir test durumda çalışması yapmak için fırlatır kötü sayısal kod hata ayıklama bol zaman geçirdim.

Herhangi bir tür sayısal programlama yaparsanız ve sabit noktalı epsilons için ulaşmanız gerektiğine inanıyorsanız, BRUCE'NUN YÜZER NOKTALARI SAYILARINI KARŞILAŞTIRMASI ÜZERİNE OKUYUNUZ .

Kayan Nokta Sayılarını Karşılaştırma


5

Qt iki işlevi uygular, belki onlardan öğrenebilirsiniz:

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

Ve aşağıdaki işlevlere ihtiyacınız olabilir, çünkü

P1 veya p2'nin 0.0 olduğu değerlerin karşılaştırılmasının, değerlerden birinin NaN veya sonsuz olduğu değerlerin karşılaştırılmasının işe yaramayacağını unutmayın. Değerlerden biri her zaman 0.0 ise, bunun yerine qFuzzyIsNull kullanın. Değerlerden birinin 0,0 olması muhtemelse, bir çözüm her iki değere de 1,0 eklemektir.

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

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

3

Kayan nokta sayılarının genel amaçlı karşılaştırılması genellikle anlamsızdır. Nasıl karşılaştırılacağı gerçekten eldeki bir soruna bağlıdır. Birçok problemde, sayılar belirli bir tolerans dahilinde karşılaştırılmaya izin verecek kadar yeterince ayrıktır. Ne yazık ki, böyle bir hilenin gerçekten işe yaramadığı kadar çok sorun var. Bir örnek olarak, gözlemleriniz bariyere çok yakın olduğunda söz konusu bir sayının Heaviside (adım) işleviyle (dijital stok seçenekleri akla gelir) çalışmayı düşünün. Tolerans tabanlı karşılaştırma yapmak, konuyu orijinal bariyerden iki yenisine etkili bir şekilde değiştireceği için pek iyi olmaz. Yine, bu tür problemler için genel amaçlı bir çözüm yoktur ve özel çözüm, istikrarı sağlamak için sayısal yöntemi değiştirmeyi gerektirebilir.


3

Ne yazık ki, "savurgan" kodunuz bile yanlış. EPSILON, 1.0'a eklenebilen ve değerini değiştirebilen en küçük değerdir. Değeri 1,0 çok önemli - EPSILON eklendiğinde daha büyük sayılar değişmez. Şimdi, bu değeri farklı olup olmadıklarını söylemek için karşılaştırdığınız sayılarla ölçeklendirebilirsiniz. İki ikiliyi karşılaştırmak için doğru ifade:

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

Bu en azından. Bununla birlikte, genel olarak, hesaplamalarınızdaki gürültüyü hesaba katmak ve en az önemli bitlerden birkaçını göz ardı etmek istersiniz, bu nedenle daha gerçekçi bir karşılaştırma şöyle görünecektir:

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

Karşılaştırma performansı sizin için çok önemliyse ve değerlerinizin aralığını biliyorsanız, bunun yerine sabit nokta sayılarını kullanmalısınız.


2
“EPSILON, 1.0'a eklenebilen ve değerini değiştirebilen en küçük değerdir”: Aslında, bu onur 0.5 * EPSILON'un halefine gider (varsayılan turdan en yakın moda). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq

Sizce neden EPSILONsoru DBL_EPSILONya da FLT_EPSILON? Sorun, kendi hayal gücünüzde, burada DBL_EPSILON(aslında yanlış seçim olurdu) kullanmadığınız koda değiştirdiniz.
Ben Voigt

@ BenVoigt, haklısın, o zaman aklımda bir şey vardı ve soruyu o ışıkta yorumladım.
Don Reba

2

Sınıfım daha önce gönderilen cevaplara dayanıyor. Google'ın koduna çok benzer, ancak tüm NaN değerlerini 0xFF000000'ün üzerine iten bir önyargı kullanıyorum. Bu, NaN için daha hızlı bir kontrol sağlar.

Bu kod, genel bir çözüm değil, kavramı göstermek içindir. Google'ın kodu, platforma özgü tüm değerlerin nasıl hesaplanacağını zaten gösteriyor ve hepsini çoğaltmak istemedim. Bu kod üzerinde sınırlı test yaptım.

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

İşte kullanmanın std::numeric_limits::epsilon()cevap olmadığının kanıtı - birden fazla değer için başarısız:

Yukarıdaki yorumumun kanıtı:

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

Koşu bu çıktıyı verir:

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

İkinci durumda (bir ve bir taneden daha büyük), iki giriş değerinin olabildiğince yakın olduğunu ve yine de yakın olmadığıyla karşılaştırıldığını unutmayın. Böylece, 1.0'dan büyük değerler için, sadece bir eşitlik testi kullanabilirsiniz. Sabit epsilons, kayan noktalı değerleri karşılaştırırken sizi kurtarmaz.


return *(reinterpret_cast<double*>(&x));Genellikle işe yarıyor olsa da, aslında tanımsız bir davranış olduğuna inanıyorum .
Jaap Versteegh

Adil nokta, bu kod açıklayıcı olsa da, sorunu numeric_limits<>::epsilonve IEEE 754 döşeme noktası için göstermek için yeterli .
Steve Hollasch

Ayrıca adil bir nokta, ama bu tür bir içgörü beklerken yığın taşması üzerine yazı imho akıllıca değil. Kod olacak körü körüne hiç zor sadece gerektiği tüm UD olarak kaçınılmalıdır birlik trick-- ile bu çok yaygın desen --together ortadan kaldırmak için yapım kopyalanabilir.
Jaap Versteegh

1

Başka bir ilginç uygulama daha bulundu: 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

Kayan nokta çıkarma (örneğin, fabs (ab) <epsilon) içeren bu cevaplardan herhangi birine çok dikkat ediyorum. Birincisi, kayan nokta sayıları daha büyük büyüklüklerde ve aralığın epsilondan daha büyük olduğu yeterince yüksek büyüklüklerde daha seyrek olur, sadece == b de yapabilirsiniz. İkincisi, iki çok yakın kayan nokta sayısının çıkarılması (bunlar, eşit eşitlik aradığınız göz önüne alındığında), tam olarak nasıl elde ettiğinizdir. felaket iptali almaktır .

Taşınabilir olmasa da, grom'un cevabının bu sorunlardan kaçınmanın en iyi işi olduğunu düşünüyorum.


1
İyi bilgi için +1. Ancak, göreli hatayı artırarak eşitlik karşılaştırmasını nasıl bozabileceğinizi göremiyorum; IMHO hata sadece çıkarma sonucu önemli hale gelir, ancak çıkartılan iki işleneninkine göre büyüklük sırası yine de eşitliği yargılayacak kadar güvenilir olmalıdır. Kararın genel olarak daha yüksek olması gerekmedikçe, ancak bu durumda tek çözüm mantiste daha önemli bitlerle kayan nokta gösterimine geçmektir.
sehe

Neredeyse eşit olan iki sayının çıkarılması Felaket iptaline yol açmaz - aslında hiç hata vermez (qv Sterbenz Teoremi). Felaket iptali daha önce, hesaplamaları sırasında ave bkendileri sırasında gerçekleşir . Bulanık karşılaştırmanın bir parçası olarak kayan nokta çıkarımı kullanma konusunda kesinlikle bir sorun yoktur (diğerlerinin söylediği gibi, belirli bir kullanım örneği için mutlak bir epsilon değeri uygun olabilir veya olmayabilir.)
Sneftel

0

Sayısal yazılımda aslında iki kayan nokta sayısının tam olarak eşit olup olmadığını kontrol etmek istediğiniz durumlar vardır . Bunu benzer bir soruya gönderdim

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

Yani "CompareDoubles1" genel olarak yanlış olduğunu söyleyemezsiniz.


Aslında iyi bir cevaba çok sağlam bir referans olsa da, bilimsel hesaplama veya sayısal analiz arka planı (yani LAPACK, BLAS) olmayan herkesi tamlığı anlamayacak şekilde sınırlamak çok özeldir. Başka bir deyişle, Sayısal Tarifler tanıtımı veya Yük ve Faires tarafından Sayısal Analiz gibi bir şey okuduğunuzu varsayar .
mctylr

0

Karşılaştırmanın ne kadar kesin olmasını istediğinize bağlıdır. Aynı sayıyı karşılaştırmak istiyorsanız, sadece == ile gidin. (Tam olarak aynı numarayı istemediğiniz sürece bunu neredeyse hiç yapmak istemezsiniz.) Herhangi bir nezih platformda aşağıdakileri de yapabilirsiniz:

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

olarak fabsoldukça hızlı olma eğilimindedir. Oldukça hızlı demek istediğim, temelde biraz yönlü VE bu yüzden hızlı olmalıydı.

Ve çiftleri ve şamandıraları karşılaştırmak için tam sayı hileleri güzeldir, ancak çeşitli CPU boru hatlarının etkili bir şekilde ele alınmasını zorlaştırır. Yığını sıkça kullanılan değerler için geçici bir depolama alanı olarak kullanması nedeniyle bu sıralı bazı mimarilerde kesinlikle daha hızlı değil. (İlgilenenler için yük deposu.)


0

Miktar ölçeği açısından:

Eğer epsilonbelli fiziksel anlamda miktar büyüklük küçük bir kısmını (örneğin, görece değeri) ve Ave Btip I şu oldukça doğru olduğunu düşünmek daha aynı anlamda kıyaslanabilir:

#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

Bu kodu kullanıyorum:

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

2
Bunun için bu değil epsilon.
Sneftel

1
Neden olmasın? Bunu açıklayabilir misin?
debuti

2
@debuti epsilon, bu kod sadece iki sayı olup olmadığını kontrol etmeye çalışıyor sadece 1 ve En iyi ihtimalle 1. sonraki Temsil sayısı arasındaki mesafe tam olarak birbirine eşit fakat 2 sivil güçler ile çarpılarak ediliyor çünkü epsilono, bunu doğru yapmıyor bile.
Sneftel

2
Oh, ve std::fabs(std::min(v1, v2))yanlış - negatif girişler için daha büyük olanı seçer.
Sneftel

0

Bunu java için yazıyorum, ama belki de yararlı bulursunuz. Çiftler yerine uzunlar kullanır, ancak NaN'ler, alt normaller vb.

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

Bir dizi kayan nokta işleminden sonra, sayının beklediğimizden çok farklı olabileceğini unutmayın. Bunu düzeltmek için kod yok.


0

Buna ne dersin?

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

Çeşitli yaklaşımlar gördüm - ama bunu hiç görmedim, bu yüzden de herhangi bir yorum duymayı merak ediyorum!


1.99999999 ve 1.99999998
Mehdi

@Mehdi Ben sadece repl.it/repls/SvelteSimpleNumerator#main.cpp ile denedim ve beklendiği gibi davranıyor gibi görünüyor - ama belki bunu yapmaz referans için belirli bir derleyici uygulaması var?
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;
}

Bu işlevi küçük projem için kullandım ve işe yarıyor, ancak aşağıdakilere dikkat edin:

Çift hassasiyetli hata sizin için bir sürpriz yaratabilir. Diyelim ki epsilon = 1.0e-6, o zaman 1.0 ve 1.000001 yukarıdaki koda göre eşit olarak düşünülmemelidir, ancak makinemde işlev bunların eşit olduğunu düşünür, bunun nedeni 1.000001'in tam olarak bir ikili biçime çevrilememesi, muhtemelen 1.0000009xxx. 1.0 ve 1.0000011 ile test ediyorum ve bu sefer beklenen sonucu elde ediyorum.


-1

Bu lambda ile başka bir çözümdür:

#include <cmath>
#include <limits>

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

Bu, lambda olması ve hiçbir açıklaması olmaması dışında diğer cevapların çoğu ile tamamen aynıdır, bu yüzden bu cevap olarak çok fazla değer katmaz.
stijn

-2

Yolum doğru değil, yararlı olabilir

Her iki şamandırayı dizelere dönüştürün ve ardından dize karşılaştırması yapın

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


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

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

    return false;

}

overlaoding operatörü de yapılabilir


+1: hey, bununla oyun programlama yapmayacağım, ama yuvarlak açma şamandıraları fikri Bruce Dawson'un blogunda (inceleme?: D) bu konuda birkaç kez ortaya çıktı ve eğer tuzağa düştüyseniz bir oda ve birisi kafanıza bir silah koyar ve "Hey, X'in önemli rakamlar içinde iki şamandırayı karşılaştırmanız gerekir, 5 dakikanız var, GO!" bu muhtemelen dikkate alınması gereken bir şey. ;)
shelleybutterfly

@shelleybutterfly Daha sonra soru, iki kayan nokta sayısını karşılaştırmanın en etkili yoluydu.
Tommy Andersen

@TommyA lol belki, ama bahse girerim yuvarlak açma verimlilikle ilgili olmayan nedenlerden dolayı aşağı düştü. Benim sezgim HW fp matematik ile karşılaştırıldığında oldukça verimsiz olmasına rağmen, aynı zamanda yazılım fp algoritmalarının en azından büyük bir OO farkı olması muhtemel olmadığını söylüyor. Bu durumda verimlilik kaygılarının önemli olduğunu gösteren analizinizi sabırsızlıkla bekliyorum. Ayrıca, bazen optimalden daha azı hala değerli bir cevap olabilir ve aşağı doğru söylendiği gibi - Dawson'un konuyla ilgili blogunda bile belirtilen geçerli bir teknik olmasına rağmen, bir oylamayı hak ettiğini düşündüm.
shelleybutterfly

-2

doubleSabit ile ikisini karşılaştıramazsınız EPSILON. Değerine bağlı olarak double, EPSILONdeğişir.

Daha iyi bir çift karşılaştırma:

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

Daha genel bir şekilde:

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

4
Bu yöntemin birçok zayıflığı vardır, örneğin sayılar zaten var olandan daha küçükse ave fark hala önemli olabilir. Tersine sayılar çok büyükse, birkaç hata biti bile, sayıların eşit kabul edilmesini isteseniz bile karşılaştırmanın başarısız olmasını sağlar. Bu yanıt tam olarak kaçınmak istediğiniz "genel" karşılaştırma algoritması türüdür. bepsilon()
SirGuy

-3

Neden bitsel XOR yapmıyorsunuz? Karşılık gelen bitleri eşitse, iki kayan nokta sayısı eşittir. Bence, iki float karşılaştırmasını hızlandırmak için üs bitlerini mantisin önüne yerleştirme kararı verildi. Bence, burada birçok cevap epsilon karşılaştırmasının noktasını kaçırıyor. Epsilon değeri yalnızca hassas kayan nokta sayılarının karşılaştırılmasına bağlıdır. Örneğin, şamandıralarla bazı aritmetik yaptıktan sonra iki sayı elde edersiniz: 2.5642943554342 ve 2.5642943554345. Eşit değildirler, ancak çözüm için sadece 3 ondalık basamak önemlidir, o zaman eşittirler: 2.564 ve 2.564. Bu durumda 0.001'e eşit epsilon seçersiniz. Epsilon karşılaştırması, bitsel XOR ile de mümkündür. Yanlışım varsa düzelt.


Lütfen aynı cevabı birden fazla soruya eklemeyin. En iyiyi cevaplayın ve gerisini kopya olarak işaretleyin. Bkz. Meta.stackexchange.com/questions/104227/…
Rao

Ben sadece aynı formatta normalleştirilmiş temsiller ile sınırlı, sadece ExOr (ve bir veya iki karşılaştırma) kullanarak "epsilon karşılaştırma" mümkün olduğunu düşünmüyorum.
greybeard
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.