Search code examples
doublebigdecimal

Validate if big decimal is created without any precision loss


I have a method which accepts BigDecimal. I want to make sure its decimal value have no precision loss (i.e) input to big decimal is exactly stored as it is. My understanding is that precision loss happens when I try to convert infinite double to big decimal and so, it is recommended to create big decimal using string representation of double. Basically, I want to make sure if big decimal is constructed using BigDecimal(String) for such infinite doubles.

As per my understanding after going through doc, input double value which results in precision loss during big decimal conversion always have very large magnitude which won't fit in 64 bits. Example: 0.1. So, string and double value representation of such big decimals won't match. is it enough to say that precision loss has occurred when string and double value won't match?
Eg:
BigDecimal decimal = new BigDecimal(0.1);
System.out.println(decimal.toString()) // prints 0.1000000000000000055511151231257827021181583404541015625
System.out.println(decimal.doubleValue()) // prints 0.1.
String and double value of big decimal differ and so, precision loss happened.


Solution

  • This idea breaks down if you allow the BigDecimal to be the result of arithmetic.

    If you are going to require the BigDecimal to be the direct, unmodified result of conversion of a decimal string it would be much simpler to require a String argument and convert it to BigDecimal in your method.

    The following program is an attempt to implement and test your validity check. The variable third was calculated without any involvement of doubles, using only decimal strings and BigDecimal, but fails the test.

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    
    public strictfp class Test {
        public static void main(String[] args) {
            BigDecimal third = BigDecimal.ONE.divide(new BigDecimal("3"), 30, RoundingMode.HALF_EVEN);
            testIt(new BigDecimal("0.1"));
            testIt(new BigDecimal(0.1));
            testIt(third);
        }
    
        static void testIt(BigDecimal in) {
            System.out.println(in+" "+isValid(in));
        }
    
        static boolean isValid(BigDecimal in) {
            double d = in.doubleValue();
            String s1 = in.toString();
            String s2 = Double.toString(d);
            return s1.equals(s2);
        }
    }
    

    Output:

    0.1 true
    0.1000000000000000055511151231257827021181583404541015625 false
    0.333333333333333333333333333333 false