Qual è il modo più efficace per il float e il doppio confronto?


524

Quale sarebbe il modo più efficace per confrontare due doubleo due floatvalori?

Fare semplicemente questo non è corretto:

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

Ma qualcosa del tipo:

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

Sembra al trattamento dei rifiuti.

Qualcuno conosce un comparatore float più intelligente?


2
> sarebbe più efficiente aggiungere ... all'inizio della funzione? <invoke Knuth>L'ottimizzazione prematura è la radice di tutti i mali. </invoke Knuth>Basta andare con abs (ab) <EPS come indicato sopra, è chiaro e facile da capire.
Andrew Coleson,


2
L'unica cosa non ottimale sull'implementazione del poster originale è che contiene un ramo extra su &&. La risposta di OJ è ottimale. fabs è un intrinseco che è una singola istruzione su x87, e suppongo che su quasi tutto il resto. Accetta già la risposta di OJ!
3yE

3
Se puoi, rilascia il punto mobile e usa i punti fissi. Esempio, utilizzare millimetri {punto fisso} anziché metri {virgola mobile}.
Thomas Matthews,

33
"Semplicemente fare questo non è corretto" - Questo è solo spazzatura, ovviamente usare ==può essere perfettamente corretto, ma questo dipende interamente dal contesto non indicato nella domanda. Fino a quando quel contesto non sarà noto, ==rimarrà comunque il "modo più efficiente" .
Christian Rau,

Risposte:


459

Fai molta attenzione usando uno degli altri suggerimenti. Tutto dipende dal contesto.

Ho passato molto tempo a rintracciare un bug in un sistema che presumeva a==bse |a-b|<epsilon. I problemi sottostanti erano:

  1. La presunzione implicita in un algoritmo che se a==be b==cpoia==c .

  2. Utilizzando lo stesso epsilon per linee misurate in pollici e linee misurate in mil (0,001 pollici). Questo è a==bma 1000a!=1000b. (Ecco perché AlmostEqual2sComplement richiede epsilon o max ULPS).

  3. L'uso dello stesso epsilon sia per il coseno degli angoli che per la lunghezza delle linee!

  4. Utilizzando tale funzione di confronto per ordinare gli elementi in una raccolta. (In questo caso l'utilizzo dell'operatore C ++ incorporato == per i doppi ha prodotto risultati corretti.)

Come ho detto: tutto dipende dal contesto e dalle dimensioni previste di a e b.

BTW, std::numeric_limits<double>::epsilon() è la "macchina epsilon". È la differenza tra 1,0 e il valore successivo rappresentabile da un doppio. Suppongo che potrebbe essere utilizzato nella funzione di confronto, ma solo se i valori previsti sono inferiori a 1. (Questo è in risposta alla risposta di @ cdv ...)

Inoltre, se in pratica hai l' intaritmetica doubles(qui usiamo i doppi per mantenere i valori int in alcuni casi) la tua aritmetica sarà corretta. Ad esempio 4.0 / 2.0 sarà uguale a 1.0 + 1.0. Questo a condizione che non si eseguano operazioni che generano frazioni (4.0 / 3.0) o che non superino le dimensioni di un int.


10
+1 per indicare l'ovvio (che spesso viene ignorato). Per un metodo generico, puoi rendere epsilon relativo fabs(a)+fabs(b)ma compensando NaN, 0 somma e overflow, questo diventa abbastanza complesso.
Peter

4
Deve esserci qualcosa che non capisco. Il tipico float/ doubleè MANTISSA x 2 ^ EXP . epsilondipenderà dall'esponente. Ad esempio se la mantissa è 24 bit e l' esponente è firmato 8 bit, allora 1/(2^24)*2^127o ~2^103è un valore epsilonper alcuni valori; o si riferisce a un epsilon minimo ?
rumore senza arti

3
Aspetta un secondo. Quello che ho detto è quello che volevi dire? Stai dicendo perché |a-b|<epsilon, non è corretto. Aggiungi questo link alla tua risposta; se accetti cygnus-software.com/papers/comparingfloats/comparingfloats.htm e posso rimuovere i miei stupidi commenti.
rumore senza arti

3
Questo è un commento molto lungo, non una risposta in sé. Esiste una (serie di) risposte canoniche per tutti i contesti?
Merlyn Morgan-Graham,

2
Il vecchio link sembra essere obsoleto, la nuova pagina è qui randomascii.wordpress.com/2012/02/25/…
Marson Mao,

174

Il confronto con un valore epsilon è ciò che la maggior parte delle persone fa (anche nella programmazione di gioco).

Dovresti cambiare un po 'l'implementazione però:

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

Modifica: Christer ha aggiunto una grande quantità di informazioni su questo argomento in un recente post sul blog . Godere.


@OJ: c'è qualcosa che non va nel primo esempio di codice? Ho pensato che l'unico problema fosse in una situazione come questa: float a = 3.4; if(a == 3.4){...}cioè quando si confronta un punto fluttuante memorizzato con un valore letterale | In questo caso, entrambi i numeri sono memorizzati, quindi avranno la stessa rappresentazione, se uguale, quindi qual è il danno nel fare a == b?
Lazer,

11
@DonReba: solo se EPSILONdefinito come DBL_EPSILON. Normalmente sarà un valore specifico scelto in base alla precisione richiesta del confronto.
Nemo157,

7
EPSILONil confronto non funziona quando i float sono grandi, poiché anche la differenza tra i float consecutivi aumenta. Vedere questo articolo .
kevintodisco,

22
Non c'è da stupirsi che in alcuni giochi ci sia il combattimento con Z quando sfarfallio di trame / oggetti, come in Battlefield 4. Confrontare la differenza con EPSILONè praticamente inutile. È necessario confrontare con una soglia che ha senso per le unità a portata di mano. Inoltre, utilizzare std::abspoiché è sovraccarico per diversi tipi in virgola mobile.
Maxim Egorushkin,

11
Ho effettuato il downgrade poiché il codice di esempio mostra che il tipico bug viene ripetuto dalla maggioranza dei programmatori. Il virgola mobile riguarda sempre errori relativi, poiché è virgola mobile (non punto fisso). Quindi non funzionerà mai correttamente con un errore fisso (epsilon).
user2261015

115

Ho scoperto che Google C ++ Testing Framework contiene una bella implementazione basata su modelli multipiattaforma di AlmostEqual2sComplement che funziona sia su doppio che su float. Dato che è rilasciato sotto la licenza BSD, usarlo nel tuo codice non dovrebbe essere un problema, purché conservi la licenza. Ho estratto il codice seguente da 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 e ha aggiunto la licenza in cima.

Assicurati di #definire GTEST_OS_WINDOWS su un valore (o di modificare il codice in cui è utilizzato per qualcosa che si adatta alla tua base di codice - dopo tutto è la licenza BSD).

Esempio di utilizzo:

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

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

Ecco il codice:

// 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: questo post ha 4 anni. Probabilmente è ancora valido e il codice è carino, ma alcune persone hanno trovato miglioramenti. Meglio andare a prendere l'ultima versione di AlmostEqualsdirettamente dal codice sorgente di Google Test, e non quella che ho incollato qui.


3
+1: sono d'accordo che questo sia corretto. Tuttavia, non spiega perché. Vedi qui: cygnus-software.com/papers/comparingfloats/comparingfloats.htm Ho letto questo post sul blog dopo aver scritto il mio commento sul punteggio più alto qui; Credo che dica la stessa cosa e fornisca la razionale / soluzione implementata sopra. Poiché c'è così tanto codice, alla gente mancherà la risposta.
rumore senza arti

Ci sono un paio di cose brutte che possono accadere quando si verificano cast impliciti facendo dire FloatPoint <double> fp (0.03f). Ho apportato un paio di modifiche a questo per aiutare a prevenirlo. template <typename U> esplicito FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "Stai eseguendo una conversione implicita con FloatingPoint, non "<< std :: endl; assert (typeid (U) .name () == typeid (RawType) .name ()); } u_.value_ = x; }
JeffCharter

2
Buona scoperta! Immagino che sarebbe meglio contribuirli a Google Test, tuttavia, da dove è stato rubato questo codice. Aggiornerò il post per riflettere che probabilmente c'è una versione più recente. Se i ragazzi di Google si comportano in modo pruriginoso, potresti metterli ad esempio in una sintesi di GitHub? Collegherò anche a quello, quindi.
skrebbel,

3
Per lo snippet di codice più recente, vedere qui e qui .
Jaege,

1
Ho estratto le righe necessarie in un file gist. Chiunque può raggiungere da qui .
Yusuf Tarık Günaydın,

111

Il confronto di numeri in virgola mobile per dipende dal contesto. Poiché anche la modifica dell'ordine delle operazioni può produrre risultati diversi, è importante sapere quanto "uguali" si desidera che i numeri siano.

Confrontare i numeri in virgola mobile di Bruce Dawson è un buon punto di partenza quando si guarda il confronto in virgola mobile.

Le seguenti definizioni sono tratte da L'arte della programmazione per computer di 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);
}

Naturalmente, la scelta di epsilon dipende dal contesto e determina quanto uguali si desidera che i numeri siano.

Un altro metodo per confrontare i numeri in virgola mobile è osservare l'ULP (unità all'ultimo posto) dei numeri. Pur non trattando in modo specifico dei confronti, l'articolo Che cosa ogni scienziato informatico dovrebbe sapere sui numeri in virgola mobile è una buona risorsa per capire come funziona il virgola mobile e quali sono le insidie, incluso ciò che è ULP.


1
grazie per aver pubblicato come determinare quale numero è più piccolo / più grande!
Pomodoro

1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);mi ha salvato la vita. LOL Nota che questa versione (non ho verificato se è applicabile anche per gli altri) considera anche il cambiamento che potrebbe verificarsi nella parte integrale del numero in virgola mobile (esempio: 2147352577.9999997616 == 2147352576.0000000000dove puoi vedere chiaramente che c'è quasi una differenza di 2tra i due numeri) che è abbastanza bello! Ciò accade quando l'errore di arrotondamento accumulato trabocca dalla parte decimale del numero.
rbaleksandar,

Articolo molto carino e utile di Bruce Dawson, grazie!
BobMorane,

2
Dato che questa domanda è taggata in C ++, i tuoi controlli sarebbero più facili da leggere essendo scritti come std::max(std::abs(a), std::abs(b))(o con std::min()); std::absin C ++ è sovraccarico di float e doppi tipi, quindi funziona bene (puoi sempre tenerlo fabsper leggibilità).
Razakhel,

1
Si scopre che il problema era nel mio codice, la differenza tra il valore previsto originale e la stringa analizzata.
mwpowellhtx,

47

Per un approccio più approfondito leggi Confrontare i numeri in virgola mobile . Ecco lo snippet di codice da quel link:

// 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
Qual è il valore suggerito di maxUlps?
unj2

6
" *(int*)&A;" Violerà una rigorosa regola di aliasing?
osgx,

3
Secondo gtest (ricerca di ULP), 4 è un numero accettabile.
Maggio Oakes,

4
E qui ci sono un paio di aggiornamenti al documento di Bruce Dawson (uno dei quali è collegato nell'intro del documento): randomascii.wordpress.com/2012/02/25/… e randomascii.wordpress.com/2012/06/26/…
Michael Burr,

3
Mi ci è voluto un po 'per capire cosa fosse su ULP: Units in the Last Place
JeffCharter

27

Rendersi conto che questo è un vecchio thread, ma questo articolo è uno dei più semplici che ho trovato sul confronto di numeri in virgola mobile e se vuoi esplorare di più ha anche riferimenti più dettagliati e il sito principale copre una gamma completa di problemi gestione dei numeri in virgola mobile La Guida in virgola mobile: confronto .

Possiamo trovare un articolo un po 'più pratico in Tolleranze in virgola mobile rivisitate e osserva che esiste un test di tolleranza assoluta , che si riduce a questo in C ++:

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

e test di tolleranza relativa :

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

L'articolo osserva che i test assoluti falliscono quando xe ysono grandi e falliscono nel caso relativo quando sono piccoli. Supponendo che la tolleranza assoluta e relativa sia la stessa, un test combinato sarebbe simile al seguente:

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

Il modo portatile per ottenere epsilon in C ++ è

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

Quindi la funzione di confronto diventa

#include <cmath>
#include <limits>

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

34
Probabilmente vorrai un multiplo di quel epsilon.
user7116

11
Non puoi semplicemente usare std :: abs? AFAIK, std :: abs è sovraccarico anche per i doppi. Per favore avvisami se sbaglio.
kolistivra,

3
@kolistivra, ti sbagli. La "f" in "fabs" non significa il tipo float. Probabilmente stai pensando alle funzioni C fabsf () e fabsl ().
jcoffland,

9
In realtà per le ragioni delineate nell'articolo di Bruce, epsilon cambia man mano che il valore in virgola mobile aumenta. Vedi la parte in cui dice "Per numeri maggiori di 2.0 il divario tra i float aumenta e se si confrontano i float usando FLT_EPSILON, allora si sta solo facendo un controllo dell'uguaglianza più costoso e meno ovvio."
Bobobobo,

5
so che questo è vecchio ma std :: abs è sovraccarico per i tipi a virgola mobile in cmath.
mholzmann,

18

Ho finito per passare un bel po 'di tempo a leggere il materiale in questo fantastico thread. Dubito che tutti vogliano passare così tanto tempo in modo da evidenziare il riassunto di ciò che ho imparato e la soluzione che ho implementato.

Riepilogo rapido

  1. 1e-8 è approssimativamente uguale a 1e-16? Se stai osservando dati di sensori rumorosi, probabilmente sì, ma se stai facendo una simulazione molecolare, allora potrebbe non esserlo! In conclusione: devi sempre pensare alla tolleranza valore nel contesto di una chiamata di funzione specifica e non solo renderla costante generica codificata a livello di app.
  2. Per le funzioni di libreria generali, è comunque bello avere un parametro con tolleranza predefinita . Una scelta tipica ènumeric_limits::epsilon() è la stessa di FLT_EPSILON in float.h. Ciò è tuttavia problematico perché epsilon per confrontare valori come 1.0 non è uguale a epsilon per valori come 1E9. FLT_EPSILON è definito per 1.0.
  3. L'implementazione ovvia per verificare se il numero rientra nella tolleranza è fabs(a-b) <= epsilontuttavia non funziona perché epsilon predefinito è definito per 1.0. Dobbiamo ridimensionare epsilon su o giù in termini di a e b.
  4. Esistono due soluzioni a questo problema: o imposta epsilon in modo proporzionale max(a,b)o puoi ottenere i numeri rappresentabili successivi attorno a a e quindi vedere se b rientra in quell'intervallo. Il primo si chiama metodo "relativo" e in seguito si chiama metodo ULP.
  5. Entrambi i metodi in realtà falliscono comunque se confrontati con 0. In questo caso, l'applicazione deve fornire la tolleranza corretta.

Implementazione di funzioni di utilità (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;
}

isDefinitelyLessThani controlli diff < tolerance, il che significa che aeb sono quasi uguali (e quindi a non è sicuramente inferiore a b). Non ha più senso controllare la tolleranza diff> in entrambi i casi? O forse aggiungere un orEqualToargomento che controlla se il controllo approssimativo di uguaglianza debba restituire vero o no.
Matt Chambers,

14

Il codice che hai scritto è stato corretto:

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

Il codice corretto sarebbe:

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

(... e sì, questo è diverso)

Mi chiedo se i fab non ti farebbero perdere una valutazione pigra in qualche caso. Direi che dipende dal compilatore. Potresti voler provare entrambi. Se sono equivalenti in media, prendi l'implementazione con fabs.

Se hai alcune informazioni su quale dei due float ha maggiori probabilità di essere più grande di altri, puoi giocare nell'ordine del confronto per sfruttare meglio la valutazione pigra.

Infine, potresti ottenere risultati migliori incorporando questa funzione. Probabilmente non migliorerà molto però ...

Modifica: GU, grazie per aver corretto il tuo codice. Ho cancellato il mio commento di conseguenza


13

`return fabs (a - b) <EPSILON;

Questo va bene se:

  • l'ordine di grandezza dei tuoi input non cambia molto
  • numeri molto piccoli di segni opposti possono essere considerati uguali

Ma per il resto ti porterà nei guai. I numeri a doppia precisione hanno una risoluzione di circa 16 cifre decimali. Se i due numeri che stai confrontando sono di grandezza maggiore di EPSILON * 1.0E16, allora potresti anche dire:

return a==b;

Esaminerò un approccio diverso che presuppone la necessità di preoccuparsi del primo problema e presumo che il secondo vada bene per l'applicazione. Una soluzione sarebbe qualcosa di simile:

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

Questo è costoso dal punto di vista computazionale, ma a volte è ciò che viene richiesto. Questo è ciò che dobbiamo fare nella mia azienda perché abbiamo a che fare con una biblioteca di ingegneria e gli input possono variare di alcune decine di ordini di grandezza.

Ad ogni modo, il punto è questo (e si applica praticamente a tutti i problemi di programmazione): valuta quali sono le tue esigenze, quindi trova una soluzione per soddisfare le tue esigenze - non dare per scontato che la risposta facile soddisfi le tue esigenze. Se dopo la tua valutazione trovi che fabs(a-b) < EPSILONsarà sufficiente, perfetto - usalo! Ma sii consapevole delle sue carenze e anche di altre possibili soluzioni.


3
A parte i refusi (s / - /, / comma mancante in fmax ()), questa implementazione ha un bug per i numeri vicini allo zero che si trovano all'interno di EPSILON, ma non ancora del tutto VERYSMALL. Ad esempio, AreSame (1.0E-10, 1.0E-9) riporta false perché l'errore relativo è enorme. Sarai l'eroe della tua compagnia.
brlcad,

1
@brlcad Non hai ottenuto il punto in virgola mobile. 1.0E-10 e 1.0E-9 differiscono per l'entità di 10. Quindi è vero che non sono uguali. il virgola mobile riguarda sempre errori relativi . Se si dispone di un sistema che considera 1.0E-10 e 1.0E-9 quasi uguali, poiché entrambi sono "abbastanza vicini allo zero" (che sembra ragionevole per l'uomo ma non è nulla matematicamente), allora EPSILON deve essere regolato in modo appropriato per un tale sistema.
user2261015,

8

Come altri hanno sottolineato, l'uso di un epsilon ad esponente fisso (come 0,0000001) sarà inutile per valori diversi dal valore epsilon. Ad esempio, se i tuoi due valori sono 10000.000977 e 10000, allora ci sono NO valori a virgola mobile a 32 bit tra questi due numeri - 10000 e 10000,000,977 mila sono il più vicino si può eventualmente ottenere senza essere bit per bit identici. Qui, un epsilon inferiore a 0,0009 non ha senso; potresti anche usare l'operatore di uguaglianza diretta.

Allo stesso modo, quando i due valori si avvicinano alla dimensione epsilon, l'errore relativo aumenta al 100%.

Quindi, provare a mescolare un numero in virgola fissa come 0,00001 con valori in virgola mobile (dove l'esponente è arbitrario) è un esercizio inutile. Funzionerà sempre solo se si può essere certi che i valori degli operandi si trovano all'interno di un dominio ristretto (cioè vicino ad un esponente specifico) e se si seleziona correttamente un valore epsilon per quel test specifico. Se tiri fuori un numero dall'aria ("Ehi! 0.00001 è piccolo, quindi deve essere buono!"), Sei condannato a errori numerici. Ho trascorso molto tempo a eseguire il debug di codici numerici errati in cui alcuni poveri schmuck lanciano valori casuali di epsilon per far funzionare ancora un altro test.

Se esegui una programmazione numerica di qualsiasi tipo e ritieni di dover raggiungere epsilon a virgola fissa, LEGGI L'ARTICOLO DI BRUCE SUL CONFRONTO DEI NUMERI A PUNTO GALLEGGIANTE .

Confronto tra numeri in virgola mobile


5

Qt implementa due funzioni, forse puoi imparare da esse:

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

E potresti aver bisogno delle seguenti funzioni, da allora

Si noti che il confronto di valori in cui p1 o p2 è 0,0 non funzionerà, né il confronto di valori in cui uno dei valori è NaN o infinito. Se uno dei valori è sempre 0,0, utilizzare invece qFuzzyIsNull. Se è probabile che uno dei valori sia 0.0, una soluzione è aggiungere 1.0 a entrambi i valori.

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

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

3

Il confronto generico di numeri in virgola mobile è generalmente insignificante. Come confrontare dipende davvero da un problema a portata di mano. In molti problemi, i numeri sono sufficientemente discretizzati per consentire di confrontarli entro una determinata tolleranza. Sfortunatamente, ci sono altrettanti problemi, in cui un simile trucco non funziona davvero. Per un esempio, considera di lavorare con una funzione Heaviside (step) di un numero in questione (vengono in mente le opzioni di stock digitali) quando le tue osservazioni sono molto vicine alla barriera. L'esecuzione di un confronto basato sulla tolleranza non farebbe molto bene, in quanto sposterebbe effettivamente il problema dalla barriera originale a due nuove. Ancora una volta, non esiste una soluzione generale per tali problemi e la particolare soluzione potrebbe richiedere di cambiare il metodo numerico per raggiungere la stabilità.


3

Sfortunatamente, anche il tuo codice "dispendioso" non è corretto. EPSILON è il valore più piccolo che è possibile aggiungere a 1.0 e modificarne il valore. Il valore 1.0 è molto importante: numeri più grandi non cambiano quando vengono aggiunti a EPSILON. Ora, puoi ridimensionare questo valore ai numeri che stai confrontando per dire se sono diversi o no. L'espressione corretta per confrontare due doppi è:

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

Questo è come minimo. In generale, tuttavia, dovresti tenere conto del rumore nei tuoi calcoli e ignorare alcuni dei bit meno significativi, quindi un confronto più realistico sembrerebbe:

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

Se le prestazioni del confronto sono molto importanti per te e conosci l'intervallo dei tuoi valori, devi invece utilizzare numeri a virgola fissa.


2
"EPSILON è il valore più piccolo che può essere aggiunto a 1.0 e modificarne il valore": in realtà, questo onore va al successore di 0,5 * EPSILON (nella modalità predefinita arrotondata al più vicino). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq

Perché pensi che EPSILONnella domanda sia DBL_EPSILONo FLT_EPSILON? Il problema è nella tua immaginazione, in cui hai sostituito DBL_EPSILON(che in effetti sarebbe stata la scelta sbagliata) in un codice che non lo utilizzava.
Ben Voigt,

@BenVoigt, hai ragione, era qualcosa nella mia mente in quel momento, e ho interpretato la domanda in quella luce.
Don Reba,

2

La mia lezione si basa su risposte precedentemente pubblicate. Molto simile al codice di Google, ma utilizzo un bias che spinge tutti i valori NaN al di sopra di 0xFF000000. Ciò consente un controllo più rapido per NaN.

Questo codice intende dimostrare il concetto, non essere una soluzione generale. Il codice di Google mostra già come calcolare tutti i valori specifici della piattaforma e non volevo duplicare tutto ciò. Ho eseguito test limitati su questo codice.

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

Ecco la prova che l'utilizzo std::numeric_limits::epsilon() non è la risposta - fallisce per valori maggiori di uno:

Prova del mio commento sopra:

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

L'esecuzione produce questo output:

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

Si noti che nel secondo caso (uno e appena più grande di uno), i due valori di input sono il più vicino possibile e si confrontano comunque come non vicini. Pertanto, per valori superiori a 1.0, è possibile utilizzare un test di uguaglianza. Gli epsilon fissi non ti salveranno confrontando i valori in virgola mobile.


Credo che return *(reinterpret_cast<double*>(&x));sebbene di solito funzioni, in realtà è un comportamento indefinito.
Jaap Versteegh,

Il punto giusto, sebbene questo codice sia illustrativo, è sufficiente per dimostrare il problema numeric_limits<>::epsilone il punto di pavimentazione IEEE 754.
Steve Hollasch,

Anche un punto giusto, ma non è saggio postare in overflow dello stack aspettandosi quel tipo di intuizione. Il codice verrà copiato alla cieca, rendendo sempre più difficile l'eradicazione di questo modello molto comune - insieme al trucco del sindacato - che dovrebbe essere evitato come tutti gli UD.
Jaap Versteegh,

1

Ho trovato un'altra interessante implementazione su: 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

Sarei molto diffidente nei confronti di una qualsiasi di queste risposte che comporta la sottrazione in virgola mobile (ad es. Fabs (ab) <epsilon). In primo luogo, i numeri in virgola mobile diventano più sparsi a magnitudini maggiori e ad magnitudini abbastanza elevate in cui la spaziatura è maggiore di epsilon, si potrebbe anche fare a == b. In secondo luogo, sottraendo due numeri in virgola mobile molto vicini (poiché questi tenderanno ad essere, dato che stai cercando una quasi uguaglianza) è esattamente come ottieni una cancellazione catastrofica .

Pur non essendo portatile, penso che la risposta di Grom faccia il miglior lavoro per evitare questi problemi.


1
+1 per una buona informazione. Tuttavia, non riesco a vedere come si possa confondere il confronto di uguaglianza aumentando l'errore relativo; IMHO l'errore diventa significativo solo nel risultato della sottrazione, tuttavia il suo ordine di grandezza rispetto a quello dei due operandi da sottrarre dovrebbe essere ancora abbastanza affidabile per giudicare l'uguaglianza. A meno che la risoluzione non debba essere complessivamente più elevata, ma in tal caso l'unica soluzione è quella di passare a una rappresentazione in virgola mobile con bit più significativi nella mantissa.
vede

Sottrarre due numeri quasi uguali NON porta a una cancellazione catastrofica - in realtà, non introduce alcun errore (il Teorema di qb Sterbenz). La cancellazione catastrofica si verifica prima, durante il calcolo di ae bstessi. Non vi è assolutamente alcun problema con l'utilizzo della sottrazione in virgola mobile come parte di un confronto fuzzy (anche se, come altri hanno già detto, un valore assoluto di epsilon può o non può essere appropriato per un determinato caso d'uso.)
Sneftel,

0

In realtà ci sono casi nel software numerico in cui si desidera verificare se due numeri in virgola mobile sono esattamente uguali. Ho pubblicato questo su una domanda simile

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

Quindi non puoi dire che "CompareDoubles1" sia sbagliato in generale.


In realtà un riferimento molto solido a una buona risposta, anche se è molto specializzato nel limitare chiunque non abbia un background scientifico o di analisi numerica (vale a dire LAPACK, BLAS) per non comprenderne la completezza. O in altre parole, si presume che tu abbia letto qualcosa come l' introduzione di Ricette numeriche o Analisi numerica di Burden & Faires.
mctylr,

0

Dipende da quanto preciso vuoi che sia il confronto. Se vuoi confrontare esattamente lo stesso numero, procedi con ==. (Non vuoi quasi mai farlo a meno che tu non voglia esattamente lo stesso numero.) Su qualsiasi piattaforma decente puoi anche fare quanto segue:

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

come fabstende ad essere piuttosto veloce. Con abbastanza veloce intendo che è fondamentalmente un bit per bit, quindi è meglio essere veloci.

E i trucchi interi per confrontare doppi e float sono buoni ma tendono a rendere più difficile la gestione efficace delle varie pipeline della CPU. E sicuramente non è più veloce su alcune architetture in ordine in questi giorni a causa dell'utilizzo dello stack come area di archiviazione temporanea per i valori che vengono utilizzati frequentemente. (Load-hit-store per chi se ne frega.)


0

In termini di scala delle quantità:

Se epsilonè la piccola frazione della grandezza della quantità (cioè il valore relativo) in un certo senso fisico Ae Btipi è paragonabile nello stesso senso, di quanto io ritenga, che quanto segue sia del tutto corretto:

#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

Io uso questo codice:

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

2
Non è quello che epsilonserve.
Sneftel,

1
Perchè no? Puoi spiegarlo?
debuti,

2
@debuti epsilonè semplicemente la distanza tra 1 e il prossimo numero rappresentabile dopo 1. Nella migliore delle ipotesi, quel codice sta solo cercando di verificare se i due numeri sono esattamente uguali tra loro, ma poiché i non poteri di 2 vengono moltiplicati per epsilon, esso non lo sta nemmeno facendo correttamente.
Sneftel,

2
Oh, ed std::fabs(std::min(v1, v2))è errato - per input negativi seleziona quello con una grandezza maggiore.
Sneftel,

0

Scrivo questo per Java, ma forse lo trovi utile. Usa i long anziché i doppi, ma si occupa di NaN, subanormali, ecc.

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

Tieni presente che dopo una serie di operazioni in virgola mobile, il numero può essere molto diverso da quello che ci aspettiamo. Non esiste un codice per risolverlo.


0

Cosa ne pensi di questo?

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

Ho visto vari approcci, ma non l'ho mai visto, quindi sono curioso di sapere di eventuali commenti!


Questo non funziona per 1.99999999 e 1.99999998
Mehdi

@Mehdi Ho appena provato con repl.it/repls/SvelteSimpleNumerator#main.cpp e sembra che si comporti come previsto - ma forse hai una particolare implementazione del compilatore a cui fare riferimento che non lo fa?
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;
}

Ho usato questa funzione per il mio piccolo progetto e funziona, ma nota quanto segue:

L'errore di doppia precisione può creare una sorpresa per te. Diciamo epsilon = 1.0e-6, quindi 1.0 e 1.000001 NON devono essere considerati uguali in base al codice sopra, ma sulla mia macchina la funzione li considera uguali, questo perché 1.000001 non può essere tradotto con precisione in un formato binario, è probabilmente 1.0000009xxx. Lo collaudo con 1.0 e 1.0000011 e questa volta ottengo il risultato atteso.


-1

Questa è un'altra soluzione con lambda:

#include <cmath>
#include <limits>

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

Questo è esattamente lo stesso di molte altre risposte tranne per il fatto che è un lambda e non ha spiegazioni, quindi questo non aggiunge molto valore come risposta.
stijn

-2

La mia strada potrebbe non essere corretta ma utile

Converti entrambi float in stringhe e poi confronta le stringhe

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;

}

la sovrapposizione dell'operatore può anche essere eseguita


+1: hey, non ho intenzione di programmare il gioco con questo, ma l'idea di float di round-trip è venuta più volte nel blog di Bruce Dawson (trattato?: D) sulla questione, e se sei intrappolato in una stanza e qualcuno ti mette una pistola in testa e dice "ehi devi confrontare due carri armati con X cifre significative, hai 5 minuti, VAI!" questo è probabilmente uno da considerare. ;)
shelleybutterfly,

@shelleybutterfly Quindi la domanda era ancora per il modo più efficiente di confrontare due numeri in virgola mobile.
Tommy Andersen,

@TommyA lol forse, ma scommetto che il round trip è stato annullato per ragioni non legate all'efficienza. Anche se la mia intuizione è che sarebbe piuttosto inefficiente rispetto a HW FP matematica, ma dice anche che è improbabile che gli algoritmi nel software FP abbiano una grande differenza. Attendo con impazienza le analisi che hai fatto mostrando che i problemi di efficienza in quel caso sono significativi. Inoltre, a volte meno che ottimale può ancora essere una risposta preziosa, e poiché è stato sottoposto a downgrade - nonostante sia una tecnica valida che è stata persino citata dal blog di Dawson sull'argomento, quindi ho pensato che meritasse un voto.
shelleybutterfly,

-2

Non è possibile confrontare due doublecon un fisso EPSILON. A seconda del valore di double, EPSILONvaria.

Un doppio confronto migliore sarebbe:

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

In un modo più generico:

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

4
Questo metodo ha molti punti deboli, come se i numeri ae fossero bgià più piccoli di quanto la epsilon()differenza possa essere ancora significativa. Al contrario, se i numeri sono molto grandi, anche un paio di bit di errore faranno fallire il confronto anche se si volessero considerare i numeri uguali. Questa risposta è esattamente il tipo di algoritmo di confronto "generico" che si desidera evitare.
SirGuy,

-3

Perché non eseguire XOR bit a bit? Due numeri in virgola mobile sono uguali se i loro bit corrispondenti sono uguali. Penso che sia stata presa la decisione di posizionare i bit esponente prima della mantissa per accelerare il confronto tra due galleggianti. Penso che a molte risposte qui manchi il punto del confronto epsilon. Il valore di Epsilon dipende solo dalla precisione con cui vengono confrontati i numeri in virgola mobile. Ad esempio, dopo aver fatto un po 'di aritmetica con i float ottieni due numeri: 2.5642943554342 e 2.5642943554345. Non sono uguali, ma per la soluzione contano solo 3 cifre decimali, quindi sono uguali: 2.564 e 2.564. In questo caso scegli epsilon uguale a 0,001. Il confronto Epsilon è possibile anche con XOR bit a bit. Correggimi se sbaglio.


Si prega di non aggiungere la stessa risposta a più domande. Rispondi a quello migliore e contrassegna il resto come duplicati. Vedi meta.stackexchange.com/questions/104227/…
Bhargav Rao

Non credo che il "confronto epsilon" sia possibile usando solo ExOr (e uno o due confronti), anche limitato a rappresentazioni normalizzate nello stesso formato.
greybeard,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.