Free, Fast, Accurate — Pick Any Three!
This technical note describes how Gnumeric does calculations internally and why certain simple calculations like 0.3-(3*0.1) give unexpected non-zero results.
Numbers in Gnumeric are so-called floating-point numbers described in the IEEE-754 standard. Specifically we are using the double precision format described therein. (It is possible to set up Gnumeric to use another format, but it is very rarely done. Unless you have worked hard on doing that, you may assume that you are indeed using "double precision".)
This choice of number system has consequences just like making another choice of number system would have come with a different set of consequences. Here are some of the consequences of using "double precision":
"Double precision" has some special values like "Infinity" and "Not-a-number". When encountered, these are turned into error values in Gnumeric. We also do our best to hide the value "-0" from users.
Note: "double precision" is what pretty much every program uses because it is what the hardware works with. Microsoft Excel, LibreOffice, OpenOffice, and R all are based on "double precision".
When an operation like addition is performed in Gnumeric, that operation will get a number of argument values and produce one or more output values. It is important to realize that the argument values that the operation gets are always representable numbers, i.e., any rounding needed to make numbers representable has already taken place. The job of the operation is to calculate its result using the arguments and round the result to the nearest representable number.
For example, for the division 1/10 where the arguments ("1" and "10") are both precisely representable, we calculate the mathematical value one-tenth and round that into the nearest representable value which happens to be exactly 0.1000000000000000000013552527156068805425093160010874271392822265625. That value clearly is a tiny bit bigger than one tenth in the same way the desk calculator's 0.33333333 is a tiny bit smaller than one third.
Rounding errors tend to add up. Computing 0.3-(3*0.1), for example, one does not get zero as one might have hoped for. Each individual sub-result is subject to rounding to representable values, so we are really computing r(r(0.3)-r(r(3)*r(0.1))) where "r" is the rounding operation. The end result is about -6*10-17. That is an unpleasant result of working with finite-precision math.
For more complex operations, like ERF(0.25), the goal is still to produce the exact result rounded to nearest representable value. Again, the term "exact result" is based on the argument value as already rounded to a representable number. We do not always achieve this goal, i.e., we sometimes calculate a value that is slightly off from the nearest representable value. If we have at least about 15 correct significant digits we are not too concerned by this, but anything less is worth reporting as a bug. Note, however, that Gnumeric in general produces results that are far more accurate than other spreadsheets.
Microsoft Excel uses the same number system as Gnumeric, but Microsoft Excel does some operations differently.
If the top-level operand of an expression is a subtraction and the result of the subtraction is very close to zero compared to the two arguments, then the result will be rounded to zero. This operation, called snap-to-zero, is also performed for addition and the subtraction implied by comparisons. Is is not performed for operations deeper in an expression, not even for subtractions surrounded by nothing but parentheses.
Snap-to-zero has the effect of hiding the small rounding errors that arise by the representation error on 0.01. That is, accounting calculations on amounts of dollars and cents will appear to be accurate most of the time.
But snap-to-zero comes at a price:
Gnumeric does not do snap-to-zero. We recommend an explicit rounding operation where it matters.
LibreOffice and OpenOffice both use the same number system as Gnumeric. These programs both use snap-to-zero (see above), but use it much more aggressively than Microsoft Excel. In particular, every subtraction is subject to snap-to-zero and there are no easy ways of performing a subtraction that does not snap-to-zero. Snap-to-zero is also done internally in many sheet functions, for example in VAR. There does not seem to be any accurate and complete documentation for snap-to-zero.
The lack of an honest subtraction operation makes it hard to even test the accuracy of sheet-level functions. LibreOffice and OpenOffice will claim that a result matches a reference value even when they do not. (Update: LibreOffice nowadays has a function that allows raw subtraction.)
We sometimes get asked why Gnumeric is not using some other number system that would avoid a particular problem a user is seeing.