Search code examples
floating-pointprecisioncurrency

What is the minimum number of floating-point operations needed to get a one-cent error when computing monetary values with double?


A sequence of arithmetic operations (+,-,*,/,round) are performed on only monetary values of 1 trillion dollars or less (1e12 USD), rounded to the nearest penny. What is the minimum number double-precision floating point operations mirroring these operations to result in a one-penny or more rounding error on the value?

In practice, how many operations are safe to perform when computing results when rounding double-precision numbers?

This question is related to Why not use Double or Float to represent currency? but seeks a specific example of a problem with using double-precision floating point not currently found in any of the answers to that question.

Of course, double values MUST be rounded before comparisons such as ==, <, >, <=, >=, etc. And double values MUST be rounded for display. But this question asks how long you can keep double-precision values unrounded without risking a rounding error with realistic constraints on the sorts of calculations being performed.

This question is similar to the question, Add a bunch of floating-point numbers with JavaScript, what is the error bound on the sum?, but is less-constrained in that multiplication and division are allowed. Frankly, I may have constrained the question too little, because I'm really hoping for an example of a rounding error that is plausible to occur in ordinary business.


It has become clear in the extended discussion on the first answer that this question is ill-formulated because of the inclusion of "round" in the operations.

I feel the ability to occasionally round to the nearest cent is important, but I'm not sure how best to define that operation.

Similarly, I think rounding to the nearest dollar could be justified, e.g., in a tax environment where such rounding is (for who knows what reason) actually encouraged though not required in US Tax law.

Yet I find the current first answer to be dissatisfying because it feels as if cent rounding followed by banker's rounding would still produce the correct result.


Solution

  • At most three.

    Presumably, IEEE-754 binary64, also known as “double precision” is used.

    .29 rounds to 0.289999999999999980015985556747182272374629974365234375. Multiplying by 50 produces 14.4999999999999982236431605997495353221893310546875, after which round produces 14. However, with real-number arithmetic, .29•50 would be 14.5 and would round to 15. (Recall the round function is specified to round half-way cases away from zero.)

    The preceding uses rounding to an integer. Here is an example using rounding to the nearest “cent,” that is, to two digits after the decimal point. A C implementation using IEEE-754 binary64 semantics with round-to-nearest ties-to-even with this program:

    #include <math.h>
    #include <stdio.h>
    
    
    int main(void)
    {
        printf(".55 -> %.99g.\n", .55);
        printf(".55/2 -> %.99g.\n", .55/2);
        printf("Rounded to two digits after decimal point -> %.2f.\n", .55/2);
        printf("1.15 -> %.99g.\n", 1.15);
        printf("1.15/2 -> %.99g.\n", 1.15/2);
        printf("Rounded to two digits after decimal point -> %.2f.\n", 1.15/2);
    }
    

    produces this output:

    .55 -> 0.5500000000000000444089209850062616169452667236328125.
    .55/2 -> 0.27500000000000002220446049250313080847263336181640625.
    Rounded to two digits after decimal point -> 0.28.
    1.15 -> 1.149999999999999911182158029987476766109466552734375.
    1.15/2 -> 0.5749999999999999555910790149937383830547332763671875.
    Rounded to two digits after decimal point -> 0.57.
    

    The real-number results of the divisions would be .275 and .575. Any ordinary tie-breaker rule for round-to-nearest would round these in the same direction (upward produces .28 and .58, downward produces .27 and .57, to-even produces .28 and .58). But the IEEE-754 binary64 results produce results rounded in different directions, one up and one down. Therefore one of the floating-point results does not match the desired real-number result regardless of which tie-breaker rule is chosen.