37

Is there a function to test floating point approximate equality in python? Something like,

 def approx_equal(a, b, tol): return abs(a - b) < tol 

My use case is similar to how Google's C++ testing library, gtest.h, defines EXPECT_NEAR.

Here is an example:

def bernoulli_fraction_to_angle(fraction): return math.asin(sqrt(fraction)) def bernoulli_angle_to_fraction(angle): return math.sin(angle) ** 2 def test_bernoulli_conversions(): assert(approx_equal(bernoulli_angle_to_fraction(pi / 4), 0.5, 1e-4)) assert(approx_equal( bernoulli_fraction_to_angle(bernoulli_angle_to_fraction(0.1)), 0.1, 1e-4)) 
6
  • 11
    I think you just wrote it! Commented Oct 26, 2010 at 23:41
  • :) I just figured they might be a module for this... Commented Oct 26, 2010 at 23:49
  • 1
    Well, one potential problem is that tol is likely only approximately equal to the value you want. Commented Oct 27, 2010 at 1:07
  • 4
    You're not supposed to compare to a single tolerance value, since very large numbers and very small numbers have different tolerances. Numpy's spacing() function will tell you the distance between a given floating point number and the nearest floating point number, which might be a way to make a good test. Commented Jan 13, 2013 at 0:25
  • 2
    The documentation for Numpy's spacing function is at github.com/numpy/numpy/blob/master/numpy/core/code_generators/… (it doesn't show up in normal Google searches). Commented Jun 12, 2013 at 15:40

6 Answers 6

53
  • For comparing numbers, there is math.isclose.
  • For comparing numbers or arrays, there is numpy.allclose.
  • For testing numbers or arrays, there is numpy.testing.assert_allclose
Sign up to request clarification or add additional context in comments.

2 Comments

This is actually just a redirect to unittest's assertAlmostEqual, so you can use it even if you're not using nose.
If you want to compare arrays and to get the result of all the comparisons number by number: [True, False, ...] numpy.isclose() can be used as well.
19

Another approach is to compute the relative change (or relative difference) of the two numbers, which is "used to compare two quantities while taking into account the 'sizes' of the things being compared". The two formulas mentioned in the Wikipedia article could be used in comparisons like the following in Python, which also handle cases where one or both of the values being compared are zero:

def approx_equal(a, b, tol): return abs(a-b) <= max(abs(a), abs(b)) * tol def approx_equal(a, b, tol): return abs(a-b) <= (abs(a)+abs(b))/2 * tol 

The calculated value in either case is a unitless fraction. In the first case the baseline value is the maximum absolute value of the two numbers and in the second it's their mean absolute value. The article discusses each in more detail as well as their pros and cons. The latter can turned into a percentage difference if multiplied by 100 before the comparison (with tol becoming a percentage value). Note that the article suggests that if the changing value "is a percentage itself, it is better to talk about its change by using percentage points" — i.e. absolute change.

Both of these methods (obviously) require a little more computation than simply taking the absolute value of the difference of the two numbers, which might be a consideration.

11 Comments

max(abs(a)-abs(b)) reduces to max(single_value) which raises a TypeError, as max() expects an iterable.
You're welcome. The Internet knows all The Internet sees all. :-)
Note: You get ZeroDivisionError if you do approx_equal(0, 0, .00001) even though comparing for equal works with assertAlmostEqual
rather than dividing by max(abs(a), abs(b)) which can be zero, you should move this term to the right side, i.e. require < tol * max(abs(a), abs(b))
@Andre: Not quite, the < must also be changed to <=. Regardless that's an excellent suggestion -- answer updated. Thanks.
|
8

Is there a function to test floating point approximate equality in python?

There can't be a function, since the definition depends on context.

def eq( a, b, eps=0.0001 ): return abs(a - b) <= eps 

Doesn't always work. There are circumstances where

def eq( a, b, eps=0.0001 ): return abs( a - b ) / abs(a) <= eps 

could be more appropriate.

Plus, there's the always popular.

def eq( a, b, eps=0.0001 ): return abs(math.log( a ) - math.log(b)) <= eps 

Which might be more appropriate.

I can't see how you can ask for a (single) function do combine all the mathematical alternatives. Since it depends on the application.

4 Comments

Since he has to implement it himself anyway, can't he just implement it appropriately for his application? You're basically answering his question with a question.
"Is there a function to test floating point approximate equality in python?" That question -- as asked -- is a bad question. The answer is a useless "No". Since the question is bad, we have to move on to a related question like "why not?" The answer to the more useful related question is "There can't be a function". As in singular. This isn't built-in because there's no point in building it in. You always have to implement it yourself. The "answer" is a useless "no, there is no function" which the questioner already knew.
Do no specify arbitrary epsilon values. This is defined: from sys.float_info import epsilon
@Vince The value of epsilon you use necessarily depends on the circumstances surrounding your comparison — in particular, you cannot just use machine epsilon (sys.float_info.epsilon or FLT/DBL_EPSILON in C) everywhere (which is a common mistake). Yes, you can multiply an arbitrary value by machine epsilon to compute something suitable if that makes sense, but please don't advocate just using machine epsilon on its own.
5

If I were you, I'd just use what you wrote, and either put it in a separate module (perhaps with other utilities you need that Python doesn't have an implementation for) or at the top of whatever code requires it.

You can also use a lambda expression (one of my favorite language features, but probably less clear):

approx_equal = lambda a, b, t: abs(a - b) < t 

7 Comments

That's very nice, thanks. (Will leave this question open for a day just in case.)
@NeilG: okay, let's see if a guru can descend on this question and find a more elegant solution :D
no reason to use a lambda here, points are not awarded for shorter code, and it is otherwise exactly the same code. you even gave it a name...
@Rafe Kettler - yes, but think of it like a narcotic. Sure, you love them. But one day you'll wake up on a concrete floor in a pool of your own sweat and think "why does this code only make sense when I read it backwards?"
In the Python language, I've found it is better to use the "def" keyword to define named functions. I also use "lambda" frequently but only in cases where there is no need to define a name, such as the key parameter on the min() function. I often define named functions inside of named functions as a way to name the bodies of for-loops! You will read elsewhere that lambdas are severely restricted in Python to only be a single expression, which is doubly bad in Python where statements and expressions are two different things. Use lambdas only where you don't need a name.
|
4

Comparing floats for equality is just usually a bad idea. Even with the tolerance feature you're using, this isn't really what you want to do.

If you want to use floats, a reasonable option is to refactor your algorithm to use inequalities, a < b because this is more likely to do what you expect, with far fewer false negatives or positives, and most importantly, it means you don't have to guess how equal they must be for them to be equal.

If you can't do that, another option is to use an exact representation. If your algorithm is composed only of arithmetic operations (+, -, * and /) then you can use a rational represntation, as provided by fractions.Fraction, or maybe decimal.Decimal is what you want (for instance, with financial calculations).

If your algorithm cannot be expressed easily with an arbitrary precision representation, another choice is to manage the roundoff error explicitly with interval arithmetic, for instance with this module.

2 Comments

what about in tests where you calculate a value in two different ways?
In that specific case, interval math is the way to go. each operation tracks the amount of round-off error it will cause.
0

According to the tutorial:

... Though the numbers cannot be made closer to their intended exact values, the round() function can be useful for post-rounding so that results with inexact values become comparable to one another...

Therefore, this is the way that I define "isclose" functions in Python:

def isclose(a, b, ndigits): return round(a-b, ndigits) == 0 

I usually use 5 as ndigits; However, it depends on the precision that you expect.

1 Comment

Thanks for your answer You should read the pep that lead to isclose being adopted

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.