Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RoundingMode.HALF_DOWN issue in Java8

I am using jdk 1.8.0_45, and our tests discovered a bug in rouding. RoundingMode.HALF_DOWN works the same as RoundingMode.HALF_UP when the last decimal that decide the rounding is 5.

I found related issues with RoundingMode.HALF_UP, but they are fixed in update 40. I also put a bug to the oracle, but from my experience they are really unresponsive.

package testjava8;

import java.math.RoundingMode;
import java.text.DecimalFormat;

public class Formatori {

    public static void main(String[] args) {
        DecimalFormat format = new DecimalFormat("#,##0.0000");
        format.setRoundingMode(RoundingMode.HALF_DOWN);
        Double toFormat = 10.55555;
        System.out.println("Round down");
        System.out.println(format.format(toFormat));

        format.setRoundingMode(RoundingMode.HALF_UP);
        toFormat = 10.55555;
        System.out.println("Round up");
        System.out.println(format.format(toFormat));
    }
}

Actual result: Round down 10.5556 Round up 10.5556

Expected result(obtain with jdk 1.7): Round down 10.5555 Round up 10.5556

like image 653
cristi Avatar asked Jun 11 '15 10:06

cristi


People also ask

What RoundingMode down?

DOWN − Rounding mode to round towards zero. FLOOR − Rounding mode to round towards negative infinity. HALF_DOWN − Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.

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?

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).

What is rounding mode ceiling?

When you round toward ceiling, both positive and negative numbers are rounded toward positive infinity. As a result, a positive cumulative bias is introduced in the number. In the MATLAB® software, you can round to ceiling using the ceil function.


2 Answers

Seems that it's intended change. The JDK 1.7 behavior was incorrect.

The problem is that you simply cannot represent the number 10.55555 using the double type. It stores the data in IEEE binary format, so when you assign the decimal 10.55555 number to the double variable, you actually get the closest value which can be represented in IEEE format: 10.555550000000000210320649784989655017852783203125. This number is bigger than 10.55555, so it's correctly rounded to 10.5556 in HALF_DOWN mode.

You can check some numbers which can be exactly represented in binary. For example, 10.15625 (which is 10 + 5/32, thus 1010.00101 in binary). This number is rounded to 10.1562 in HALF_DOWN mode and 10.1563 in HALF_UP mode.

If you want to restore the old behavior, you can first convert your number to BigDecimal using BigDecimal.valueOf constructor, which "translates a double into a BigDecimal, using the double's canonical string representation":

BigDecimal toFormat = BigDecimal.valueOf(10.55555);
System.out.println("Round down");
System.out.println(format.format(toFormat)); // 10.5555

format.setRoundingMode(RoundingMode.HALF_UP);
toFormat = BigDecimal.valueOf(10.55555);
System.out.println("Round up");
System.out.println(format.format(toFormat)); // 10.5556
like image 105
Tagir Valeev Avatar answered Sep 28 '22 04:09

Tagir Valeev


The change of behaviour is documented in the release notes of Java 8

When using the NumberFormat and DecimalFormat classes, the rounding behavior of previous versions of the JDK was wrong in some corner cases. [...]

As an example, when using the default recommended NumberFormatFormat API form: NumberFormat nf = java.text.NumberFormat.getInstance() followed by nf.format(0.8055d), the value 0.8055d is recorded in the computer as 0.80549999999999999378275106209912337362766265869140625 since this value cannot be represented exactly in the binary format. Here, the default rounding rule is "half-even", and the result of calling format() in JDK 7 is a wrong output of "0.806", while the correct result is "0.805", since the value recorded in memory by the computer is "below" the tie.

This new behavior is also implemented for all rounding positions that might be defined by any pattern chosen by the programmer (non default patterns).

like image 45
assylias Avatar answered Sep 28 '22 06:09

assylias