I'm writing a unit test for a class for 3D vector objects and its algebra (dot product, cross product etc.) and just observed a behavior I can somehow understand, but not to its fully extent.
What I do is actually to generate 2 pseudorandom vectors, b
and c
, and a pseudorandom scalar, s
, and subsequently check the results of different operations on those vectors.
b
's components are generated in the range [-1, 1]
, while c
's component in the range [-1e6, 1e6]
, since in my use case I'll encounter similar situations, which could cause a significant lost of information in the mantissa. s
is generated in the range [-1, 1]
as well.
I created a MWE in python (using numpy) just to expose better my question (but I'm actually coding in C++ and the question in itself is language agnostic):
b = np.array([0.4383006177615909, -0.017762134447941058, 0.56005552104818945])
c = np.array([-178151.26386435505, 159388.59511391702, -720098.47337336652])
s = -0.19796489160874975
I then define
d = s*np.cross(b,c)
e = np.cross(b,c)
And finally calculate
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
Since d
and e
are both perpendicular to b
and c
, the scalar products computed above should all give 0 (algebraically).
Now, it's clear to me that in a real computer this can only achieved in the limits of floating point arithmetic. I however would like to better understand how this error generates.
What actually surprised me a bit is the poor accuracy of the first of the three results.
I'll try to expose my thoughts in the following:
np.cross(b, c)
is basically[b[1]*c[2]-b[2]*c[1], b[2]*c[0]-b[0]*c[2], ...]
which involves multiplication of a large and a small number and subsequent subtraction.e
(the cross product b x c) itself keeps relative large components, i.e.array([-76475.97678585, 215845.00681978, 66695.77300175])
- So, to get
d
you still multiply once pretty large components times a number <1. This of course will lead to some truncation error. - When taking the dot product
e . c
the result is correct, while ind . c
the result is off of almost2e-6
. Can this last multiplication bys
lead to such a big difference? A naïve thought would be to say that, given my machine epsilon of2.22045e-16
and the magnitude of the components ofd
, the error should be around4e-11
. - Is the information of the mantissa lost in the subtraction taken in the cross product?
To check that last thought, I did:
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
And it indeed appears that in the subtraction I loose much more information. Is that correct? How can that be explained in terms of floating point approximation?
Also, does that mean that, regardless of the input (i.e., no matter if the two vectors are of similar magnitude or completely different), it is better to always perform first all the operations which involve multiplication (and division?), then those involving addition/subtraction?