Comment puis-je comparer correctement les valeurs doubles pour l'égalité dans un test unitaire?

20

J'ai récemment conçu un module de série chronologique où ma série chronologique est essentiellement un SortedDictionnary<DateTime, double>.

Maintenant, je voudrais créer des tests unitaires pour m'assurer que ce module fonctionne toujours et produit le résultat attendu.

Une opération courante consiste à calculer les performances entre les points de la série temporelle.

Donc, ce que je fais, c'est créer une série chronologique avec, disons, {1.0, 2.0, 4.0} (à certaines dates), et je m'attends à ce que le résultat soit {100%, 100%}.

Le fait est que si je crée manuellement une série temporelle avec les valeurs {1.0, 1.0} et que je vérifie l'égalité (en comparant chaque point), le test ne passera pas, car il y aura toujours des inexactitudes lors du travail avec des représentations binaires de réels Nombres.

J'ai donc décidé de créer la fonction suivante:

private static bool isCloseEnough(double expected, double actual, double tolerance=0.002)
{
    return squaredDifference(expected, actual) < Math.Pow(tolerance,2);
}

Existe-t-il une autre manière courante de traiter un tel cas?

SRKX
la source

Réponses:

10

Je peux penser à deux autres façons de résoudre ce problème:

Vous pouvez utiliser Is.InRange:

Assert.That(result, Is.InRange(expected-tolerance, expected+tolerance));

Vous pouvez utiliser Math.Round:

Assert.That(Math.Round(result, sigDigits), Is.EqualTo(expected));

Je pense que les deux façons sont plus expressives qu'une fonction dédiée, car le lecteur peut voir précisément ce qui se passe avec votre numéro avant qu'il ne soit comparé à la valeur attendue.

dasblinkenlight
la source
2
Juste une note que cette réponse est spécifique à NUnit et présente le modèle d'assertion "basé sur la contrainte". Le modèle d'assertion classique ressemblerait à: Assert.AreEqual (attendu, réel, tolérance);
RichardM
1
@RichardM: postez-le comme réponse et je le sélectionnerai pour l'accepter.
SRKX
La réponse de @dasblinkenlight est correcte, ajoutant simplement quelques détails (car cela peut ne pas être clair - le modèle d'assertion classique est également NUnit). D'autres cadres de test (pas MSTest) ont probablement leur propre modèle d'assertion pour gérer les valeurs à virgule flottante.
RichardM
1
Assert.That(result, Is.InRange(expected-tolerance, expected+tolerance));échouera si tolerance/abs(expected) < 1E-16.
quant_dev
@quant_dev Vous avez absolument raison. Comme OP parle de calculer les rendements en pourcentage, j'ai supposé que ce abs(expected)serait un à deux chiffres. J'ai également supposé la tolérance au voisinage de 1E-9. Sous ces hypothèses, cette approche certes simpliste pourrait vous servir raisonnablement bien (j'utilise Is.InRangedans mes tests).
dasblinkenlight
6

Comme RichardM l'a suggéré, si vous n'utilisez pas NUnit, la meilleure façon semble être d'utiliser Assert.AreEqual (double, double, double) , où le dernier est la précision.

SRKX
la source
3

Cela dépend de ce que vous faites avec les chiffres. Si vous testez une méthode qui est censée, par exemple, sélectionner une valeur appropriée dans un ensemble d'entrées en fonction de certains critères, vous devez tester une stricte égalité. Si vous effectuez des calculs à virgule flottante, vous devrez généralement effectuer un test avec une tolérance non nulle. La taille de la tolérance dépend des calculs, mais avec une double précision, un bon point de départ est de choisir la tolérance relative 1E-14 pour les calculs simples et 1E-8 (tolérance) pour les calculs plus compliqués. YMMV bien sûr, et vous devez ajouter une petite tolérance absolue si le résultat attendu est 0.

quant_dev
la source