How to perform unittest for floating point outputs? - python

24,225

Solution 1

The precision of float in Python is dependent on the underlying C representation. From Tutorial/Floating Point Arithmetic: Issues and Limitations, 15.1:

Almost all machines today (November 2000) use IEEE-754 floating point arithmetic, and almost all platforms map Python floats to IEEE-754 “double precision”.


As for testing, a better idea is to use existing functionality, e.g. TestCase.assertAlmostEqual:

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

Test that first and second are approximately (or not approximately) equal by computing the difference, rounding to the given number of decimal places (default 7), and comparing to zero. If delta is supplied instead of places then the difference between first and second must be less or equal to (or greater than) delta.

Example:

import unittest

def div(x, y): return x / float(y)

class Testdiv(unittest.TestCase):
    def testdiv(self):
        self.assertAlmostEqual(div(1, 9), 0.1111111111111111)
        self.assertAlmostEqual(div(1, 9), 0.1111, places=4)

unittest.main() # OK

If you prefer to stick to assert statement, you could use the math.isclose (Python 3.5+):

import unittest, math

def div(x, y): return x / float(y)

class Testdiv(unittest.TestCase):
    def testdiv(self):
        assert math.isclose(div(1, 9), 0.1111111111111111)

unittest.main() # OK

The default relative tolerance of math.close is 1e-09, "which assures that the two values are the same within about 9 decimal digits.". For more information about math.close see PEP 485.

Solution 2

The unittest.TestCase class has specific methods for comparing floats: assertAlmostEqual and assertNotAlmostEqual. To quote the documentation:

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

Test that first and second are approximately (or not approximately) equal by computing the difference, rounding to the given number of decimal places (default 7), and comparing to zero. Note that these methods round the values to the given number of decimal places (i.e. like the round() function) and not significant digits.

If delta is supplied instead of places then the difference between first and second must be less or equal to (or greater than) delta.

Thus, you could test the function like this:

self.assertAlmostEqual(div(1, 9), 0.1111111111111111)  # round(a-b, 7) == 0
self.assertAlmostEqual(div(1, 9), 0.1111, 4)           # round(a-b, 4) == 0

On a side note, unless you use pytest as a tests runner, you should prefer the TestCase.assert* methods to bare assert statements, as the test failure messages produced by the methods are generally much more informative.

Share:
24,225
alvas
Author by

alvas

食飽未?

Updated on July 24, 2022

Comments

  • alvas
    alvas over 1 year

    Let's say I am writing a unit test for a function that returns a floating point number, I can do it as such in full precision as per my machine:

    >>> import unittest
    >>> def div(x,y): return x/float(y)
    ... 
    >>>
    >>> class Testdiv(unittest.TestCase):
    ...     def testdiv(self):
    ...             assert div(1,9) == 0.1111111111111111
    ... 
    >>> unittest.main()
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    Will the same full floating point precision be the same across OS/distro/machine?

    I could try to round off and do a unit test as such:

    >>> class Testdiv(unittest.TestCase):
    ...     def testdiv(self):
    ...             assert round(div(1,9),4) == 0.1111
    ... 
    >>>
    

    I could also do an assert with log(output) but to keep to a fix decimal precision, I would still need to do rounding or truncating.

    But what other way should one pythonically deal with unittesting for floating point output?