J'ai une variable double
appelée x
. Dans le code, x
reçoit la valeur 0.1
et je la vérifie dans une instruction 'if' comparant x
et 0.1
if (x==0.1)
{
----
}
Malheureusement, il n’entre pas la déclaration if
Devrais-je utiliser Double
ou double
?
Quelle est la raison derrière cela? Pouvez-vous suggérer une solution pour cela?
C'est un problème standard dû à la manière dont l'ordinateur stocke les valeurs en virgule flottante. Cherchez ici "problème de virgule flottante" et vous trouverez des tonnes d'informations.
En bref - un float/double ne peut pas stocker 0.1
avec précision. Ce sera toujours un peu éteint.
Vous pouvez essayer d'utiliser le type decimal
qui stocke les nombres en notation décimale. Ainsi, 0.1
sera représentable avec précision.
Vous vouliez connaître la raison:
Les valeurs float/double sont stockées sous forme de fractions binaires et non de fractions décimales. Pour illustrer:
12.34
en notation décimale (ce que nous utilisons) signifie
2
:10.01
signifie1/3
en notation décimale est0.3333333…
. La même chose se passe en notation binaire, sauf que les nombres qui ne peuvent pas être représentés avec précision sont différents. Parmi eux se trouve le numéro1/10
. En notation binaire, c'est0.000110011001100…
.Comme la notation binaire ne peut pas la stocker avec précision, elle est stockée de manière arrondie. D'où votre problème.
double
et Double
sont identiques (double
est un alias pour Double
) et peut être utilisé indifféremment.
Le problème avec la comparaison d'un double avec une autre valeur est que les doubles sont des valeurs approximatives, et non des valeurs exactes. Ainsi, lorsque vous définissez x
sur 0.1
, il peut en réalité être stocké sous la forme 0.100000001
ou quelque chose du genre.
Au lieu de vérifier l’égalité, vous devez vérifier que la différence est inférieure à une différence minimale définie (tolérance). Quelque chose comme:
if (Math.Abs(x - 0.1) < 0.0000001)
{
...
}
Vous avez besoin d'une combinaison de Math.Abs
sur X-Y
et d'un value
pour pouvoir la comparer.
Vous pouvez utiliser la méthode de la méthode d'extension suivante
public static class DoubleExtensions
{
const double _3 = 0.001;
const double _4 = 0.0001;
const double _5 = 0.00001;
const double _6 = 0.000001;
const double _7 = 0.0000001;
public static bool Equals3DigitPrecision(this double left, double right)
{
return Math.Abs(left - right) < _3;
}
public static bool Equals4DigitPrecision(this double left, double right)
{
return Math.Abs(left - right) < _4;
}
...
Comme vous appelez rarement des méthodes sur double sauf ToString
je crois que c'est une extension relativement sûre.
Ensuite, vous pouvez comparer x
et y
comme
if(x.Equals4DigitPrecision(y))
La comparaison des nombres en virgule flottante ne peut pas toujours être effectuée précisément à cause de l'arrondissement. Comparer
(x == .1)
l'ordinateur compare vraiment
(x - .1) vs 0
Le résultat de la sybtraction ne peut pas toujours être représenté précisément à cause de la façon dont le nombre à virgule flottante est représenté sur la machine. Par conséquent, vous obtenez une valeur différente de zéro et la condition est évaluée à false
.
Pour surmonter cette comparaison
Math.Abs(x- .1) vs some very small threshold ( like 1E-9)
De la documentation :
Précision dans les comparaisons La méthode Equals doit être utilisée avec prudence, car deux valeurs apparemment équivalentes peuvent être inégales en raison de la précision différente des deux valeurs. L'exemple suivant indique que la valeur Double .3333 et le Double renvoyés en divisant 1 par 3 sont inégaux.
...
Plutôt que de comparer l’égalité, une technique recommandée consiste à définir une marge de différence acceptable entre deux valeurs (telle que 0,01% d’une des valeurs). Si la valeur absolue de la différence entre les deux valeurs est inférieure ou égale à cette marge, la différence est probablement due à des différences de précision et, par conséquent, les valeurs sont probablement égales. L'exemple suivant utilise cette technique pour comparer .33333 et 1/3, les deux valeurs Double que l'exemple de code précédent a trouvées inégal.
Donc si vous avez vraiment besoin d'un double, vous devriez utiliser la technique décrite dans la documentation ..__ Si possible, changez-le en décimal. Ce sera plus lent, mais vous n'aurez pas ce type de problème.
Double et double sont identiques.
Pour la raison, voir http://www.yoda.arachsys.com/csharp/floatingpoint.html . En bref: un double n'est pas un type exact et une différence minute entre "x" et " 0,1 "va le jeter.
On sait que la comparaison exacte des valeurs en virgule flottante ne fonctionne pas toujours en raison des problèmes d’arrondi et de représentation interne.
Essayez une comparaison imprécise:
if (x >= 0.099 && x <= 0.101)
{
}
L'autre alternative consiste à utiliser le type de données décimal.
Utilisez decimal
. Il n'a pas ce "problème".
En vous appuyant sur la base de code Java, essayez d'utiliser .CompareTo
et testez la comparaison à zéro. Cela suppose que la fonction .CompareTo
prend en compte l'égalité de virgule flottante de manière précise. Par exemple,
System.Math.PI.CompareTo(System.Math.PI) == 0
Ce prédicat doit renvoyer true
.
1) Devrais-je utiliser double ou double ???
Double
et double
est la même chose. double
est juste un mot-clé C # fonctionnant comme un alias pour la classe System.Double
La chose la plus courante est d'utiliser les alias! Idem pour string
(System.String
), int
(System.Int32
)
Voir aussi Tableau des types intégrés (référence C #)
Double (appelé float dans certaines langues) est une source de problèmes liés aux problèmes d'arrondis. Il n'est utile que si vous avez besoin de valeurs approximatives.
Le type de données décimal fait ce que vous voulez.
Pour référence, les valeurs décimales et décimales sont identiques dans .NET C #, de même que les types double et double, ils font tous deux référence au même type (décimal et double sont très différents, comme vous l'avez vu).
Notez que le type de données Decimal comporte des coûts, utilisez-le avec prudence si vous recherchez des boucles, etc.
// number of digits to be compared
public int n = 12
// n+1 because b/a tends to 1 with n leading digits
public double Epsilon { get; } = Math.Pow(10, -(n+1));
public bool IsEqual(double a, double b)
{
if (Math.Abs(a)<= double.Epsilon || Math.Abs(b) <= double.Epsilon)
return Math.Abs(a - b) <= double.Epsilon;
return Math.Abs(1.0 - a / b) <= Epsilon;
}
En règle générale:
La double représentation suffit dans la plupart des cas mais peut échouer lamentablement dans certaines situations. Utilisez des valeurs décimales si vous avez besoin d'une précision complète (comme dans les applications financières).
La plupart des problèmes avec les doubles ne proviennent pas de la comparaison directe, mais résultent généralement de l’accumulation de plusieurs opérations mathématiques qui perturbent de manière exponentielle la valeur en raison d’arrondis et d’erreurs fractionnaires (en particulier avec des multiplications et des divisions).
Vérifiez votre logique, si le code est:
x = 0.1
if (x == 0.1)
cela ne devrait pas échouer, c'est trop simple pour échouer, si la valeur X est calculée par des moyens ou des opérations plus complexes, il est fort possible que la méthode ToString utilisée par le débogueur utilise un arrondi intelligent, vous pouvez peut-être faire de même (si c'est trop risqué retour à la décimale):
if (x.ToString() == "0.1")
les représentations de nombres en virgule flottante sont notoirement inexactes (en raison de la manière dont les flottants sont stockés en interne), par ex. x peut en réalité être 0.0999999999 ou 0.100000001 et votre condition échouera. Si vous voulez déterminer si les flottants sont égaux, vous devez préciser s'ils sont égaux à une certaine tolérance.
c'est à dire.
if(Math.Abs(x) - 0.1 < tol) {
// Do something
}