user1819676
user1819676

Reputation: 369

What's the most precision you might lose if you use double to convert a currency value to another currency and back?

I know the problem of doubles due to base 2 calculations:

var total: Double = 0.1
total *= 0.1
total /= 0.1
println(total) // 0.10000000000000002

I understand that BigDecimal should be used for money related calculations, but in my case the little precision loss is acceptable, cuase all the currency calculation I need to do is convert currency C1 to currency C2 and back to currency C1. The only thing is that the two values of C1 after this conversion should match, when I show the result to customer with 5 decimal precision.

Now I'm trying to figure out, how much the value might drift over this conversion at worst case scenario? What are the numbers that might cause the worst case scenario?

Upvotes: 2

Views: 266

Answers (3)

chux
chux

Reputation: 154169

A double as in binary64 will maintain 15+ significant decimal places if properly rounded to/from Decimal.

Using fixed place math: with 15 significant decimal places and OP needing 5 places after the decimal point, that leaves 10 digits to the left of the decimal.

x,xxx,xxx,xxx.xxxxx

Upvotes: 1

Aasmund Eldhuset
Aasmund Eldhuset

Reputation: 37980

I would recommend to never store or transfer the C2 amount anywhere. Instead, store or transfer the C1 amount and the exchange rate. That way, you can compute and display the C2 amount whenever you need it, and you'll always have the exact original C1 value available.

If you absolutely have to store C2 and later recompute C1 from it, it will be fine as long as you use rounding rather than truncation, as you risk that the recomputed result will be e.g. 0.0199999999999. (And as @Andreas pointed out in his answer, there's a limit to how many digits you can support in this way.)

Upvotes: 0

Andreas
Andreas

Reputation: 159165

You could read the description of double-precision floating-point:

The 11 bit width of the exponent allows the representation of numbers with a decimal exponent between 10^−308 and 10^308, with full 15–17 decimal digits precision.

Since you want 5 fraction digits, that means you're ok up to max. 10 integral digits.

Or you could test it. Sometimes empirical evidence is more convincing:

double value = 0.00009;
while (true) {
    String nextDown = String.format("%.5f", Math.nextDown(value));
    String nextUp   = String.format("%.5f", Math.nextUp(value));
    if (! nextDown.equals(nextUp))
        break; // loss of precision to 5 decimals
    System.out.printf("Good: %.5f%n", value);
    value = value * 10 + 0.00009;
}
System.out.printf("Bad: %.5f%n", value);

Output

Good: 0.00009
Good: 0.00099
Good: 0.00999
Good: 0.09999
Good: 0.99999
Good: 9.99999
Good: 99.99999
Good: 999.99999
Good: 9999.99999
Good: 99999.99999
Good: 999999.99999
Good: 9999999.99999
Good: 99999999.99999
Good: 999999999.99999
Good: 9999999999.99999
Bad: 99999999999.99997

Yup, 10 is good, 11 bad.

Upvotes: 4

Related Questions