302

How to do assert almost equal with pytest for floats without resorting to something like:

assert x - 0.00001 <= y <= x + 0.00001

More specifically it will be useful to know a neat solution for quickly comparing pairs of float, without unpacking them:

assert (1.32, 2.4) == i_return_tuple_of_two_floats()
jdhao
  • 24,001
  • 18
  • 134
  • 273
Vladimir Keleshev
  • 13,753
  • 17
  • 64
  • 93

8 Answers8

489

I noticed that this question specifically asked about pytest. pytest 3.0 includes an approx() function (well, really class) that is very useful for this purpose.

import pytest

assert 2.2 == pytest.approx(2.3)
# fails, default is ± 2.3e-06
assert 2.2 == pytest.approx(2.3, 0.1)
# passes

# also works the other way, in case you were worried:
assert pytest.approx(2.3, 0.1) == 2.2
# passes
jdhao
  • 24,001
  • 18
  • 134
  • 273
dbn
  • 13,144
  • 3
  • 60
  • 86
  • 30
    Nice! Also found it works for sequences of numbers too e.g. `assert [0.1 + 0.2, 0.2 + 0.4] == pytest.approx([0.3, 0.6])` – Mr Kriss Mar 16 '17 at 11:46
  • 11
    @Mr Kriss And even for dicts: `assert {'a': 0.1+0.2} == pytest.approx({'a': 0.3})` – Antony Hatchkins Apr 17 '17 at 18:56
  • 7
    This doesn't work for lists of lists: for example, `assert [[0.1 + 0.2], [0.2 + 0.4]] == pytest.approx([[0.3], [0.6]])` leads to a `TypeError`. If found that Numpy's `np.testing.assert_allclose([[0.1 + 0.2], [0.2 + 0.4]], [[0.3], [0.6]])` (see answer below) did work for this case. – Kurt Peek Sep 12 '17 at 10:35
  • 6
    It's worth noting that the second positional argument is relative tolerance, but you can specify absolute tolerance as well: `0.2 == pytest.approx(0.3, 0.1) # returns false; 0.2 == pytest.approx(0.3, abs=0.1) # returns true` – Kyle Aug 12 '20 at 17:01
  • I do believe it is [now] a function. `def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:` – Felipe Alvarez Jan 19 '22 at 04:51
56

You will have to specify what is "almost" for you:

assert abs(x-y) < 0.0001

to apply to tuples (or any sequence):

def almost_equal(x,y,threshold=0.0001):
  return abs(x-y) < threshold

assert all(map(almost_equal, zip((1.32, 2.4), i_return_tuple_of_two_floats())

Update:
pytest.approx was released as part of pytest v3.0.0 in 2016.
This answer predates it, use this if:

  • you don't have a recent version of pytest AND
  • you understand floating point precision and it's impact to your use case.

for practically all common scenarios, use pytest.approx as suggested in this answer.

yurib
  • 8,043
  • 3
  • 30
  • 55
  • 9
    The question asks how to do it "without resorting to something like" this – endolith Aug 28 '16 at 02:09
  • I interpret "something like this" as a repetitive and awkward expression like `x - d <= y <= x+d`, seems like that's what OP meant as well. If you don't wish to explicitly specify the threshold for 'almost', see @jiffyclub's answer. – yurib Oct 12 '16 at 12:22
  • 2
    py.test now has a feature that does this. I've added an answer discussing it. – dbn Nov 03 '16 at 18:36
  • 2
    @NeilG Why on earth should this be deleted? If there's something obviously wrong with it please explain what it is. – user2699 Jan 05 '18 at 21:50
  • 1
    @user2699 The question is how to do this in pytest. The correct way to do it in pytest is to to use `pytest.approx`. Writing your own approximate function is a bad idea. (The one in this answer isn't even as good as the included one.) – Neil G Jan 05 '18 at 22:43
  • @NeilG, Sure, but `pytest.approx` didn't exist when this answer was posted, and probably does the exact same thing under the hood. – user2699 Jan 05 '18 at 23:42
  • @user2699 that's why this answer is no longer up to date. It's not actually what happens under the hood (there's a lot more). And anyway, no one should be reinventing what's already in pytest. – Neil G Jan 05 '18 at 23:50
  • @yurib This is a repetitive and akward expression. Though I love using built-in functions like `all`, `map`, and `zip`, in this case it's very long and doesn't read well. – pigrammer Oct 11 '22 at 14:20
  • @pigrammer there's zero repetition in this expression. and it's long and doesn't read well because it uses names from the original question and readability is subjective. you're a developer - make it readable for you.... `def myapprox(left, right): return all(map(approx, zip(left,right)))`. – yurib Oct 11 '22 at 16:49
41

If you have access to NumPy it has great functions for floating point comparison that already do pairwise comparison with numpy.testing.

Then you can do something like:

numpy.testing.assert_allclose(i_return_tuple_of_two_floats(), (1.32, 2.4))
Mike T
  • 41,085
  • 18
  • 152
  • 203
jiffyclub
  • 1,837
  • 2
  • 16
  • 9
21

These answers have been around for a long time, but I think the easiest and also most readable way is to use unittest for it's many nice assertions without using it for the testing structure.

Get assertions, ignore rest of unittest.TestCase

(based on this answer)

import unittest

assertions = unittest.TestCase('__init__')

Make some assertions

x = 0.00000001
assertions.assertAlmostEqual(x, 0)  # pass
assertions.assertEqual(x, 0)  # fail
# AssertionError: 1e-08 != 0

Implement original questions' auto-unpacking test

Just use * to unpack your return value without needing to introduce new names.

i_return_tuple_of_two_floats = lambda: (1.32, 2.4)
assertions.assertAlmostEqual(*i_return_tuple_of_two_floats())  # fail
# AssertionError: 1.32 != 2.4 within 7 places
Community
  • 1
  • 1
KobeJohn
  • 7,390
  • 6
  • 41
  • 62
  • 1
    This has the advantage to every answer above that you can use collections and assert them to be equal +1 – GuiTaek Dec 28 '22 at 21:53
15

If you want something that works not only with floats but for example Decimals you can use python's math.isclose():

# - rel_tol=0.01` is 1% difference tolerance.
assert math.isclose(actual_value, expected_value, rel_tol=0.01)
validname
  • 1,376
  • 15
  • 22
  • Here relative tolerance (or percentage difference) is convenient to use in some use cases, e.g. scienfific. – Karioki Mar 04 '19 at 02:13
14

Something like

assert round(x-y, 5) == 0

That is what unittest does

For the second part

assert all(round(x-y, 5) == 0 for x,y in zip((1.32, 2.4), i_return_tuple_of_two_floats()))

Probably better to wrap that in a function

def tuples_of_floats_are_almost_equal(X, Y):
    return all(round(x-y, 5) == 0 for x,y in zip(X, Y))

assert tuples_of_floats_are_almost_equal((1.32, 2.4), i_return_tuple_of_two_floats())
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
5

I'd use nose.tools. It plays well with py.test runner and have other equally useful asserts - assert_dict_equal(), assert_list_equal(), etc.

from nose.tools import assert_almost_equals
assert_almost_equals(x, y, places=7) #default is 7 
volodymyr
  • 7,256
  • 3
  • 42
  • 45
  • 2
    Besides pytest has an option for this I don't consider a good option add an extra depency (in this case, a whole testing framwork) only for this. – Marc Tudurí Jun 13 '17 at 12:37
1

Could just use round()

a, b = i_return_tuple_of_two_floats()
assert (1.32, 2.4) == round(a,2), round(b,1)
Pluto
  • 816
  • 10
  • 9