Round a double in Java
JavaFloating PointJava Problem Overview
I have found this great solution for rounding:
static Double round(Double d, int precise) {
BigDecimal bigDecimal = new BigDecimal(d);
bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
return bigDecimal.doubleValue();
}
However, the results are confusing:
System.out.println(round(2.655d,2)); // -> 2.65
System.out.println(round(1.655d,2)); // -> 1.66
Why is it giving this output? I'm using jre 1.7.0_45.
Java Solutions
Solution 1 - Java
You have to replace
BigDecimal bigDecimal = new BigDecimal(d);
with
BigDecimal bigDecimal = BigDecimal.valueOf(d);
and you will get the expected results:
2.66
1.66
Explanation from Java API:
BigDecimal.valueOf(double val) - uses the double's canonical string representation provided by the Double.toString() method. This is preferred way to convert a double (or float) into a BigDecimal.
new BigDecimal(double val) - uses the exact decimal representation of the double's binary floating-point value and thus results of this constructor can be somewhat unpredictable.
Solution 2 - Java
You may try to change your program like this:-
static Double round(Double d, int precise)
{
BigDecimal bigDecimal = BigDecimal.valueOf(d);
bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
return bigDecimal.doubleValue();
}
Success time: 0.07 memory: 381184 signal:0
Rounded: 2.66
Rounded: 1.66
Success time: 0.07 memory: 381248 signal:0
Rounded: 2.66
Rounded: 1.66
Reason why you are getting the expected result with BigDecimal.valueOf
and not with new BigDecimal
, in the words of Joachim Sauer:
> BigDecimal.valueOf(double)
will use the canonical String representation of the double value passed in to instantiate the BigDecimal object. In other words: The value of the BigDecimal
object will be what you see when you do System.out.println(d)
.
>
> If you use new BigDecimal(d)
however, then the BigDecimal will try to represent the double value as accurately as possible. This will usually result in a lot more digits being stored than you want.
Hence resulting in some confusion which you are watching in your program.
From the Java Doc:
> BigDecimal.valueOf(double val) - Translates a double into a BigDecimal, using the double's canonical string representation
> provided by the Double.toString(double) method.
>
> new BigDecimal(double val) -
>
> Translates a double into a BigDecimal which is the exact decimal
> representation of the double's binary floating-point value. The scale
> of the returned BigDecimal is the smallest value such that (10scale ×
> val) is an integer. Notes:
>
> - The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a
> BigDecimal which is exactly equal to 0.1 (an unscaled value of 1,
> with a scale of 1), but it is actually equal to
> 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that
> matter, as a binary fraction of any finite length). Thus, the value
> that is being passed in to the constructor is not exactly equal to
> 0.1, appearances notwithstanding.
> - The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal
> which is exactly equal to 0.1, as one would expect. Therefore, it
> is generally recommended that the String constructor be used in
> preference to this one.
> - When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the
> same result as converting the double to a String using the
> Double.toString(double) method and then using the BigDecimal(String)
> constructor. To get that result, use the static valueOf(double)
> method.
Solution 3 - Java
This test case ends up pretty self-explanatory:
public static void main (String[] args) throws java.lang.Exception
{
System.out.println("Rounded: " + round(2.655d,2)); // -> 2.65
System.out.println("Rounded: " + round(1.655d,2)); // -> 1.66
}
public static Double round(Double d, int precise)
{
BigDecimal bigDecimal = new BigDecimal(d);
System.out.println("Before round: " + bigDecimal.toPlainString());
bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
System.out.println("After round: " + bigDecimal.toPlainString());
return bigDecimal.doubleValue();
}
Output:
Before round: 2.654999999999999804600747665972448885440826416015625
After round: 2.65
Rounded: 2.65
Before round: 1.6550000000000000266453525910037569701671600341796875
After round: 1.66
Rounded: 1.66
A dirty hack to fix it would be to round in two steps:
static Double round(Double d, int precise)
{
BigDecimal bigDecimal = new BigDecimal(d);
System.out.println("Before round: " + bigDecimal.toPlainString());
bigDecimal = bigDecimal.setScale(15, RoundingMode.HALF_UP);
System.out.println("Hack round: " + bigDecimal.toPlainString());
bigDecimal = bigDecimal.setScale(precise, RoundingMode.HALF_UP);
System.out.println("After round: " + bigDecimal.toPlainString());
return bigDecimal.doubleValue();
}
Here, 15
is a bit under the maximum number of digits a double can represent in base 10. Output:
Before round: 2.654999999999999804600747665972448885440826416015625
Hack round: 2.655000000000000
After round: 2.66
Rounded: 2.66
Before round: 1.6550000000000000266453525910037569701671600341796875
Hack round: 1.655000000000000
After round: 1.66
Rounded: 1.66
Solution 4 - Java
As said in API
> 1. The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a > BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with > a scale of 1), but it is actually equal to > 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that > matter, as a binary fraction of any finite length). Thus, the value > that is being passed in to the constructor is not exactly equal to > 0.1, appearances notwithstanding. > > 2. The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which > is exactly equal to 0.1, as one would expect. Therefore, it is > generally recommended that the String constructor be used in > preference to this one. > > 3. When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the > same result as converting the double to a String using the > Double.toString(double) method and then using the BigDecimal(String) > constructor. To get that result, use the static valueOf(double) > method.
It's because of cannot represent double value exactly. So you have to use BigDecimal bigDecimal = BigDecimal.valueOf(d);
instead of BigDecimal bigDecimal = new BigDecimal(d);
Solution 5 - Java
Rounding a double
resp Double
in itself does not make much sense, as a double
datatype cannot be rounded (easily, or at all?).
What you are doing is:
- Take a
Double d
as input and aint precise
number of digits behind the seperator. - Create a
BigDecimal
from thatd
. - Round the
BigDecimal
correctly. - Return the
double
value of thatBigDecimal
, which has no rounding applied to it anymore.
You can go two ways:
- You can return a
BigDecimal
that represents the rounded double, and later decide what you do with it. - You can return a
String
representing the roundedBigDecimal
.
Either of those ways will make sense.
Solution 6 - Java
Decimal numbers can't be represented exactly in double.
So 2.655 ends up being this: 2.65499999999999980460074766597
whereas 1.655 ends up being this: 1.655000000000000026645352591