Round a double in Java

JavaFloating Point

Java 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();
}

Sample Ideone

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:

  1. Take a Double d as input and a int precise number of digits behind the seperator.
  2. Create a BigDecimal from that d.
  3. Round the BigDecimal correctly.
  4. Return the double value of that BigDecimal, which has no rounding applied to it anymore.

You can go two ways:

  1. You can return a BigDecimal that represents the rounded double, and later decide what you do with it.
  2. You can return a String representing the rounded BigDecimal.

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpushisticView Question on Stackoverflow
Solution 1 - JavaBorisView Answer on Stackoverflow
Solution 2 - JavaRahul TripathiView Answer on Stackoverflow
Solution 3 - JavaMartijn CourteauxView Answer on Stackoverflow
Solution 4 - JavaAbimaran KugathasanView Answer on Stackoverflow
Solution 5 - JavaskiwiView Answer on Stackoverflow
Solution 6 - JavaLevView Answer on Stackoverflow