Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly floor doubles in Java to two digits

I'm trying to floor (not round!) doubles to two digits in a Java application. I use DecimalFormat for this, but noticed that for negative values close to zero, the values are not rounded to -0.01 but to -0.00.

public class MyTest {

    void formatAndPrint(double value) {
        DecimalFormat df = new DecimalFormat("0.00");
        df.setRoundingMode(RoundingMode.FLOOR);

        System.out.println(value + " => " + df.format(value));
    }

    @Test
    public void testFloor() {
        formatAndPrint(2.3289);  //  2.32
        formatAndPrint(2.3);     //  2.30
        formatAndPrint(-1.172);  // -1.18
        formatAndPrint(0.001);   //  0.00
        formatAndPrint(0.0001);  //  0.00
        formatAndPrint(-0.001);  // -0.01
        formatAndPrint(-0.0001); // -0.00 WRONG, expected: -0.01
    }
}

Other solutions like Math.floor(value * 100.0) / 100.0 does not have this issue but has other issues, like wrongly flooring 2.3 to 2.29.

Is there a flooring solution for Java which works in all cases?

like image 870
Jos de Jong Avatar asked Mar 29 '18 12:03

Jos de Jong


People also ask

How to get 2 decimal places in Java for double?

format(“%. 2f”) We also can use String formater %2f to round the double to 2 decimal places.

How many decimal places in double Java?

The number of decimal places in a double is 16.

How do you number a floor in Java?

Java Math floor() floor() method with the help of examples. The floor() method rounds the specified double value downward and returns it. The rounded value will be equal to a mathematical integer. That is, the value 3.8 will be rounded to 3.0 which is equal to integer 3.


1 Answers

The BigDecimal implementation works fine.

BigDecimal value = new BigDecimal(-0.0001);
value = value.setScale(2, BigDecimal.ROUND_FLOOR);
System.out.println(value.toPlainString());

Output: -0.01

2.3 will still floor to 2.29. This is not a result of the flooring, but instead the result of writing 2.3 as double. The closest 64-Bit IEEE754 representation of 2.3 is 2.29999...

When instantiating the BigDecimal you can instead provide a String like new BigDecimal("2.3") which will not result in such errors.


EDIT: As @Hulk mentioned, you can/should use BigDecimal.valueOf() instead, which will call Double.toString() for instantiation. Double.toString() does not use the exact value of the double but will instead "round" to the closest unique value.

Double JavaDoc:

There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double.

Here is a small table of long values (used with Double.longBitsToDouble(long) and their BigDecimal and Double.toString() counterparts:

long value            Double.toString()    new BigDecimal()
4612361558371493473 - 2.2999999999999976 - 2.299999999999997601918266809661872684955596923828125
4612361558371493474 - 2.299999999999998  - 2.29999999999999804600747665972448885440826416015625
4612361558371493475 - 2.2999999999999985 - 2.299999999999998490096686509787105023860931396484375
4612361558371493476 - 2.299999999999999  - 2.2999999999999989341858963598497211933135986328125
4612361558371493477 - 2.2999999999999994 - 2.299999999999999378275106209912337362766265869140625
4612361558371493478 - 2.3                - 2.29999999999999982236431605997495353221893310546875
4612361558371493479 - 2.3000000000000003 - 2.300000000000000266453525910037569701671600341796875
4612361558371493480 - 2.3000000000000007 - 2.300000000000000710542735760100185871124267578125
4612361558371493481 - 2.300000000000001  - 2.300000000000001154631945610162802040576934814453125
4612361558371493482 - 2.3000000000000016 - 2.30000000000000159872115546022541821002960205078125
like image 83
Link64 Avatar answered Oct 21 '22 09:10

Link64