Python中的assertAlmostEqual浮点数集合的单元测试

2024-06-11 21:19:38 发布

您现在位置:Python中文网/ 问答频道 /正文

Python's unit testing framework中的assertAlmostEqual(x, y)方法测试xy在假定它们是浮点数的情况下是否近似相等。

assertAlmostEqual()的问题是它只在浮点上工作。我正在寻找一个类似assertAlmostEqual()的方法,它可以处理浮点列表、浮点集、浮点字典、浮点元组、浮点元组列表、浮点列表集等

例如,让x = 0.1234567890y = 0.1234567891xy几乎相等,因为它们在除最后一个数字外的每个数字上都一致。因此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()应该具有相同的功能。

  • 如果两个列表的数字在完全相同的顺序上几乎相等,则它们被认为几乎相等。形式上,for i in range(n): self.assertAlmostEqual(list1[i], list2[i])

  • 类似地,如果可以将两个集合转换为几乎相等的列表(通过为每个集合指定一个顺序),则认为它们几乎相等。

  • 类似地,如果每个字典的密钥集几乎等于另一个字典的密钥集,则认为两个字典几乎相等,并且对于每个这样几乎相等的密钥对,都有一个对应的几乎相等的值。

  • 一般来说:我认为两个集合几乎相等,如果它们相等,除了一些对应的浮点数几乎相等。换句话说,我希望真正比较对象,但在比较沿途浮动时精度较低(自定义)。


Tags: 方法selftrue列表字典密钥情况数字
3条回答

从Python3.5开始,您可以使用

math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)

pep-0485所述。 实现应该等同于

abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )

如果您不介意使用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)

首先,复制需要比较的对象(firstsecond),但不要精确复制:剪切在对象中遇到的任何浮点的不重要的小数位数。

既然已经有了firstsecond的副本,其中不重要的十进制数字已经消失,那么只需使用==运算符比较firstsecond

假设我们有一个cut_insignificant_digits_recursively(obj, places)函数,它与obj重复,但只保留原始obj中每个浮点数的places最有意义的小数位数。以下是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

代码及其单元测试可以在这里获得:https://github.com/snakile/approximate_comparator。我欢迎任何改进和错误修复。

相关问题 更多 >