2

Why is this DecimalFormat not rounding using RoundingMode.HALF_UP as expected, and what should be done to get the expected output? Can it be done with DecimalFormat?

DecimalFormat df = new DecimalFormat("0.0");

df.setRoundingMode(RoundingMode.HALF_UP);

df.format(0.05); // expecting 0.1, getting 0.1
df.format(0.15); // expecting 0.2, getting 0.1  << unexpected

df.format(0.06); // expecting 0.1, getting 0.0  << unexpected

I have seen the answers from this question, specifically this answer, but it only seems to work when rounding to an integer.

My JDK is 8.0.110 and my JRE is 8.0.730.2

Community
  • 1
  • 1
4castle
  • 32,613
  • 11
  • 69
  • 106

3 Answers3

4

(Answer below using Java 8.)

The issue you are seeing comes from specifying "0.15" or "0.05" in code, which when represented as a double is something slightly less than 0.15. Check this out

DecimalFormat df = new DecimalFormat("#.#");

df.setRoundingMode(RoundingMode.HALF_UP);

BigDecimal bd = new BigDecimal(0.15);
System.out.println("bd=" + bd);
System.out.println(df.format(0.15)); // expecting 0.1, getting 0.1
bd = new BigDecimal(0.05);
System.out.println("bd=" + bd);
System.out.println(df.format(0.05));
bd = new BigDecimal(0.06);
System.out.println("bd=" + bd);
System.out.println(df.format(0.06));

The output of this code is

bd=0.1499999999999999944488848768742172978818416595458984375
0.1
bd=0.05000000000000000277555756156289135105907917022705078125
0.1
bd=0.059999999999999997779553950749686919152736663818359375
0.1

A possible solution (if you absolutely need it to round the right way) is to use BigDecimal.valueOf to create the value. For example

BigDecimal bd = BigDecimal.valueOf(0.15);
System.out.println("bd=" + bd);
System.out.println(df.format(bd)); // expecting 0.1, getting 0.1
bd = BigDecimal.valueOf(0.05);
System.out.println("bd=" + bd);
System.out.println(df.format(bd));
bd = BigDecimal.valueOf(0.06);
System.out.println("bd=" + bd);
System.out.println(df.format(bd));

Will now yield

bd=0.15
0.2
bd=0.05
0.1
bd=0.06
0.1

BTW, as Scary Wombat pointed out, the mask set as 0.0 instead of #.# will make 0.6 0. But I think that was a later edit then when I started looking at it. Use #.#.

tofutim
  • 22,664
  • 20
  • 87
  • 148
  • So the problem is `DecimalFormat df = new DecimalFormat("0.0");` vs `DecimalFormat df = new DecimalFormat("#.#");` ? – Scary Wombat May 13 '16 at 04:40
  • @ScaryWombat He didn't say so. What are you taking about? – user207421 May 13 '16 at 04:41
  • The problem is writing "0.15" in the code, which is actually not 0.15, it is stored in the system as 0.1499999999999999944488848768742172978818416595458984375 – tofutim May 13 '16 at 04:43
  • 1
    @EJP Hence my `?` mark. The OP states using mask of `"0.0"` with a value of 0.06 becomes `0.0` whereas this answer is using a mask or `"#.#"` and the value becomes `0.1` – Scary Wombat May 13 '16 at 04:44
  • I'm getting `"0"` and `"0.0"` from `"#.#"` and `"0.0"` with `0.06`, so I figured they were the same. This answer is very helpful btw. Thanks very much – 4castle May 13 '16 at 04:50
  • I'm going to accept this answer, since it includes how to get the expected output, but many thanks to both answers so far for the insight. I'm going to consider the `0.06` thing to be a bug, because I can't reproduce it in other versions either. – 4castle May 13 '16 at 05:11
4

When you do

df.format(0.15);

there are actually two rounding operations. The obvious one is the one you asked for with df.format, but there's an earlier one that happens at compile time.

The decimal value 0.15 isn't representable as a double. Doubles can only represent rationals whose denominator is a power of two, just like decimal notation can only represent rationals whose denominator is a power of ten. When you write 0.15, the compiler rounds this to the closest value representable as a double, and that value happens to be

0.1499999999999999944488848768742172978818416595458984375

which df.format correctly rounds down. It's not exactly halfway between 0.1 and 0.2, so the HALF_UP rounding mode doesn't matter.

As for

df.format(0.06); // expecting 0.1, getting 0.0  << unexpected

If you're seeing that, that's a bug. It looks like it matches one linked by ScaryWombat, reported to be fixed in 8u40 and 8u45. A test on Ideone, which uses 8u51, shows the correct behavior. Updating your Java should resolve the issue.

user2357112
  • 260,549
  • 28
  • 431
  • 505
0

I think the answer is IEEE Standard for Floating-Point Arithmetic (IEEE 754). The Wiki article has a good example in the Rounding rules section. Also you can read section 9.1 of Introduction to Java that expends more about floating point. BigDecimal solves most of these short coming by storing the uncalled value inside a BigInteger, the precision and scale in separate in integer fields.

Kalenda
  • 1,847
  • 14
  • 15