Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Biggest amount in USD (double) that can accurately be converted to cents (long)

I'm writing a bank program with a variable long balance to store cents in an account. When users inputs an amount I have a method to do the conversion from USD to cents:

public static long convertFromUsd (double amountUsd) {
   if(amountUsd <= maxValue || amountUsd >= minValue) {
      return (long) (amountUsd * 100.0)
   } else {
      //no conversion (throws an exception, but I'm not including that part of the code)
   }
}

In my actual code I also check that amountUsd does not have more than 2 decimals, to avoid inputs that cannot be accurately be converted (e.g 20.001 dollars is not exactly 2000 cents). For this example code, assume that all inputs has 0, 1 or 2 decimals.

At first I looked at Long.MAX_VALUE (9223372036854775807 cents) and assumed that double maxValue = 92233720368547758.07 would be correct, but it gave me rounding errors for big amounts:

convertFromUsd(92233720368547758.07) gives output 9223372036854775807

convertFromUsd(92233720368547758.00) gives the same output 9223372036854775807

What should I set double maxValue and double minValue to always get accurate return values?

like image 866
Alexander Wiklund Avatar asked Dec 31 '22 19:12

Alexander Wiklund


1 Answers

You could use BigDecimal as a temp holder

If you have a very large double (something between Double.MAX_VALUE / 100.0 + 1 and Double.MAX_VALUE) the calculation of usd * 100.0 would result in an overflow of your double.

But since you know that every possible result of <any double> * 100 will fit in a long you could use a BigDecimal as a temporary holder for your calculation. Also, the BigDecimal class defines two methods which come in handy for this purpose:

  • BigDecimal#movePointRight
  • BigDecimal#longValueExact

By using a BigDecimal you don't have to bother about specifying a max-value at all -> any given double representing USD can be converted to a long value representing cents (assuming you don't have to handle cent-fractions).

double usd = 123.45;
long cents = BigDecimal.valueOf(usd).movePointRight(2).setScale(0).longValueExact();

Attention: Keep in mind that a double is not able to store the exact USD information in the first place. It is not possible to restore the information that has been lost by converting the double to a BigDecimal. The only advantage a temporary BigDecimal gives you is that the calculation of usd * 100 won't overflow.

like image 94
Felix Avatar answered Feb 05 '23 11:02

Felix