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.