简体   繁体   中英

DecimalFormat using incorrect RoundingMode even when set explicitly

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

(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 #.#.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM