将浮点值的相等性置换为显着的数字容差

时间:2013-10-28 20:05:49

标签: python unit-testing assert scientific-computing

我正在尝试编写单元测试来检查工程分析的输出。我有理论值,我想检查对一定数量的重要数字的分析。所以,例如:

Ixx_ther = 0.000123
Iyy_ther = 0.0123

Ixx, Iyy = getI(*args, **kwargs)

self.assertAlmostEqual(Ixx_ther, Ixx, 6)
self.assertAlmostEqual(Iyy_ther, Iyy, 4)

在这种情况下,我需要知道我要检查的数字,因为在两种情况下将公差设置为6会使测试过于严格并将其设置为4会太宽松。我需要的是测试相同数量的有效数字是否相等。理想的是说:

Ixx_ther = 1.23E-4
Iyy_ther = 1.23E-2

Ixx, Iyy = getI(*args, **kwargs)

self.assertAlmostEqual(Ixx_ther, Ixx, 2)
self.assertAlmostEqual(Iyy_ther, Iyy, 2)

并使assert语句删除exponent并仅检查Significand是否相等。我想这之前已经完成了,但是我还没有找到一个内置函数来以这种方式断言相等。之前有没有人遇到过这个问题,

问题

1)之前是否有人遇到此问题,并且知道工程分析的单元测试的一般指南

2)是否有内置解决方案。解决这个问题

3)有人已经编写了一个以这种方式工作的自定义断言语句吗?

4 个答案:

答案 0 :(得分:4)

这是answer I left on another question的修改。

def AlmostEqual(a, b, digits):
    epsilon = 10 ** -digits
    return abs(a/b - 1) < epsilon

如果b可以为零,则需要更多工作。

答案 1 :(得分:3)

Re:有没有内置的解决方案:如果您可以将numpy作为依赖项,请查看 numpy.testing

以下是一个示例(逐字assert_allclose个文档):

>>> x = [1e-5, 1e-3, 1e-1]
>>> y = np.arccos(np.cos(x))
>>> assert_allclose(x, y, rtol=1e-5, atol=0)

编辑:为了完整性,这里是源代码的链接:assert_allclose将实际工作转发给np.allclose。这几乎与@Mark Ransom的答案相同(加上对数组参数和无穷大的处理)。

答案 2 :(得分:0)

也许没有回答你问题的全部范围,但这就是我写这样一个函数的方法:

def assertAlmostEqual(arg1,arg2,tolerance=2):
    str_formatter = '{0:.' + str(tolerance) + 'e}'
    lhs = str_formatter.format(arg1).split('e')[0]
    rhs = str_formatter.format(arg2).split('e')[0]
    assert lhs == rhs

Python的string formatting mini-language可用于将浮动格式化为给定方式。所以我们可以做的是强制它们用指数表示法格式化,以便输入0.1230.000123,我们有:

str_formatter.format(0.123) == '1.23e-01'
str_formatter.format(0.000123) == '1.23e-04'

剩下的就是砍掉指数并断言平等。

演示:

assertAlmostEqual(0.0123,0.0001234)

assertAlmostEqual(0.123,0.0001234)

assertAlmostEqual(0.123,0.0001234,tolerance=3)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/home/xxx/<ipython-input-83-02fbd71b2e87> in <module>()
----> 1 assertAlmostEqual(0.123,0.0001234,tolerance=3)

/home/xxx/<ipython-input-74-ae32ed74769d> in assertAlmostEqual(arg1, arg2, tolerance)
      3     lhs = str_formatter.format(arg1).split('e')[0]
      4     rhs = str_formatter.format(arg2).split('e')[0]
----> 5     assert lhs == rhs
      6 

AssertionError: 

如果您不喜欢我定义tolerance的方式,可能会出现一个一个一个问题。尽管如此,我还是想到了这个想法。

答案 3 :(得分:0)

感谢roippi这个好主意,我稍微修改了你的代码:

def assertAlmostEqualSigFig(self, arg1,arg2,tolerance=2):
    if tolerance > 1: 
        tolerance -= 1
    #end

    str_formatter = '{0:.' + str(tolerance) + 'e}'
    significand_1 = float(str_formatter.format(arg1).split('e')[0])
    significand_2 = float(str_formatter.format(arg2).split('e')[0])

    exponent_1 = int(str_formatter.format(arg1).split('e')[1])
    exponent_2 = int(str_formatter.format(arg2).split('e')[1])

    self.assertEqual(significand_1, significand_2)
    self.assertEqual(exponent_1, exponent_2)

    return

我改变了一些事情

1)我检查指数和有效数字(这是一个顶部的抽屉词不是它)

2)我将有效数和指数分别转换为float / int。这可能没有必要,但我更乐于将数字的相等性检查为数字而不是字符串。

3)Jim Lewis注意到我需要将容差调整为1,因为0.0123的正确格式字符串{0:.3e}是1.230E-2而不是0.123E-1。即如果你想要三个有效数字,你只需要小数点后的两位数,因为小数点前的数字也很重要。

她是一个实施的例子

class testSigFigs(Parent_test_class):

    @unittest.expectedFailure
    def test_unequal_same_exp(self):
        self.assertAlmostEqualSigFig(0.123, 0.321, 3)

    @unittest.expectedFailure
    def test_unequal_diff_exp(self):
        self.assertAlmostEqualSigFig(0.123, 0.0321, 3)

    @unittest.expectedFailure
    def test_equal_diff_exp(self):
        self.assertAlmostEqualSigFig(0.0123, 0.123, 3)

    def test_equal_same_exp(self):
        self.assertAlmostEqualSigFig(0.123, 0.123, 3)

    def test_equal_within_tolerance(self):
        self.assertAlmostEqualSigFig(0.123, 0.124, 2)
    #end

输出:

test_equal_diff_exp (__main__.testSigFigs) ... expected failure
test_equal_same_exp (__main__.testSigFigs) ... ok
test_equal_within_tolerance (__main__.testSigFigs) ... ok
test_unequal_diff_exp (__main__.testSigFigs) ... expected failure
test_unequal_same_exp (__main__.testSigFigs) ... expected failure

----------------------------------------------------------------------
Ran 5 tests in 0.081s

OK (expected failures=3)

感谢您的反馈。