判断两个复数是否相等

1 投票
3 回答
1758 浏览
提问于 2025-04-18 23:56

下面的代码会让打印语句被执行:

import numpy as np
import math

foo = np.array([1/math.sqrt(2), 1/math.sqrt(2)], dtype=np.complex_)

total = complex(0, 0)
one = complex(1, 0)
for f in foo:
   total = total + pow(np.abs(f), 2)
   if(total != one):
      print str(total) + " vs " + str(one)
      print "NOT EQUAL"

但是,我输入的 [1/math.sqrt(2), 1/math.sqrt(2)] 结果却是 total 变成了 one

(1+0j) vs (1+0j) NOT EQUAL

这是不是跟把NumPy和Python的复杂类型混在一起有关呢?

3 个回答

0

总结:

比较浮点数或复数的正确方法是:

def isclose(a, b, rtol=1e-5, atol=1e-8):
    return abs(a-b) < atol + rtol * abs(b)

这实际上就是 np.isclose() 背后的工作原理,使用 np.isclose() 更好,因为它能处理无穷大、非数字等情况。

详细信息:

问题中的特定情况并不仅限于复数。如果你把

total = complex(0, 0)
one = complex(1, 0)

换成它的浮点数等价物

total = 0
one = 1

你会得到完全相同的结果:

0.4999999999999999 vs 1
NOT EQUAL
0.9999999999999998 vs 1
NOT EQUAL

@bereal 提出的标准 abs(a-b) < epsilon 在某些情况下是有效的,但如果你在不同的尺度上查看误差:

>>> np.sqrt(1.234)**2-1.234
2.220446049250313e-16
>>> np.sqrt(12.34)**2-12.34
1.7763568394002505e-15
>>> np.sqrt(123.4)**2-123.4
-1.4210854715202004e-14

你会发现对于大数字,误差是线性增加的(这并不奇怪,因为浮点数在 float64 中只保留 15 位小数),所以使用相对差而不是绝对差会更合理:

abs(a-b)/a < epsilon

这样可以解决误差增加的问题,但即使快速看一下也会发现 a==0 的问题。无论你喜欢哪种方式:
  • abs(a-b)/max(a,b)(在 math.isclose() 中使用),
  • abs(a-b)/(a+b)
或者其他方式,它们都有同样的问题:当 a 和/或 b 为零时会失败。

为了解决这个问题,通常的做法是使用两个“epsilon”:一个绝对的 epsilon(也叫 atol,绝对容忍度)用于与零比较,另一个相对的 epsilon(也叫 rtol,相对容忍度)用于与其他数字比较。

总的来说:
  • np.isclose() 的比较是 abs(a-b) <= atol + rtol * abs(b)
  • math.isclose() 的比较是 abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

math 版本更对称,但 numpy 版本更快。

3

在使用浮点数时,要记住这些数字的计算永远不会完全准确,因此每次计算都会受到舍入误差的影响。这是因为浮点运算的设计造成的,而目前在资源有限的计算机上进行高精度数学运算的方式就是这样。你不能用浮点数进行精确计算(实际上没有其他选择),因为你的数字必须在某个地方被截断,以适应合理的内存大小(通常最多64位),这个截断就是通过舍入来实现的(下面有个例子)。

为了正确处理这些问题,你应该避免直接比较浮点数是否相等,而是比较它们的接近程度。Numpy提供了两个函数来实现这一点:np.isclose用于比较单个值(或者对数组进行逐项比较),而np.allclose则用于比较整个数组。后者实际上是np.all(np.isclose(a, b)),这样你就能得到一个数组的单一值。

>>> np.isclose(np.float32('1.000001'), np.float32('0.999999'))
True

不过,有时候舍入的结果非常实用,正好符合我们的分析预期,比如:

>>> np.float(1) == np.square(np.sqrt(1))
True

在平方后,值会被缩小以适应给定的内存,因此在这种情况下,它被舍入到我们所期望的结果。

这两个函数内置了绝对和相对的容忍度(你也可以作为参数传入),用于比较两个值。默认的容忍度是rtol=1e-05atol=1e-08


另外,不要混合使用不同包的类型。如果你使用Numpy,就要使用Numpy的类型和函数。这也会减少你的舍入误差。

顺便提一下:当处理指数差异很大的数字时,舍入误差的影响会更大。

2

我想,对于真实数字来说,考虑的因素是一样的:永远不要假设它们可以完全相等,而是要认为它们可能足够接近:

eps = 0.000001
if abs(a - b) < eps:
    print "Equal"

撰写回答