-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Fix loss of precision on small angles in qua's pow #946
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a921b51
to
f252b0a
Compare
The PR is failing the unit tests. Please, merge master to your branch and resolve the failure for the PR to be merged. Thanks, |
6b0e971
to
6d3c5b9
Compare
So, I have investigated this a bit further. I had gone a bit fast with the threshold value. I wrote a small program to iterate over all possible This means the value Also, the sin-method is not slower than the cos-method on the [0.0, 1.0] interval, at least with a naive scalar implementation. So, the new version passes the unit tests, and should essentially extract most of the available precision, without sacrificing much performance. |
b4570d8
to
477bbe6
Compare
To visualize better what this PR does, this is the imprecision of We easily notice the loss of precision in [-1; 1]. The change in behavior looks sharp, but do note that the x-axis is given in logarithmic scale. For |x| > 1, ULP(x) ≥ ε; since |cos(x)| ≤ 1, it does not waste precision. However, for |x| < 1, ULP(x) < ε (and particularily for |x| ≪ 1, ULP(x) ≪ ε); in this case ½ ≤ |cos(x)| < 1, so ULP(cos(x)) = ε / 2. It means that, as x gets close to 0, the absolute precision of its float32 representation increases, while that of cos(x) remains constant. In layman's terms, more and more possible values for x gets mapped to the same value by Outside the ±1 boundary, you get most of the precision back, so the curve looks almost flat. Within it, you quickly gets >100 % error, so it plateaus at what looks like 62 bits of error (maybe 64-bit fixed-point representation, 1 bit of sign, and 1 bit because a broken clock is right twice a day?). Between roughly ±1e-19 all precision is lost (even with 64 bits of precision), and acos(cos(x)) always return exactly 0. However, the relative precision gets lower, as the real values gets closer and closer to the approximated 0. Compare this to the curve for Because Although Clearly, we are still losing quite a few bits of precision. So it's best to use the cos-method outside ±1, and the sin-method inside ±1. Using By manually iterating over the floats, it seems like the sin-hypot method indeed behaves much more nicely than the sin-sqrt method for values lower than roughly 2⁻⁶². Actually, it behaves well even on subnormal numbers, and only starts to break down below 2⁻¹²⁶. My guess is that this might be related to the 64 bits of the extended precision of the FPU. However, the asin-hypot method is several times slower than the asin-sqrt method where it matters. It might make sense to use the asin-hypot method below a set threshold, but the appropriate value for this threshold would most likely depend on the architecture. |
69fbd3f
to
c2af2c2
Compare
Finally placated the gods of the CI 😅 |
I really wish all the PR would be of that level of quality ! Thanks for contributing ! |
Thank you very much for your patience and your kinds words. GLM is really a great library, I was happy to be able to contribute! |
I encountered loss of precision when using quaternions with very small angles with the function
pow()
. This is because it determines the angle in the quaternion by applyingacos()
to the scale componentw
, which is equal to1.
because of dramatic cancellation.However, it is still possible to recover an accurate angle by looking at the non-scale components (which are then very close to, but different than,
0.
) and usingasin()
. I have written a fix to verify that this was possible, and it might be useful to merge it into the project.I have tried to take non-normalized quaternions into account, but I may have missed something. Any feedback is welcome.
Edit: I have included a similar fix for qua's
angle()
function