Search code examples
javatypescurrency

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?


Solution

  • Using a double, the biggest, in Java, would be: 70368744177663.99.

    What you have in a double is 64 bit (8 byte) to represent:

    1. Decimals and integers
    2. +/-

    Problem is to get it to not round of 0.99 so you get 46 bit for the integer part and the rest need to be used for the decimals.

    You can test with the following code:

    double biggestPossitiveNumberInDouble = 70368744177663.99;
    
    for(int i=0;i<130;i++){
        System.out.printf("%.2f\n", biggestPossitiveNumberInDouble);
        biggestPossitiveNumberInDouble=biggestPossitiveNumberInDouble-0.01;
    }
    

    If you add 1 to biggestPossitiveNumberInDouble you will see it starting to round off and lose precision. Also note the round off error when subtracting 0.01.

    First iterations

    70368744177663.99
    70368744177663.98
    70368744177663.98
    70368744177663.97
    70368744177663.96
    ...
    

    The best way in this case would not to parse to double:

    System.out.println("Enter amount:");
    String input = new Scanner(System.in).nextLine();
    
    int indexOfDot = input.indexOf('.');
    if (indexOfDot == -1) indexOfDot = input.length();
    int validInputLength = indexOfDot + 3;
    if (validInputLength > input.length()) validInputLength = input.length();
    String validInput = input.substring(0,validInputLength);
    long amout = Integer.parseInt(validInput.replace(".", ""));
    
    System.out.println("Converted: " + amout);
    

    This way you don't run into the limits of double and just have the limits of long.

    But ultimately would be to go with a datatype made for currency.