如何在Java中正确将double格式化为两位小数

5

我正在尝试在Java应用程序中对双精度浮点数进行取整(而不是四舍五入)以保留两位小数。我使用DecimalFormat实现这一目标,但注意到对于接近零的负值,结果不是-0.01而是-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
    }
}

其他方案,例如Math.floor(value * 100.0) / 100.0没有这个问题,但还有其他问题,比如错误地将2.3向下舍入到2.29

是否有适用于Java的舍位解决方案适用于所有情况?


1
你知道十进制数在二进制中并不是精确表示的吗? - Thorbjørn Ravn Andersen
浮点数计算是否存在问题 - Serge Ballesta
是的,我完全意识到浮点数的局限性。我希望有一些聪明的解决方案。 - Jos de Jong
1个回答

8
BigDecimal实现工作正常。
BigDecimal value = new BigDecimal(-0.0001);
value = value.setScale(2, BigDecimal.ROUND_FLOOR);
System.out.println(value.toPlainString());

输出结果:-0.01

2.3 仍然会舍入为2.29。这不是因为舍入的原因,而是因为使用了double类型表示2.3时,最接近的64位IEEE754表示形式是2.29999...

当实例化BigDecimal时,可以提供一个字符串,例如 new BigDecimal("2.3"),这样就不会出现这种错误。


编辑:正如@Hulk所提到的,您可以/应该使用BigDecimal.valueOf(),它将调用Double.toString()来进行实例化。而Double.toString()不会使用精确值的double,而是会“四舍五入”到最接近的唯一值。

Double JavaDoc:

必须至少有一个数字来表示小数部分,除此之外还需要尽可能多但仅有所需的数字,以使参数值在相邻的double类型值中得到独特区分。

下面是一个小表格,其中包含long值(与Double.longBitsToDouble(long)一起使用)及其BigDecimalDouble.toString()对应项:

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

也尝试过这种方式,但这会将2.3变成2.29。 - Malte Hartwig
@dasblinkenlight,这就是为什么Link64使用了ROUND_FLOOR而不是ROUND_DOWN - Malte Hartwig
@Malte Hartwig 这是一个精度错误。当创建 BigDecimal 时,您可以将值提供为字符串,它会非常好地工作。 - Link64
哦,太棒了,确实有效。你能把它加到你的答案里吗? - Malte Hartwig
谢谢@Link64,使用BigDecimal在这里非常有意义!我不知道为什么我自己想不到。 - Jos de Jong
2
值得一提的是,从doublefloat创建BigDecimal的首选方法是BigDecimal.valueOf,它的工作方式类似于字符串构造函数,并产生更直观的结果,避免了2.3问题:“这通常是将double(或float)转换为BigDecimal的首选方法,因为返回的值等于使用Double.toString(double)的结果构造BigDecimal的结果。” - Hulk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接