web-dev-qa-db-fra.com

Unittest (parfois) échoue à cause d'une imprécision en virgule flottante

J'ai une classe Vector qui représente un point dans un espace tridimensionnel. Ce vecteur a une méthode normalize(self, length = 1) qui réduit le vecteur à length == vec.normalize(length).length.

Le testeur pour cette méthode parfois échoue à cause de l'imprécision des nombres à virgule flottante. Ma question est la suivante: comment puis-je m'assurer que ce test n'échoue pas lorsque les méthodes sont correctement implémentées? Est-il possible de le fairesanstester une valeur approximative?



Information additionnelle :

    def testNormalize(self):
        vec = Vector(random.random(), random.random(), random.random())
        self.assertEqual(vec.normalize(5).length, 5)

Ceciparfoisrésulte en AssertionError: 4.999999999999999 != 5 ou AssertionError: 5.000000000000001 != 5.

Note: Je suis conscient que le problème de virgule flottante peut être dans la propriété Vector.length ou dans Vector.normalize().

52
Niklas R

1) Comment puis-je m'assurer que le test fonctionne?

Utilisez assertAlmostEqual, assertNotAlmostEqual.

De la documentation officielle :

assertAlmostEqual(first, second, places=7, msg=None, delta=None)

Vérifiez que les première et seconde sont approximativement égales en calculant la différence, en arrondissant au nombre de décimales donné (7 par défaut) et en effectuant une comparaison à zéro.

2) Est-il possible de le faire sans tester une valeur approximative?

Essentiellement Non.

Le problème de virgule flottante ne peut pas être ignoré, vous devez donc "arrondir" le résultat donné par vec.normalize ou accepter un résultat presque égal (chacun des deux est une approximation).

97
Rik Poggi

En utilisant une valeur à virgule flottante, vous acceptez une petite imprécision possible. Par conséquent, vos tests doivent vérifier si votre valeur calculée se situe dans une plage acceptable, telle que:

theoreticalValue - epsilon < normalizedValue < theoreticalValue + epsilon

epsilon est une très petite valeur que vous définissez comme acceptable pour une variation due à une imprécision en virgule flottante.

2

En général, vous ne devriez pas affirmer l'égalité pour les flottants. Au lieu de cela, assurez-vous que le résultat se situe dans certaines limites, par exemple:

self.assertTrue(abs(vec.normalize(5).length - 5) < 0.001)
1
Thomas Lötzer

Je suppose qu’une possibilité consiste à appliquer la fonction à des cas de test pour lesquels toutes les entrées, les résultats de tous les calculs intermédiaires et la sortie sont exactement représentables par float.

Pour illustrer:

In [2]: import math

In [4]: def norm(x, y):
   ...:     return math.sqrt(x*x + y*y)
   ...: 

In [6]: norm(3, 4) == 5
Out[6]: True

Je ne sais pas si c'est pratique, cependant ...

0
NPE