55

我被要求测试第 3 方提供的库。已知该库精确到n 个有效数字。任何不太重要的错误都可以安全地忽略。我想写一个函数来帮助我比较结果:

def nearlyequal( a, b, sigfig=5 ):

此函数的目的是确定两个浮点数(a 和 b)是否近似相等。如果 a==b(完全匹配)或者如果 a 和 b 在以十进制形式写入时舍入到sigfig有效数字时具有相同的值,则该函数将返回 True。

任何人都可以提出一个好的实施方案吗?我写了一个迷你单元测试。除非您可以在我的测试中看到错误,否则一个好的实现应该通过以下内容:

assert nearlyequal(1, 1, 5) 
assert nearlyequal(1.0, 1.0, 5) 
assert nearlyequal(1.0, 1.0, 5) 
assert nearlyequal(-1e-9, 1e-9, 5) 
assert nearlyequal(1e9, 1e9 + 1 , 5) 
assert not nearlyequal( 1e4, 1e4 + 1, 5) 
assert nearlyequal( 0.0, 1e-15, 5 ) 
assert not nearlyequal( 0.0, 1e-4, 6 ) 

补充笔记:

  1. 值 a 和 b 可能是 int、float 或 numpy.float64 类型。值 a 和 b 将始终属于同一类型。转换不会在函数中引入额外的错误,这一点至关重要。
  2. 让我们保留这个数字,因此转换为字符串或使用非数学技巧的函数并不理想。该程序将由数学家审核,他希望能够证明该函数完成了它应该做的事情。
  3. 速度...我必须比较很多数字,所以越快越好。
  4. 我有 numpy、scipy 和标准库。其他任何东西对我来说都很难得到,尤其是对于项目的一小部分。
4

11 回答 11

123

从 Python 3.5 开始,执行此操作的标准方法(使用标准库)是使用math.isclose函数。

它具有以下签名:

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

绝对容错的使用示例:

from math import isclose
a = 1.0
b = 1.00000001
assert isclose(a, b, abs_tol=1e-8)

如果您希望它具有n 个有效数字的精度,只需将最后一行替换为:

assert isclose(a, b, abs_tol=10**-n)
于 2016-04-04T16:24:31.437 回答
29

(source here)assert_approx_equal中有一个函数可能是一个很好的起点。numpy.testing

def assert_approx_equal(actual,desired,significant=7,err_msg='',verbose=True):
    """
    Raise an assertion if two items are not equal up to significant digits.

    .. note:: It is recommended to use one of `assert_allclose`,
              `assert_array_almost_equal_nulp` or `assert_array_max_ulp`
              instead of this function for more consistent floating point
              comparisons.

    Given two numbers, check that they are approximately equal.
    Approximately equal is defined as the number of significant digits
    that agree.
于 2009-02-17T19:16:25.010 回答
7

这是一个例子。

def nearly_equal(a,b,sig_fig=5):
    return ( a==b or 
             int(a*10**sig_fig) == int(b*10**sig_fig)
           )
于 2009-02-17T19:07:04.783 回答
6

我相信您的问题定义得不够好,您提供的单元测试证明了这一点:

如果“四舍五入到 N sig-fig 小数位”是指“小数点右侧的 N 个小数位”,那么测试assert nearlyequal(1e9, 1e9 + 1 , 5)应该会失败,因为即使将 1000000000 和 1000000001 舍入到 0.00001 精度,它们仍然不同。

如果“四舍五入到 N sig-fig 小数位”是指“N 个最高有效数字,无论小数点如何”,那么测试assert nearlyequal(-1e-9, 1e-9, 5)应该失败,因为从这种方式来看,0.000000001 和 -0.000000001 完全不同。

如果您的意思是第一个定义,那么此页面上的第一个答案(由 Triptych 提供)很好。如果你的意思是第二个定义,请说出来,我保证会考虑一下 :-)

于 2009-02-17T19:24:29.047 回答
4

已经有很多很好的答案,但这里有一个想法:

def closeness(a, b):
  """Returns measure of equality (for two floats), in unit
     of decimal significant figures."""
  if a == b:
    return float("infinity")
  difference = abs(a - b)
  avg = (a + b)/2
  return math.log10( avg / difference )


if closeness(1000, 1000.1) > 3:
  print "Joy!"
于 2009-02-19T05:50:20.877 回答
2

十进制中的“有效数字”是调整小数点并截断为整数的问题。

>>> int(3.1415926 * 10**3)
3141
>>> int(1234567 * 10**-3)
1234
>>>
于 2009-02-17T19:10:06.993 回答
2

这是浮点数的一个相当普遍的问题。我根据 Demmel[1] 第 1.5 节中的讨论解决了这个问题。(1) 计算舍入误差。(2) 检查舍入误差是否小于某个 epsilon。我已经有一段时间没有使用 python 了,只有 2.4.3 版本,但我会尽量做到这一点。

步骤 1. 舍入误差

def roundoff_error(exact, approximate):
    return abs(approximate/exact - 1.0)

步骤 2. 浮点相等

def float_equal(float1, float2, epsilon=2.0e-9):
    return (roundoff_error(float1, float2) < epsilon)

这段代码有几个明显的缺陷。

  1. 如果精确值为零,则除以零错误。
  2. 不验证参数是否为浮点值。

修订 1。

def roundoff_error(exact, approximate):
    if (exact == 0.0 or approximate == 0.0):
        return abs(exact + approximate)
    else:
        return abs(approximate/exact - 1.0)

def float_equal(float1, float2, epsilon=2.0e-9):
    if not isinstance(float1,float):
        raise TypeError,"First argument is not a float."
    elif not isinstance(float2,float):
        raise TypeError,"Second argument is not a float."
    else:
        return (roundoff_error(float1, float2) < epsilon)

这样好一点。如果精确值或近似值中的任何一个为零,则误差等于另一个值。如果提供了除浮点值之外的其他内容,则会引发 TypeError。

此时,唯一困难的是为 epsilon 设置正确的值。我在 2.6.1 版的文档中注意到 sys.float_info 中有一个 epsilon 属性,因此我将使用该值的两倍作为默认 epsilon。但正确的值取决于您的应用程序和算法。

[1] James W. Demmel,应用数值线性代数,SIAM,1997 年。

于 2009-02-19T04:30:15.123 回答
1

如前所述,Oren Shemesh 解决了部分问题,但还有更多问题:

断言几乎相等(0.0, 1e-15, 5)

也没有通过第二个定义(这是我在学校学到的定义。)

无论您查看多少位数,0 都不等于非零。如果您有一个正确答案为零的案例,这对于此类测试可能会令人头疼。

于 2009-02-19T04:55:15.780 回答
1

B. Dawson(使用 C++ 代码)在“比较浮点数”中有一个有趣的解决方案。他的方法依赖于两个数字的严格 IEEE 表示以及当所述数字表示为无符号整数时的强制字典顺序。

于 2009-10-20T14:36:50.867 回答
1

我被要求测试第 3 方提供的库

如果您使用的是默认 Pythonunittest框架,则可以使用assertAlmostEqual

self.assertAlmostEqual(a, b, places=5)
于 2018-08-22T09:27:22.230 回答
0

有很多方法可以比较两个数字,看看它们是否同意 N 个有效数字。粗略地说,您只想确保它们的差异小于被比较的两个数字中最大数字的 10^-N 倍。这很容易。

但是,如果其中一个数字为零怎么办?与零比较时,相对差异或有效数字的整个概念会下降。要处理这种情况,您还需要有一个绝对差异,它应该与相对差异不同地指定。

我在这篇博文中讨论了比较浮点数的问题——包括处理零的特定情况:

http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

于 2013-12-14T17:11:49.207 回答