Python中的assertAlmostEqual对浮点数集合进行unit testing
Python的unit testing框架中的assertAlmostEqual(x,y)方法testingx
和y
是否大致相等,假设它们是浮点数。
assertAlmostEqual()
的问题是它只能在浮点数上运行。 我正在寻找像assertAlmostEqual()
这样的方法,它在浮点,浮点集,浮点字典,浮点元组,浮点元组列表,浮点列表集合等列表上工作。
例如,令x = 0.1234567890
, y = 0.1234567891
。 x
和y
几乎相等,因为除了最后一个数字外,他们都同意每个数字。 因此, self.assertAlmostEqual(x, y)
是True
因为assertAlmostEqual()
适用于浮点数。
我正在寻找一个更通用的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)])
。
有没有这样的方法,还是我自己要实现呢?
澄清:
-
assertAlmostEquals()
有一个名为places
的可选参数,通过计算舍入到小数位数的差值来比较数字。 默认情况下,places=7
,因此self.assertAlmostEqual(0.5, 0.4)
为False,而self.assertAlmostEqual(0.12345678, 0.12345679)
为True。 我推测assertAlmostEqual_generic()
应该有相同的function。 -
如果两个列表中的数字完全相同,则认为它们几乎相等。 正式地,
for i in range(n): self.assertAlmostEqual(list1[i], list2[i])
。 -
同样的,如果两个集合可以转换成几乎相同的列表(通过给每个集合分配一个命令),则认为两个集合几乎相等。
-
类似地,如果每个字典的密钥集几乎等于另一个字典的密钥集,则认为两个字典几乎相等,并且对于每个这样的几乎相等的密钥对,存在对应的几乎相等的值。
-
一般来说:如果两个集合相等,我认为两个集合几乎相等,除了一些对应的浮动几乎相等。 换句话说,我想真正比较一下对象,但是在比较浮动的时候,它们的精确度很低(定制的)。
如果您不介意使用NumPy(Python(x,y)附带),您可能需要查看np.testing
模块,其中定义了assert_almost_equal
函数。
签名是np.testing.assert_almost_equal(actual, desired, decimal=7, err_msg='', verbose=True)
>>> x = 1.000001 >>> y = 1.000002 >>> np.testing.assert_almost_equal(x, y) AssertionError: Arrays are not almost equal to 7 decimals ACTUAL: 1.000001 DESIRED: 1.000002 >>> np.testing.assert_almost_equal(x, y, 5) >>> np.testing.assert_almost_equal([x, x, x], [y, y, y], 5) >>> np.testing.assert_almost_equal((x, x, x), (y, y, y), 5)
以下是我已经实现了一个通用的is_almost_equal(first, second)
函数 :
首先,复制需要比较的对象( first
和second
),但不要做一个精确的副本:剪切在对象内遇到的任何浮点数的无意义的十进制数字。
现在您已经有了first
和second
个小数位不重要的副本,只需使用==
运算符比较first
和second
数字即可。
假设我们有一个复制obj
的cut_insignificant_digits_recursively(obj, places)
函数,但只保留原始obj
中每个浮点数的最重要的十进制数字。 这是一个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
以下是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
代码和它的unit testing可以在这里find: https : //github.com/snakile/approximate_comparator 。 我欢迎任何改进和错误修复。
没有这样的方法,你必须自己做。
对于列表和元组来说,定义是显而易见的,但是请注意,您提到的其他情况并不明显,所以难怪没有提供这样的函数。 例如, {1.00001: 1.00002}
几乎等于{1.00002: 1.00001}
? 处理这种情况需要select是否靠近键或值或两者兼而有之。 对于集合,你不可能find有意义的定义,因为集合是无序的,所以没有“相应”元素的概念。
如果你不介意使用numpy
包,那么numpy.testing
就有assert_array_almost_equal
方法。
这适用于array_like
对象,所以对于数组,浮点数的列表和元组来说没问题,但是它不适用于集合和字典。
文档在这里 。
从python 3.5开始,你可以比较一下
math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)
如pep-0485所述 。 实施应该等同于
abs(ab) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
你可能必须自己实现它,而列表和集合的真实性可以用同样的方式迭代,字典是不同的故事,你迭代他们的键而不是价值观,第三个例子似乎有点模糊,你的意思是比较集合中的每个值或每个集合的每个值。
inheritance人一个简单的代码片段。
def almost_equal(value_1, value_2, accuracy = 10**-8): return abs(value_1 - value_2) < accuracy x = [1,2,3,4] y = [1,2,4,5] assert all(almost_equal(*values) for values in zip(x, y))