snakile - 6 months ago 57

Python Question

The assertAlmostEqual(x, y) method in Python's unit testing framework tests whether

`x`

`y`

The problem with

`assertAlmostEqual()`

`assertAlmostEqual()`

For instance, let

`x = 0.1234567890`

`y = 0.1234567891`

`x`

`y`

`self.assertAlmostEqual(x, y)`

`True`

`assertAlmostEqual()`

I'm looking for a more generic

`assertAlmostEquals()`

`True`

- .
`self.assertAlmostEqual_generic([x, x, x], [y, y, y])`

- .
`self.assertAlmostEqual_generic({1: x, 2: x, 3: x}, {1: y, 2: y, 3: y})`

- .
`self.assertAlmostEqual_generic([(x,x)], [(y,y)])`

Is there such a method or do I have to implement it myself?

- has an optional parameter named
`assertAlmostEquals()`

and the numbers are compared by computing the difference rounded to number of decimal`places`

. By default`places`

, hence`places=7`

is False while`self.assertAlmostEqual(0.5, 0.4)`

is True. My speculative`self.assertAlmostEqual(0.12345678, 0.12345679)`

should have the same functionality.`assertAlmostEqual_generic()`

- Two lists are considered almost equal if they have almost equal numbers in exactly the same order. formally, .
`for i in range(n): self.assertAlmostEqual(list1[i], list2[i])`

- Similarly, two sets are considered almost equal if they can be converted to almost equal lists (by assigning an order to each set).
- Similarly, two dictionaries are considered almost equal if the key set of each dictionary is almost equal to the key set of the other dictionary, and for each such almost equal key pair there's a corresponding almost equal value.
- In general: I consider two collections almost equal if they're equal except for some corresponding floats which are just almost equal to each other. In other words, I would like to really compare objects but with a low (customized) precision when comparing floats along the way.

Answer

**Here's how I've implemented a generic is_almost_equal(first, second) function**:

First, duplicate the objects you need to compare (`first`

and `second`

), but don't make an exact copy: cut the insignificant decimal digits of any float you encounter inside the object.

Now that you have copies of `first`

and `second`

for which the insignificant decimal digits are gone, just compare `first`

and `second`

using the `==`

operator.

Let's assume we have a `cut_insignificant_digits_recursively(obj, places)`

function which duplicates `obj`

but leaves only the `places`

most significant decimal digits of each float in the original `obj`

. Here's a working implementation of `is_almost_equals(first, second, places)`

:

```
from insignificant_digit_cutter import cut_insignificant_digits_recursively
def is_almost_equal(first, second, places):
'''returns True if first and second equal.
returns true if first and second aren't equal but have exactly the same
structure and values except for a bunch of floats which are just almost
equal (floats are almost equal if they're equal when we consider only the
[places] most significant digits of each).'''
if first == second: return True
cut_first = cut_insignificant_digits_recursively(first, places)
cut_second = cut_insignificant_digits_recursively(second, places)
return cut_first == cut_second
```

And here's a working implementation of `cut_insignificant_digits_recursively(obj, places)`

:

```
def cut_insignificant_digits(number, places):
'''cut the least significant decimal digits of a number,
leave only [places] decimal digits'''
if type(number) != float: return number
number_as_str = str(number)
end_of_number = number_as_str.find('.')+places+1
if end_of_number > len(number_as_str): return number
return float(number_as_str[:end_of_number])
def cut_insignificant_digits_lazy(iterable, places):
for obj in iterable:
yield cut_insignificant_digits_recursively(obj, places)
def cut_insignificant_digits_recursively(obj, places):
'''return a copy of obj except that every float loses its least significant
decimal digits remaining only [places] decimal digits'''
t = type(obj)
if t == float: return cut_insignificant_digits(obj, places)
if t in (list, tuple, set):
return t(cut_insignificant_digits_lazy(obj, places))
if t == dict:
return {cut_insignificant_digits_recursively(key, places):
cut_insignificant_digits_recursively(val, places)
for key,val in obj.items()}
return obj
```

The code and its unit tests are available here: https://github.com/snakile/approximate_comparator. I welcome any improvement and bug fix.