【发布时间】:2020-12-17 06:16:07
【问题描述】:
我正在为 3D 矢量对象及其代数(点积、叉积等)的类编写单元测试,并且刚刚观察到我可以理解的行为,但还没有完全理解。
我所做的实际上是生成 2 个伪随机向量,b 和 c,以及一个伪随机标量,s,然后检查对这些向量进行不同操作的结果。
b的组件在[-1, 1]范围内生成,而c的组件在[-1e6, 1e6]范围内生成,因为在我的用例中我会遇到类似的情况,这可能会导致重大损失尾数中的信息。 s 也在[-1, 1] 范围内生成。
我在 python 中创建了一个 MWE(使用 numpy)只是为了更好地展示我的问题(但我实际上是在 C++ 中编码,并且问题本身与语言无关):
b = np.array([0.4383006177615909, -0.017762134447941058, 0.56005552104818945])
c = np.array([-178151.26386435505, 159388.59511391702, -720098.47337336652])
s = -0.19796489160874975
然后我定义
d = s*np.cross(b,c)
e = np.cross(b,c)
最后计算
In [7]: np.dot(d,c)
Out[7]: -1.9073486328125e-06
In [8]: np.dot(e,c)
Out[8]: 0.0
In [9]: s*np.dot(e,c)
Out[9]: -0.0
由于d 和e 都垂直于b 和c,因此上面计算的标量积都应该给出0(代数)。
现在,我很清楚,在真正的计算机中,这只能在浮点运算的限制范围内实现。但是,我想更好地了解此错误是如何产生的。
实际上让我有点吃惊的是三个结果中第一个结果的准确性很差。
我会尝试在下面表达我的想法:
-
np.cross(b, c)基本上是[b[1]*c[2]-b[2]*c[1], b[2]*c[0]-b[0]*c[2], ...],它涉及一个大数和小数的乘法以及随后的减法。e(b x c 的叉积)本身保留了相对较大的组件,即array([-76475.97678585, 215845.00681978, 66695.77300175]) - 所以,要获得
d,您仍然需要将相当大的分量乘以一个数字 - 当取点积
e . c时,结果是正确的,而在d . c中,结果几乎与2e-6不同。最后一次乘以s会导致如此大的差异吗?一个天真的想法是,考虑到我的机器 epsilon 为2.22045e-16和d的分量的大小,误差应该在4e-11左右。 - 叉积中的减法是否丢失了尾数的信息?
为了检查最后的想法,我做了:
In [10]: d = np.cross(s*b,c)
In [11]: np.dot(d,c)
Out[11]: 0.0
In [12]: d = np.cross(b,s*c)
In [13]: np.dot(d,c)
Out[13]: 0.0
而且确实似乎在减法中我丢失了更多信息。那是对的吗?如何用浮点近似来解释?
另外,这是否意味着,无论输入如何(即,无论两个向量的大小相似还是完全不同),最好总是首先执行所有涉及乘法(和除法?)的操作,那么那些涉及加法/减法的?
【问题讨论】:
标签: floating-point language-agnostic precision linear-algebra floating-accuracy