È un buon modo per confrontare due numeri?

2

Se abbiamo numeri doppi, diciamo che voglio vedere se qualche parametro doppio è uguale a zero che viene passato come doppio:

public bool AlmostEqual(double x, double y)
{
    double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
    return Math.Abs(x - y) <= epsilon;
}

considerando i doppi spettacoli 15 cifre accurate. Quindi pensi che quello che ho scritto sia buono? o ha problemi?

bene, allora ho appena scritto un metodo semplice come questo:

public bool ReallyEqual(double x, double y)
{
    return (x == y);
}

e quindi non sono riuscito a trovare un caso in cui i valori di ritorno di questi due metodi sono diversi. stavano sempre restituendo lo stesso risultato. :(

    
posta Blake 21.12.2012 - 16:34
fonte

2 risposte

7

Conoscendo il formato decimale e lavorando con un numero corretto, non è difficile trovare due valori con valori diversi.

using System;

public class Example
{
   public static void Main()
   {
      double[] values = { 
        //12345678901234567
        0.10000000000000001,
        0.10000000000000002
        };
      Console.WriteLine("{0:r} = {1:r}: =:{2} ae:{3} re:{4} hmd1:{5} hmd0:{6}", 
                        values[0], values[1],  
                        values[0].Equals(values[1]),
                        AlmostEqual(values[0], values[1]),
                        ReallyEqual(values[0], values[1]),
                        HasMinimalDifference(values[0], values[1], 1),
                        HasMinimalDifference(values[0], values[1], 0));
      Console.WriteLine();
   }

   public static bool AlmostEqual(double x, double y) {
      double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
      return Math.Abs(x - y) <= epsilon;
   }

   public static bool ReallyEqual(double x, double y) {
      return (x == y);
   }

   public static bool HasMinimalDifference(double value1, double value2, int units) {
      long lValue1 = BitConverter.DoubleToInt64Bits(value1);
      long lValue2 = BitConverter.DoubleToInt64Bits(value2);

      // If the signs are different, return false except for +0 and -0. 
      if ((lValue1 >> 63) != (lValue2 >> 63))
      {
         if (value1 == value2)
            return true;

         return false;
      }

      long diff = Math.Abs(lValue1 - lValue2);
      Console.WriteLine("HMD: {0} {1} {2} {3}",
                        lValue1, lValue2, diff, units);

      if (diff <= (long) units)
         return true;

      return false;
   }
}

(Se vuoi giocare con questo codice online, è link )

Qui ci sono tre funzioni, l'Almost Equal, il Really Equal e HasMinimalDifference (dal Double.Equals Method (Doppio) @msdn )

Nota che i numeri stessi che vengono testati sono quasi gli stessi, e quando guardi l'output dei numeri da HasMinimalDifference sono solo diversi di un bit nella rappresentazione.

L'output di questo programma è:

HMD: 4591870180066957722 4591870180066957723 1 1
HMD: 4591870180066957722 4591870180066957723 1 0
0.1 = 0.10000000000000002: =:False ae:True re:False hmd1:True hmd0:False

Puoi vedere le due invocazioni di HasHminimalDistance con gli argomenti delle due unità (1 e 0). Solo l'ultimo numero tra i due è diverso e se chiami HasHminimalDistance con 0 come terzo parametro equivale a un costoso .Equals .

Con questi numeri:

  • Equals è falso
  • AlmostEquals è true
  • ReallyEqual è falso
  • HasHminimalDistance che consente 1 unità di slop è true
  • HasHminimalDistance che consente 0 unità di slop è falso

È possibile ottenere due numeri in virgola mobile veramente simili e si tratta di una domanda se si desidera considerarli uguali o simili o meno. Alcune applicazioni sono due cose uguali se sono all'interno di una certa tolleranza, altre applicazioni richiedono che i valori esatti siano uguali. L'insieme successivo di applicazioni potrebbe desiderare di prendere in considerazione classi matematiche esatte come BigDecimal per le situazioni in cui si desidera lavorare con valori esatti che non accidentalmente ottengano qualche imprecisione in virgola mobile che lo arrotonda e un valore esattamente uguale errato si propaghi attraverso il sistema.

Una cosa da considerare qui con la domanda come è scritta è per confronti con zero che trattano con un numero piuttosto speciale - zero.

Se cambiamo i numeri a cui guardiamo:

  double[] values = { 
    //12345678901234567
    0,
    Double.Epsilon
    };

Vedremo:

HMD: 0 1 1 1
HMD: 0 1 1 0
0 = 4.94065645841247E-324: =:False ae:False re:False hmd1:True hmd0:False

Quando guardiamo AlmostEqual(0, Double.Epsilon) vediamo che epsilon (la var locale) ha un valore di 0 ( Double.Epsilon * 1E-15 == 0). Quindi Math.abs(x-y) è uguale a Double.Epsilon, che è maggiore di 0. Quindi, per 0, l'unico modo per questo tipo di approccio è true è se il numero è identico a 0 - non solo molto vicino.

La chiamata HasMinimalDifference , tuttavia, sta osservando la differenza nei bit e può consentire che 0 e 1 ( Double.Epsilon cast su un valore long) siano abbastanza vicini.

    
risposta data 22.03.2014 - 03:31
fonte
2

Da Confronto di numeri in virgola mobile, edizione 2012

There is no silver bullet. You have to choose wisely.

If you are comparing against zero, then relative epsilons and ULPs based comparisons are usually meaningless. You’ll need to use an absolute epsilon, whose value might be some small multiple of FLT_EPSILON and the inputs to your calculation. Maybe.

If you are comparing against a non-zero number then relative epsilons or ULPs based comparisons are probably what you want. You’ll probably want some small multiple of FLT_EPSILON for your relative epsilon, or some small number of ULPs. An absolute epsilon could be used if you knew exactly what number you were comparing against.

If you are comparing two arbitrary numbers that could be zero or non-zero then you need the kitchen sink. Good luck and God speed.

Lavorare con i float non è banale, quindi ti invito a leggere di più su come funzionano. Quello che ho citato fa parte di una lunga serie di articoli che spiegano le complessità e le complessità del lavoro con i float. Ecco il primo post . Lo trovo più digeribile di Ciò che ogni scienziato informatico dovrebbe sapere sui numeri in virgola mobile .

    
risposta data 22.03.2014 - 03:51
fonte

Leggi altre domande sui tag