The quadratic formula and low-precision arithmetic
johndcook.com
johndcook.com
When a newer employee comes across the code they often "simplify" it using the quadratic formula they know so well, and which they believe always works.
The tests go red.
They roll back and look to try to understand. Eventually they either work it out, or they come and ask for advice. We work through the test case with them, and point out just how important it is that:
* There are tests;
* Routines have useful and enlightening names;
* If the code is complex, there's probably a reason, although not always;
* Sometimes a comment might be useful.
It's proven to be a really useful learning experience, and gets people up to speed very quickly with things. It's then left again as a learning experience waiting to happen.
Of course, this can only work in a culture where people work constructively to understand what's going on, and support each other - a place where it's OK to say that you don't understand something, and know that others will work with you. I've been in places where this wouldn't work.
All in all, I've been lucky.
The tests, by the way, only have a moderate size b, but have a very small a. In this way b^2-4ac is very close to b, so one of the solution paths is subtracting nearly equal quantities, getting small rubbish. But then you go on to divide by a very small number, giving big rubbish.
The geometric interpretation is that the parabola is very, very flat, so if you move it even the tiniest amount up or down the roots move very far, very fast.
It's a useful example - my thanks to johndcook for the write-up.
This avoids any of the bad subtraction.
Some sample js code that corresponds to quadratic in the post:
b = 5e7
c = 1
d = b*b - c
if (d >= 0) {
m = Math.sqrt(d)
if ( b> 0) {
r = -b - m
s = c/r
} else if ( b < 0) {
s = -b + m
r = c/s
} else {
r = m
s = -r
}
console.log(r, s)
} else {
console.log("no real roots");
}
This gives the correct roots for this problem in one go.http://www.solipsys.co.uk/cgi-bin/sews.py?NumericallyStableS...
Are there other "rules" like this, regarding numerical analysis? I understand this, but I've never thought of articulating it like that.
Comparing floats is tricky. Use either epsilon, epsilon with relative error or ULP's (all are tricky)
The more I experience engineering advances the more I see how cyclical techniques and technologies are. Almost everything old that was useful will eventually be needed again. Early computers were severely memory and cpu restricted. Then we got giant computers. Then we're putting microcontrollers everywhere and need those frugal techniques again. And so on the cycle will continue.
A possible conclusion is that the danger of obsolescence from a really profound knowledge in a field is far lower than one would expect, specially if you can weather the cycles of technology and find new markets to apply your expertise.
Integer math is fast, easy, and not error prone, unless you cross the limits in limits.h . But those are simple bounds checking, or you size the vars large enough so that you never need to worry about them. Primarily, if you use floats, you grab the data and send it on its way. Don't process if you don't have to. Drop it to MQTT/CoAP/AMQP.
I'm thinking in the similar way Klipper offloads 3d printer math to a heavy computer with GHz and GBs of ram for trig math and array compensation.
Sometimes you can get clever with the modular arithmetic--like, it's okay to add (or subtract) a list of fixed-point numbers where partial sums overflow and wrap, as long as the final result doesn't. I don't know any useful analogy to that here, though.
This paper discusses a great deal the sources of error, and how the IEEE standard does things. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.h...
Using integers instead bypasses this source of error. The only caveat that I keep attenion to is that you have to be able to expand the size of the space to prevent decimals from being used (think of multiplying by 100 when dealing with cash). And if you do deal with floats, do the math you have to and then drop back into ints, or save them and send them off.
There is no useful, general analogy to this in numerical analysis. If you're using any fixed-sized representation of numbers--floating point, fixed point, or otherwise--then some ways to calculate an expression will be less accurate than others. You can avoid this with variable-length representations, but then some ways are slower. There is no way to avoid thinking about numerical conditioning.
If you think I'm wrong: Can you code the quadratic formula using integer or other fixed-point math, and demonstrate why it doesn't need the attention to bad numerical conditioning noted in the parent?
Uhhhhh..... I hope this is a farcical comment. Because if you believe this, then you might want to read up here: https://en.wikipedia.org/wiki/Floating-point_arithmetic
Again: Can you write the quadratic formula in the way that you think is better (in pseudocode, or a language of your choice), and give examples of inputs where it performs better than floating point? If you don't do this and I'm wrong, then you're missing the opportunity to teach me (and all the people I'm misleading with my comments) something. If I'm right, then you're missing the opportunity to learn something yourself.
In practice I just check the coefficients and then use appropriate versions of the quadratic formula. I guess that I am luckier than Mr. Cook here since I only need one root.
code (some of it was written by me, some improvements are by other people): https://github.com/dealii/dealii/blob/master/source/fe/mappi...
This set of cases (which can probably be further improved) is equivalent to his first answer. I don't think its possible to do better in this test case due to the catastrophic cancelation in subtracting the discriminant from b.
added benefit is that it carries on in 3D (although with a larger jacobian of course).
I have not yet figured out how to make the 3D formula work so we always do the iterative method in 3D.
A nice trick: we approximate the (bi/tri)linear map as affine (usually quite accurate) to get a good initial guess.
What is important is to tailor your maths to the real problem you are solving (and remember garbage in = garbage out). Very rarely do you actually need 53 bits of precision (as you get in a double), or even need to store intermediate results with 53 bits of precision.
That's pretty amazing. CPU pipelining at work, or was the profile just dominated by other things?
The short story was that we had an expression of the form x = a/b * c/d * e/f in a fairly hot code path. As most developers know, divisions are expensive, so this got rewritten to x = (a * c * e) / (b * d * f).
The problem was that in certain edge cases, the factors were all extremely small. This caused the product b * d to become a denormal number, and when multiplied with f it became zero. This of course caused the division to return infinity.
However, closer inspection of the source of the terms a to f revealed that the pairs a,b etc always had roughly the same magnitude. Thus even though a and b were almost denormal numbers, the division a / b was well-behaved and gave a sensible result, and similar for the others.
Undoing the optimization and using the original form made the code well-behaved again.
Another expression that had to be de-optimized for the same reason was x = a / (b * b), which had to be rewritten as x = (a / b) / b.
So as with subtraction, division of IEEE floating-point numbers can be tricky.
This[1] page contains a nice set of tables with the results of performing IEEE floating-point operations on edge-case inputs.
[1]: https://www.cs.uaf.edu/2011/fall/cs301/lecture/11_09_weird_f...
Unums are more ambitious. They would have variable lengths, so implementing them would require more changes than just changing the way a fixed-length set of bits is interpreted.
But technical inertia is such that not always the best newcomer can beat the established ways.
edit: Somewhat relevant xkcd :)
[0] http://www.johngustafson.net/pdfs/BeatingFloatingPoint.pdf [1] http://nhaehnle.blogspot.de/2017/09/posits.html
The new hire made some comments that I thought sounded irresponsible, such as almost nobody does numerical analysis and instead they use double precision because, usually, even with catastrophic cancellation there are enough bits to give a useful answer. But, apparently, “use a higher precision” is a common answer in scientific programming, and it’s much easier to get right than numerical analysis ( https://www.davidhbailey.com/dhbtalks/dhb-expm-sun.pdf , first and last slides).
If you can afford it, that probably is the best approach. Cook’s article specifically mentions that the issue crops up with single precision floating point (which people might want for SIMD or GPU reasons, or on mobile to save power). In other words, Cook is talking about cases where you can’t afford extended precision.
Would it then not suffice to just load 32 bit floats, convert them to 64 bits on the processor, and then store them again as 32 bits? Essentially, we are 'compressing' our data in memory, whilst doing the computational steps with full precision.
No, there is no wrinkle whatsoever: your computer, guided by your assumptions and given inadequate input (let alone processing logic) got it wrong.
The day you start to blame inputs for errors in output, for which you have complete control is the one that you should mark as a good point to really consider whether you are in the right game.