判断两个复数是否相等
下面的代码会让打印语句被执行:
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 个回答
总结:
比较浮点数或复数的正确方法是:
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
版本更快。
在使用浮点数时,要记住这些数字的计算永远不会完全准确,因此每次计算都会受到舍入误差的影响。这是因为浮点运算的设计造成的,而目前在资源有限的计算机上进行高精度数学运算的方式就是这样。你不能用浮点数进行精确计算(实际上没有其他选择),因为你的数字必须在某个地方被截断,以适应合理的内存大小(通常最多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-05
和atol=1e-08
。
另外,不要混合使用不同包的类型。如果你使用Numpy,就要使用Numpy的类型和函数。这也会减少你的舍入误差。
顺便提一下:当处理指数差异很大的数字时,舍入误差的影响会更大。
我想,对于真实数字来说,考虑的因素是一样的:永远不要假设它们可以完全相等,而是要认为它们可能足够接近:
eps = 0.000001
if abs(a - b) < eps:
print "Equal"