Search code examples
javadoublebigdecimal

Why this weird output with truncate and BigDecimal?


I am calling a truncate method to truncate the double value so that there should be a single digit after decimal (without rounding off),
For Ex. truncate(123.48574) = 123.4.

My truncate method is something like this

public double truncate (double x) {
    long y = (long) (x * 10);
    double z = (double) (y / 10);
    return z;
}

Its working just fine for almost all the values except for this weird output.

double d = 0.787456;
d = truncate(d + 0.1); //gives 0.8 as expected. Okay.

But,

double d = 0.7;
d = truncate(d + 0.1); //should also give 0.8. But its giving 0.7 only. 
                       //Strange I don't know why?

Infact it works fine for all other 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, - , 0.8, 0.9
I mean for example,

double d = 0.8;
d = truncate(d + 0.1); //gives 0.9 as expected

I tried it with BigDecimal too. But the same. No change. Here is the code for that.

double d = 0.7;
BigDecimal a = new BigDecimal(d + 0.1);
BigDecimal floored = a.setScale(1, BigDecimal.ROUND_DOWN);
double d1 = floored.doubleValue();
System.out.println(d1); //Still gives 0.7

And again the real fact is that it works fine with Math.round.

public double roundUp1d (double d) {
    return Math.round(d * 10.0) / 10.0;
}

So if I call roundUp1d(0.7 + 0.1), it gives 0.8 as expected. But I don't want the values to be rounded off so I can't use this.

Whats the problem with 0.7 ?


Solution

  • Floating points are inherently inaccurate by their design. Other answers here already explain the theory behind this inaccuracy. It is highly recommended that you use BigDecimal and BigInteger instead.

    In my answer I want to elaborate on how you are using BigDecimal wrongly and how you can use it correctly. Don't make the mistake of simply using these classes as a wrapper for floating point calculations. In your present code:

    BigDecimal a = new BigDecimal(d + 0.1);
    

    Even though you are trying to use BigDecimal here, you are still performing the addition using regular floating point calculations. This is exactly the same as doing:

    double d_a = d + 0.1; //0.799999999 ad infinitum
    BigDecimal a = new BigDecimal(d_a);
    

    To take advantage of the accuracy of the BigX classes, you must use their own calculation methods, as well as the valueOf static method (not the constructor):

    BigDecimal a = BigDecimal.valueOf(d).add( BigDecimal.valueOf(0.1) );
    

    Here, two BigDecimal objects are created to match exactly 0.7 and 0.1, then the add method is used to calculate their sum and produce a third BigDecimal object (which will be 0.8 exactly).

    Using the valueOf static method instead of the constructor ensures the created BigDecimal object represents the exact double value as it is shown when converted to a string (0.7 as a string is "0.7"), rather than the approximate value stored by the computer to represent it (the computer stores 0.7 as 0.699999999 ad infinitum).