64

I have a class Vector that represents a point in 3-dimensional space. This vector has a method normalize(self, length = 1) which scales the vector down/up to be length == vec.normalize(length).length.

The unittest for this method sometimes fails because of the imprecision of floating-point numbers. My question is, how can I make sure this test does not fail when the methods are implemented correctly? Is it possible to do it without testing for an approximate value?



Additional information:

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

This sometimes results in either AssertionError: 4.999999999999999 != 5 or AssertionError: 5.000000000000001 != 5.

Note: I am aware that the floating-point issue may be in the Vector.length property or in Vector.normalize().

Niklas R
  • 16,299
  • 28
  • 108
  • 203
  • 1
    try rounding it to less decimal places – JBernardo Jan 19 '12 at 15:54
  • 1
    @JBernardo: That's bad advice -- small differences in the original values can still result in the rounded values being different. – Sven Marnach Jan 19 '12 at 16:05
  • 1
    round() also does different things in python 2.x vs 3.x. See http://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior and http://python-future.org/future-builtins.html?highlight=round – nu everest Jan 25 '16 at 16:52

4 Answers4

110

1) How can I make sure the test works?

Use assertAlmostEqual, assertNotAlmostEqual.

From the official documentation:

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

Test that first and second are approximately equal by computing the difference, rounding to the given number of decimal places (default 7), and comparing to zero.

2) Is it possible to do it without testing for an approximate value?

Esentially No.

The floating point issue can't be bypassed, so you have either to "round" the result given by vec.normalize or accept an almost-equal result (each one of the two is an approximation).

Community
  • 1
  • 1
Rik Poggi
  • 28,332
  • 6
  • 65
  • 82
  • Sure, but the OP is specifically asking if it can be done "**without** testing for an approximate value" (emphasis theirs). – NPE Jan 19 '12 at 16:02
  • @aix: But it is the nicest answer under the ones that recommend using an approximate comparison. I will just check out the approach of jcollado, but I think I will fall back to this method as the `decimal.Decimal` approach seems to be impractical. – Niklas R Jan 19 '12 at 16:28
  • 1
    @aix: he asked if it's possible without an approximation, and the answer to that is no, because from one side or the other he have to deal with the representation of a floating point. – Rik Poggi Jan 19 '12 at 16:29
  • (+1) Fair enough. I see that the answer has undergone significant editing since I made that remark. – NPE Jan 19 '12 at 16:34
  • Perfect answer, especially after your edit. :) +1, check-marked. – Niklas R Jan 19 '12 at 16:43
  • @NiklasR: Thanks! I'm fairly new to SO, so a compliment like yours means a lot :) – Rik Poggi Jan 19 '12 at 17:14
4

By using a floating point value, you accept a small possible imprecision. Therefore, your tests should test if your computed value falls in an acceptable range such as:

theoreticalValue - epsilon < normalizedValue < theoreticalValue + epsilon

where epsilon is a very small value that you define as acceptable for a variation due to floating point imprecision.

anthonyvd
  • 7,329
  • 4
  • 30
  • 51
1

I suppose one possibility is to apply the function to test cases for which all inputs, the results of all intermediate calculations, and the output are exactly representable by float.

To illustrate:

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

Not sure how practical this is though...

NPE
  • 486,780
  • 108
  • 951
  • 1,012
1

In general, you should not assert equality for floats. Instead, ensure that the result is within certain bounds, e.g.:

self.assertTrue(abs(vec.normalize(5).length - 5) < 0.001)
Thomas Lötzer
  • 24,832
  • 16
  • 69
  • 55
  • 1
    Warning: this can raise weird exceptions like _"AssertionError: False is not true"_! – Pete Nov 19 '12 at 21:49