BigDecimal:使用setScale进行HALF_UP舍入

3

我们有以下代码:

BigDecimal net = price
.divide(taxCumulative, RoundingMode.HALF_UP)
.setScale(2, BigDecimal.ROUND_UP); 

我们正在对此进行单元测试,根据我们是否在测试类上使用 @Transactional,结果有所不同。
我只是想知道,在使用 setScale 之前还是之后,我们是否应该期望应用 HALF_UP
例如:
假设:
价格 = 4.00
税累计 = 1.20
您会期望计算方式如下:
a) 4.00 / 1.20 = 3.33333333... --> HALF_UP --> 3 --> setScale --> 3 或者
b) 4.00 / 1.20 = 3.33333333... --> HALF_UP with setScale 2 --> 3.33 正如我所说,我们针对这段代码进行了单元测试,并且在是否使用 @Transactional 时行为不同。因此我们不能得出结论。在现实世界中,结果是b。但是 a 也是有意义的。
您有什么想法吗?
更新:
根据 @Mark 的建议,我创建了一个在 Spring 上下文之外的测试。(但我想没有办法在线分享它,就像 codepen 那样)。
package com.company;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Main {

    public static void main(String[] args) {
        BigDecimal price = new BigDecimal("4.00");
        BigDecimal taxCumulative = new BigDecimal("1.20");
        BigDecimal net = price.divide(taxCumulative, RoundingMode.HALF_UP).setScale(2, BigDecimal.ROUND_UP);
        System.out.println("With String constructor, the net =  " + net.toString());
        // prints 3.33
        price = new BigDecimal(4.00);
        taxCumulative = new BigDecimal(1.20);
        net = price.divide(taxCumulative, RoundingMode.HALF_UP).setScale(2, BigDecimal.ROUND_UP);
        System.out.println("With doubles, the net =  " + net.toString());
        // prints 3.00
    }
}

正如@gtgaxiola指出的那样,String构造函数是有区别的。

至于使用setScale进行除法操作的问题。我做了一些更多的测试:

price = new BigDecimal("4.000000");
taxCumulative = new BigDecimal("1.2000000");
net = price.divide(taxCumulative, RoundingMode.HALF_UP).setScale(2, BigDecimal.ROUND_UP);
// 3.34

price = new BigDecimal("4.000000");
taxCumulative = new BigDecimal("1.2000000");
net = price.divide(taxCumulative, RoundingMode.HALF_UP);
// 3.333333

price = new BigDecimal("4");
taxCumulative = new BigDecimal("1.2");
net = price.divide(taxCumulative, RoundingMode.HALF_UP);
// 3

因此,结果高度取决于字符串输入的精度,setScale是在除法产生结果后应用的。


你应该包含一个[mcve]。从情况a和情况b来看,价格可能是“4”或“4.00”。 - Mark Rotteveel
如果我初始化BigDecimal(4.00)BigDecimal(1.20),那么我会得到a。如果我初始化BigDecimal("4.00")BigDecimal("1.20"),那么我会得到b - gtgaxiola
请注意,这不是一个问题:“应该期望在setScale之前或之后考虑HALF_UP的应用”。舍入是在除法和应用setScale时应用的。结果四舍五入到3的规则是由于除法规则和“price”的当前比例。请参见https://docs.oracle.com/javase/10/docs/api/java/math/BigDecimal.html#divide(java.math.BigDecimal,java.math.RoundingMode)。 - Mark Rotteveel
1个回答

2
似乎它受到你如何构造BigDecimal的影响。
请查看public BigDecimal(double val)上的构造函数注释。

1.-此构造函数的结果可能有些不可预测。人们可能会认为在Java中编写new BigDecimal(0.1)会创建一个BigDecimal,它完全等于0.1(无标度值为1,比例为1),但实际上它等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1不能精确地表示为double(或者说,不能表示为任何有限长度的二进制分数)。因此,传递给构造函数的值与0.1并不完全相等,尽管表面上看起来是这样。

2.-另一方面,使用String构造函数则完全可预测:编写new BigDecimal("0.1")会创建一个BigDecimal,它完全等于0.1,正如人们所期望的那样。因此,通常建议优先使用String构造函数。

3.-当必须将double用作BigDecimal的源时,请注意,此构造函数提供了精确的转换;它与使用Double.toString(double)方法将double转换为String,然后使用BigDecimal(String)构造函数得到的结果不同。要获得该结果,请使用静态valueOf(double)方法。


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