Domanda

Qual è il metodo migliore per confrontare i float e i double IEEE per l'uguaglianza?Ho sentito parlare di diversi metodi, ma volevo vedere cosa ne pensava la comunità.

È stato utile?

Soluzione

Penso che l’approccio migliore sia quello di confrontare Ulp.

bool is_nan(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
}

bool is_finite(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
}

// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
#define UNEQUAL_NANS 1
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
#define INFINITE_INFINITIES 1

// test whether two IEEE floats are within a specified number of representable values of each other
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
{
#ifdef UNEQUAL_NANS
    if(is_nan(lhs) || is_nan(rhs))
    {
        return false;
    }
#endif
#ifdef INFINITE_INFINITIES
    if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
    {
        return false;
    }
#endif
    signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(left < 0)
    {
        left = 0x80000000 - left;
    }
    signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(right < 0)
    {
        right = 0x80000000 - right;
    }
    if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
    {
        return true;
    }
    return false;
}

Una tecnica simile può essere utilizzata per i doppi.Il trucco è convertire i numeri in virgola mobile in modo che siano ordinati (come se fossero interi) e poi vedere quanto sono diversi.

Non ho idea del perché questa dannata cosa mi stia rovinando le sottolineature.Modificare:Oh, forse è solo un artefatto dell'anteprima.Va bene allora.

Altri suggerimenti

La versione attuale che sto utilizzando è questa

bool is_equals(float A, float B,
               float maxRelativeError, float maxAbsoluteError)
{

  if (fabs(A - B) < maxAbsoluteError)
    return true;

  float relativeError;
  if (fabs(B) > fabs(A))
    relativeError = fabs((A - B) / B);
  else
    relativeError = fabs((A - B) / A);

  if (relativeError <= maxRelativeError)
    return true;

  return false;
}

Questo sembra risolvere la maggior parte dei problemi combinando la tolleranza agli errori relativa e assoluta.L'approccio dell'ULP è migliore?Se sì, perché?

@DrPizza:Non sono un guru delle prestazioni, ma mi aspetto che le operazioni in virgola fissa siano più veloci delle operazioni in virgola mobile (nella maggior parte dei casi).

Dipende piuttosto da cosa stai facendo con loro.Un tipo a virgola fissa con lo stesso intervallo di un float IEEE sarebbe molte volte più lento (e molte volte più grande).

Cose adatte per i galleggianti:

Grafica 3D, fisica/ingegneria, simulazione, simulazione climatica....

Nei software numerici spesso vuoi verificare se due numeri in virgola mobile lo sono esattamente pari.LAPACK è pieno di esempi di casi simili.Certo, il caso più comune è quello in cui si desidera verificare se un numero in virgola mobile è uguale a "Zero", "Uno", "Due", "Metà".Se qualcuno è interessato posso scegliere alcuni algoritmi e andare più nel dettaglio.

Anche in BLAS spesso vuoi verificare se un numero in virgola mobile è esattamente Zero o Uno.Ad esempio, la routine dgemv può calcolare le operazioni del modulo

  • y = beta*y + alfa*A*x
  • y = beta*y + alfa*A^T*x
  • y = beta*y + alfa*A^H*x

Quindi se beta è uguale a Uno hai un "incarico più" e per beta è uguale a Zero un "incarico semplice".Quindi puoi sicuramente ridurre il costo computazionale se dai a questi casi (comuni) un trattamento speciale.

Certo, potresti progettare le routine BLAS in modo tale da evitare confronti esatti (ad es.utilizzando alcuni flag).Tuttavia, il LAPACK è pieno di esempi in cui ciò non è possibile.

PS:

  • Ci sono certamente molti casi in cui non vuoi verificare che "è esattamente uguale".Per molte persone questo potrebbe addirittura essere l’unico caso con cui devono confrontarsi.Voglio solo sottolineare che ci sono anche altri casi.

  • Sebbene LAPACK sia scritto in Fortran, la logica è la stessa se si utilizzano altri linguaggi di programmazione per software numerico.

Oh caro Signore, per favore non interpretare i bit float come int a meno che tu non stia utilizzando un P6 o versioni precedenti.

Anche se provoca la copia dai registri vettoriali ai registri interi tramite la memoria, e anche se blocca la pipeline, è il modo migliore per farlo che abbia mai incontrato, nella misura in cui fornisce i confronti più robusti anche di fronte degli errori in virgola mobile.

cioè.è un prezzo che vale la pena pagare.

Questo sembra risolvere la maggior parte dei problemi combinando la tolleranza agli errori relativa e assoluta.L'approccio dell'ULP è migliore?Se sì, perché?

Gli ULP sono una misura diretta della "distanza" tra due numeri in virgola mobile.Ciò significa che non richiedono che tu evochi i valori di errore relativi e assoluti, né devi assicurarti di ottenere quei valori "giusti".Con gli ULP puoi esprimere direttamente quanto vuoi che i numeri siano vicini e la stessa soglia funziona altrettanto bene sia per i valori piccoli che per quelli grandi.

Se hai errori in virgola mobile hai ancora più problemi di questo.Anche se immagino che dipenda dalla prospettiva personale.

Anche se eseguiamo l'analisi numerica per ridurre al minimo l'accumulo di errori, non possiamo eliminarlo e possiamo ottenere risultati che dovrebbero essere identici (se stessimo calcolando con i reali) ma differiscono (perché non possiamo calcolare con i reali).

Se stai cercando due float uguali, secondo me dovrebbero essere identicamente uguali.Se stai affrontando un problema di arrotondamento in virgola mobile, forse una rappresentazione in virgola fissa sarebbe più adatta al tuo problema.

Se stai cercando due float uguali, secondo me dovrebbero essere identicamente uguali.Se stai affrontando un problema di arrotondamento in virgola mobile, forse una rappresentazione in virgola fissa sarebbe più adatta al tuo problema.

Forse non possiamo permetterci la perdita di portata o di prestazioni che un simile approccio causerebbe.

@DrPizza:Non sono un guru delle prestazioni, ma mi aspetto che le operazioni in virgola fissa siano più veloci delle operazioni in virgola mobile (nella maggior parte dei casi).

@CraigH:Sicuro.Sono assolutamente d'accordo con la stampa di quello.Se a o b immagazzinano denaro, dovrebbero essere rappresentati in punto fisso.Faccio fatica a pensare a un esempio nel mondo reale in cui tale logica dovrebbe essere alleata ai galleggianti.Cose adatte per i galleggianti:

  • pesi
  • ranghi
  • distanze
  • valori del mondo reale (come da un ADC)

Per tutte queste cose, o esegui molti numeri e presenti semplicemente i risultati all'utente per l'interpretazione umana, oppure fai un'affermazione comparativa (anche se tale affermazione è "questa cosa è entro 0,001 da quest'altra cosa").Un'affermazione comparativa come la mia è utile solo nel contesto dell'algoritmo:la parte "entro 0,001" dipende da cosa fisico domanda che stai ponendo.Quello è il mio 0,02.O dovrei dire 2/100?

Dipende piuttosto da cosa stai facendo con loro.Un tipo a punto fisso con lo stesso intervallo di un galleggiante IEEE sarebbe molte volte più lento (e molte volte più grande).

Ok, ma se voglio una risoluzione in bit infinitesimamente piccola allora torno al punto originale:== e != non hanno significato nel contesto di un problema del genere.

Un int mi consente di esprimere ~ 10 ^ 9 valori (indipendentemente dall'intervallo) che sembrano sufficienti per qualsiasi situazione in cui mi preoccuperei che due di essi siano uguali.E se ciò non bastasse, utilizza un sistema operativo a 64 bit e avrai circa 10 ^ 19 valori distinti.

Posso esprimere valori in un intervallo compreso tra 0 e 10 ^ 200 (ad esempio) in un int, è solo la risoluzione in bit che soffre (la risoluzione sarebbe maggiore di 1, ma, ancora una volta, nessuna applicazione ha quel tipo di intervallo come questo tipo di risoluzione).

Per riassumere, penso che in tutti i casi uno stia rappresentando un continuum di valori, nel qual caso != e == sono irrilevanti, oppure stia rappresentando un insieme fisso di valori, che può essere mappato su un int (o un altro fisso -tipo di precisione).

Un INT mi permette di esprimere ~ 10^9 valori (indipendentemente dall'intervallo) che sembra abbastanza per qualsiasi situazione in cui mi interessa che due di loro siano uguali.E se non basta, usa un sistema operativo a 64 bit e hai circa 10^19 valori distinti.

Effettivamente ho raggiunto quel limite...Stavo cercando di destreggiarsi tra i tempi in ps e il tempo in cicli di clock in una simulazione in cui raggiungi facilmente 10 ^ 10 cicli.Qualunque cosa abbia fatto, ho superato molto rapidamente l'esiguo intervallo di numeri interi a 64 bit...10 ^ 19 non è tanto quanto pensi, dammi 128 bit di calcolo adesso!

I float mi hanno permesso di ottenere una soluzione ai problemi matematici, poiché i valori traboccavano di molti zeri nella fascia bassa.Quindi in pratica avevi un punto decimale fluttuante nel numero senza perdita di precisione (mi sarebbe piaciuto con il numero distinto più limitato di valori consentiti nella mantissa di un float rispetto a un int a 64 bit, ma avevo un disperato bisogno dell'intervallo! ).

E poi le cose vengono riconvertite in numeri interi per confrontare ecc.

Fastidioso, e alla fine ho scartato l'intero tentativo e ho fatto affidamento solo su float e < e > per portare a termine il lavoro.Non perfetto, ma funziona per il caso d'uso previsto.

Se stai cercando due float uguali, secondo me dovrebbero essere identicamente uguali.Se stai affrontando un problema di arrotondamento in virgola mobile, forse una rappresentazione in virgola fissa sarebbe più adatta al tuo problema.

Forse dovrei spiegare meglio il problema.In C++, il seguente codice:

#include <iostream>

using namespace std;


int main()
{
  float a = 1.0;
  float b = 0.0;

  for(int i=0;i<10;++i)
  {
    b+=0.1;
  }

  if(a != b)
  {
    cout << "Something is wrong" << endl;
  }

  return 1;
}

stampa la frase "Qualcosa non va".dici che dovrebbe?

Oh caro Signore, per favore non interpretare i bit float come int a meno che tu non stia utilizzando un P6 o versioni precedenti.

è il modo migliore che abbia mai incontrato per farlo, nella misura in cui fornisce i confronti più robusti anche a fronte di errori in virgola mobile.

Se hai errori in virgola mobile hai ancora più problemi di questo.Anche se immagino che dipenda dalla prospettiva personale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top