Java 8中BigDecimal multiply方法在倒数时丢失精度

5

这里是Java 8。根据Google的说法:

我写了这段Java代码:

public class MeasurementConverter {
    private static final int SCALE = 2;

    public static void main(String[] args) {
        new MeasurementConverter().run();
    }

    private void run() {
        BigDecimal hundredLbs = new BigDecimal(100.00);
        BigDecimal hundredInches = new BigDecimal(100.00);

        System.out.println(hundredLbs + " pounds is " + poundsToKilos(hundredLbs) + " kilos and converts back to " + kilosToPounds(poundsToKilos(hundredLbs)) + " pounds.");
        System.out.println(hundredInches + " inches is " + inchesToMeters(hundredInches) + " meters and converts back to " + metersToInches(inchesToMeters(hundredInches)) + " inches.");
    }

    private BigDecimal metersToInches(BigDecimal meters) {
        return meters.multiply(new BigDecimal("39.3701").setScale(SCALE, BigDecimal.ROUND_HALF_UP));
    }

    private BigDecimal inchesToMeters(BigDecimal inches) {
        return inches.multiply(new BigDecimal("0.0254").setScale(SCALE, BigDecimal.ROUND_HALF_UP));
    }

    private BigDecimal kilosToPounds(BigDecimal kilos) {
        return kilos.multiply(new BigDecimal("2.20462").setScale(SCALE, BigDecimal.ROUND_HALF_UP));
    }

    private BigDecimal poundsToKilos(BigDecimal pounds) {
        return pounds.multiply(new BigDecimal("0.45359").setScale(SCALE, BigDecimal.ROUND_HALF_UP));
    }
}

当运行时,它会打印出以下内容:
100 pounds is 45.00 kilos and converts back to 99.0000 pounds.
100 inches is 3.00 meters and converts back to 118.1100 inches.

我原本期望它会输出:

100.00 pounds is 45.00 kilos and converts back to 100.00 pounds.
100.00 inches is 3.00 meters and converts back to 100.00 inches.

我只关心英制与公制的转换精确到小数点后两位,并且最终输出结果始终精确到小数点后两位。有人能看出我的问题在哪里,以及应该如何修复吗?


5
请注意,100磅等于45.36千克,而100英寸等于2.54米。 - Oliver Charlesworth
1
请记住,将double传递给new BigDecimal会丢失精度,因为您没有使用确切的值,而是使用最接近的可表示值作为双精度浮点数。请改用new BigDecimal("39.37")等。还要记住,这些倒数不是精确的,因此39.37 * 0.0254 = 0.999998,而不是1。 - Andy Turner
谢谢@AndyTurner (+1),但请看一下我的更新,包括您提出的更改。转换仍然相差甚远!有什么想法吗? - smeeb
我的上一个评论是一个提示。 - Oliver Charlesworth
增加规模。 - Andy Turner
SCALE和HALF_UP参数不是用于乘法吗?你只是将它们添加到BigDecimal中。 - Marcos Vasconcelos
4个回答

5
您的括号搞错了。请考虑以下内容:
kilos.multiply(new BigDecimal("2.20462").setScale(SCALE, BigDecimal.ROUND_HALF_UP));

在这里,您首先将比例设置为2.20462,结果为2.20,然后再乘以。

现在考虑反向转换:

pounds.multiply(new BigDecimal("0.45359").setScale(SCALE, BigDecimal.ROUND_HALF_UP));

在这里,您实际上是乘以0.45。而且2.2*0.45=0.99,这解释了结果。

您已经对乘法的结果设置了比例,而不是乘数。基本上,最后一个括号放错了位置。它应该像这样:

pounds.multiply(new BigDecimal("0.45359")).setScale(SCALE, BigDecimal.ROUND_HALF_UP);

这里是已纠正的代码

但是为什么要费心使用 setScale 呢? - Oliver Charlesworth
@OliverCharlesworth,问题已经在原帖中了。 - lexicore
实际上 - 我所建议的是答案应该是“只需删除 setScale” ;) (然后将其放入输出格式中) - Oliver Charlesworth

3
“SCALE”和“HALF_UP”参数是用于乘法运算的吗?你只是将其添加到了BigDecimal中。我认为你想要的是:
inches.multiply(new BigDecimal("0.0254"), new MathContext(SCALE, RoundingMode.HALF_UP));

将SCALE更改为8并使用您的代码与MathContext一起使用,结果如下:
100 pounds is 45.35900 kilos and converts back to 99.999359 pounds.
100 inches is 2.5400 meters and converts back to 100.00005 inches.

谢谢@Marcos (+1) - 新的MathContext实例有多昂贵/重?是否有一种方法可以在应用程序启动时仅创建它一次?我之所以问,是因为在实际使用中,这些方法将会被频繁调用... - smeeb
我认为MathContext仅适用于计算之后。 - Marcos Vasconcelos

0

你的转换因子不正确。你将转换因子只舍入到小数点后两位:

private static final int SCALE = 2;
[...]    
new BigDecimal("0.0254").setScale(SCALE, BigDecimal.ROUND_HALF_UP)

因此,"0.0254" 被四舍五入为 0.03,这与您的 "反转" 转换系数 39.37 不符。这就是为什么您的反转结果不等于原始值的原因。

0
你正在转换过程中进行四舍五入。因此,反向操作的输入值已经失准。
如果你想要显示这些值:
100.00 pounds is 45.00 kilos and converts back to 100.00 pounds.
100.00 inches is 3.00 meters and converts back to 100.00 inches.

从所有私有方法中删除 setScale。例如:

private BigDecimal metersToInches(BigDecimal meters) {
    return meters.multiply(new BigDecimal("39.3701"));
}

然后,您可以使用所需的格式简单地格式化输出:

private BigDecimal formatOutput(BigDecimal value) {
    return value.setScale(0, BigDecimal.ROUND_HALF_UP).setScale(SCALE);
}

最后:

System.out.println(hundredLbs + " pounds is " + formatOutput(poundsToKilos(hundredLbs)) + " kilos and converts back to " + formatOutput(kilosToPounds(poundsToKilos(hundredLbs))) + " pounds.");
System.out.println(hundredInches + " inches is " + formatOutput(inchesToMeters(hundredInches)) + " meters and converts back to " + formatOutput(metersToInches(inchesToMeters(hundredInches))) + " inches.");

给出:

100.00 pounds is 45.00 kilos and converts back to 100.00 pounds.
100.00 inches is 3.00 meters and converts back to 100.00 inches.

或者,如果你想要显示带有小数位的值,你可以简单地改变 formatOutput 方法为:

private BigDecimal formatOutput(BigDecimal value) {
    return value.setScale(SCALE, BigDecimal.ROUND_HALF_UP);
}

以下方式将以这种方式打印出值(我认为这样更好、更正确!)

100.00 pounds is 45.36 kilos and converts back to 100.00 pounds.
100.00 inches is 2.54 meters and converts back to 100.00 inches.

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