Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RoundingMode.UP with DecimalFormat does not work as expected

I'm trying to round up a Double number whenever there is a decimal value. I'm trying the below. The below snippet is rounding up all other values as expected.

1.08 -> 2
9.5 -> 10

but rounds 0.08 as 0 instead of 1. Am I missing something here?

DecimalFormat df = new DecimalFormat("0");
df.setRoundingMode(RoundingMode.UP);
Double number = 0.08;
System.out.println(df.format(number)); // expecting 1 but produces 0
    
number = 1.08;
System.out.println(df.format(number)); // correctly produces 2
like image 281
Sujitha Avatar asked Aug 01 '20 16:08

Sujitha


People also ask

How do you round using DecimalFormat?

DecimalFormat(“0.00”) We can use DecimalFormat("0.00") to ensure the number always round to 2 decimal places. For DecimalFormat , the default rounding mode is RoundingMode. HALF_EVEN , and we can use setRoundingMode(RoundingMode) to set a specified rounding mode.

What is the default rounding mode?

IEEE 754 defines four possible rounding modes: Round to nearest. This is the default mode.

What is RoundingMode in BigDecimal Java?

public enum RoundingMode extends Enum<RoundingMode> Specifies a rounding behavior for numerical operations capable of discarding precision. Each rounding mode indicates how the least significant returned digit of a rounded result is to be calculated.

What is the default rounding mode in Java?

The default rounding mode of DecimalFormat is RoundingMode. HALF_EVEN . This means that it rounds up, or rounds down if the number is nearer to the next neighbour. When the number is exactly between two neighbours (in your case, 2 and 3), it rounds to the nearest even number (in your case, 2).


1 Answers

This looks like a manifestation of bug JDK-8174722, reported for JDK8, and still open with a fix version "tbd" (I see the same behavior on JDK14). In that bug report, rounding up 0.0001 to two fewer decimal places incorrectly results in 0.00 rather than 0.01.

I tried to trace this calculation through the source code of DecimalFormat and found multiple assumptions on numbers of significant digits, retained for compatibility but apparently the cause of issues like this. My hunch based on the source code is that if the first digit after the requested precision is 0 (or close enough) the function rounds down. This behavior is contrary to the documentation which clearly states otherwise.

If it were easy to fix, I gather the linked bug would have been fixed long before now, but it also appears to be a low priority.

As the comments have suggested, using Math.ceil(), Math.floor(), and Math.round() functions would provide more predictable/consistent results and suffice for most use cases.

In your comments, you suggested the user provided the RoundingMode, so if you want a direct application of that enum, the bug report's MCVE includes a workaround using BigDecimal.setScale(). @Andreas posted an application of this workaround in this comment to produce this string:

// Note: this ignores Locale
BigDecimal.valueOf(number).setScale(0, RoundingMode.UP).toPlainString();

Another option using a DecimalFormat would allow changing Locale settings and would be:

df.format(BigDecimal.valueOf(number).setScale(0, RoundingMode.UP));

Note that since you would use the rounding up in setScale() you do not need to also use it in the DecimalFormat.

like image 78
Daniel Widdis Avatar answered Oct 05 '22 12:10

Daniel Widdis