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 ?
(If you are not interested in theory, scroll to end, theres the fix for your code)
The reason is quite simple: As you know the binary system only supports 0
s and 1
s
So, let's look at your values, and what they are in binary representation:
0.1 - 0.0001100110011001100110011001100110011001100110011001101
0.2 - 0.001100110011001100110011001100110011001100110011001101
0.3 - 0.010011001100110011001100110011001100110011001100110011
0.4 - 0.01100110011001100110011001100110011001100110011001101
0.5 - 0.1
0.6 - 0.10011001100110011001100110011001100110011001100110011
0.7 - 0.1011001100110011001100110011001100110011001100110011
0.8 - 0.1100110011001100110011001100110011001100110011001101
0.9 - 0.11100110011001100110011001100110011001100110011001101
What does that mean? 0.1
is a 10th of 1. No big deal in the decimal system, simply shift the separator one position. But in binary you cannot express 0.1 - cause every shift of the decimal sign equals *2
or /2
- depending on the direction. (And 10 cannot be divided into X shifts of 2)
For values you want to divide by multiples of 2 - you get an EXACT result:
1/2 - 0.1
1/4 - 0.01
1/8 - 0.001
1/16- 0.0001
and so on.
Therefore trying to calculate a /10
is an infinite long result, which is truncated when the value runs out of bits.
This said, it is a limitation of the way computers work, that such a value can never be stored with full precision.
Site Note: This "fact" has been ignored with the Patriot System causing it to become unusable after some hours of operating time, see here: http://sydney.edu.au/engineering/it/~alum/patriot_bug.html
But why does it work for every thing but 0.7 + 0.1 - you might ask
If you test your code with 0.8
- it works - but not with 0.7 + 0.1
.
Again, in binary both values are already imprecise. If you sum up both values, the result is even more imprecise, leading to a wrong result:
If you sum up 0.7 and 0.1 (after the decimal separator) you get this:
0.101100110011001100110011001100110011001100110011001 1000
+ 0.000110011001100110011001100110011001100110011001100 1101
---------------------------------------------------------
0.110011001100110011001100110011001100110011001100110 0101
But 0.8 would be
0.110011001100110011001100110011001100110011001100110 1000
Compare the last 4 bits and note, that the resulting "0.8" of the ADDITION is smaller than if you would convert 0.8
to binary directly.
Guess what:
System.out.println(0.7 + 0.1 == 0.8); //returns false
When working with numbers you should set yourself a limit of precision - and ALWAYS round numbers accordingly to avoid such errors (not truncate!):
//compare doubles with 3 decimals
System.out.println((lim(0.7, 3) + lim(0.1, 3)) == lim(0.8, 3)); //true
public static long lim(double d, int t){
return Math.round(d*10*t);
}
To have your code fixed: round it to 4 digits, before truncating after the first digit:
public static double truncate(double x){
long y = (long)((Math.round(x*10000)/10000.0)*10);
double z = (double)y/10;
return z;
}
System.out.println(truncate(0.7+0.1)); //0.8
System.out.println(truncate(0.8)); //0.8
This will still truncate as desired but ensures, that a 0.69999
will be rounded to 0.7
before truncating it. You can set the precision as required for your application. 10, 20 ,30, 40 digits?
Other values will still remain correct, because something like 0.58999
will just be rounded to 0.59 - so still truncated as 0.5
and not rounded to 0.6
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With