如何使用BigDecimal.ROUND_HALF_UP将0.745四舍五入为0.75?

56

我尝试了以下内容:

   double doubleVal = 1.745;
   double doubleVal1 = 0.745;
   BigDecimal bdTest = new BigDecimal(  doubleVal);
   BigDecimal bdTest1 = new BigDecimal(  doubleVal1 );
   bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
   bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
   System.out.println("bdTest:"+bdTest); //1.75
   System.out.println("bdTest1:"+bdTest1);//0.74    problemmmm ????????????  

但是我得到了奇怪的结果。为什么?


感谢您提到.setScale作为比神秘难懂的.round(MatchContext...)更好的四舍五入方法。 - MarkHu
6个回答

112

不要使用浮点数或双精度数构造BigDecimal。使用整数或字符串构造它们。浮点数和双精度数会失去精度。

此代码按预期工作(我只是将类型从double更改为String):

public static void main(String[] args) {
  String doubleVal = "1.745";
  String doubleVal1 = "0.745";
  BigDecimal bdTest = new BigDecimal(  doubleVal);
  BigDecimal bdTest1 = new BigDecimal(  doubleVal1 );
  bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
  bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
  System.out.println("bdTest:"+bdTest); //1.75
  System.out.println("bdTest1:"+bdTest1);//0.75, no problem
}

12
如果你使用 BigDecimal.valueOf(double),这也可以正常工作,而不必将其转换为字符串。这是一个静态工厂方法,相较于构造函数更受推荐(在 javadocs 中有注明)。使用 BigDecimal bdTest = BigDecimal.valueOf(1.745); BigDecimal bdTest1 = BigDecimal.valueOf(0.745); 会得到相同的结果(0.75)。 - Joshua Goldberg
@JoshuaGoldberg,你是不是想说 BigDecimal.valueOf("0.745"); 而不是 BigDecimal.valueOf(1.745); ?但是可能不是这样,因为没有 BigDecimal.valueOf(String) 这个方法 :-/ - Julien
是的,注释的关键在于缺少引号。从BigDec.valueOf(double)的javadoc中可以看到:“注意:这通常是将double(或float)转换为BigDecimal的首选方法,因为返回的值等于使用Double.toString(double)的结果构造BigDecimal的结果。” - Joshua Goldberg
如果我的BigDecimal是使用Spring的@ConfigurationProperties导入的,那么该怎么办?我无法控制它从application.yml中的构造方式,这个文件应该已经提供了一个作为String类型的值,但仍然存在相同的舍入问题。 - Sidney de Moraes
3
BigDecimal.valueOf(double)不是构建BigDecimal的首选方法,而是将double转换为BigDecimal的首选方法。字符串构造函数是构建BigDecimal的首选方法。根据文档:“通常建议优先使用字符串构造函数而不是[双精度构造函数]”。 - Sean
1
valueOf(double)new BigDecimal(String)有类似的注释,表明它们是“转换浮点数或双精度数的首选方式...”我的理解是,鉴于两个构造函数都提到了“new BigDecimal(double)”的不可预测性,因此前两个方法都可以使用,但是一般情况下应避免使用后者(尽管有些令人困惑的“the”)。 - Joshua Goldberg

13
double doubleVal = 1.745;
double doubleVal1 = 0.745;
System.out.println(new BigDecimal(doubleVal));
System.out.println(new BigDecimal(doubleVal1));

输出:

1.74500000000000010658141036401502788066864013671875
0.74499999999999999555910790149937383830547332763671875

该示例展示了两个双精度浮点数的实际值,并解释了您得到的结果。正如其他人所指出的那样,不要使用双精度构造函数(除非您想查看双精度实际值的特定情况)。

有关双精度的更多信息:


10

使用BigDecimal.valueOf(double d)代替new BigDecimal(double d),后者由于float和double精度问题会产生误差。


3
这可能会让你知道出了什么问题。
import java.math.BigDecimal;

public class Main {
    public static void main(String[] args) {
        BigDecimal bdTest = new BigDecimal(0.745);
        BigDecimal bdTest1 = new BigDecimal("0.745");
        bdTest = bdTest.setScale(2, BigDecimal.ROUND_HALF_UP);
        bdTest1 = bdTest1.setScale(2, BigDecimal.ROUND_HALF_UP);
        System.out.println("bdTest:" + bdTest); // prints "bdTest:0.74"
        System.out.println("bdTest1:" + bdTest1); // prints "bdTest:0.75"
    }
}

问题在于,您的输入(double x=0.745;)无法准确表示0.745。实际上,它保存了一个稍微低一点的值。对于BigDecimals来说,这已经低于0.745,因此它会向下舍入...请尽量不要使用BigDecimal(double/float)构造函数。

2

如果您有兴趣,可以使用 double 来执行相同的操作。

double doubleVal = 1.745;
double doubleVal2 = 0.745;
doubleVal = Math.round(doubleVal * 100 + 0.005) / 100.0;
doubleVal2 = Math.round(doubleVal2 * 100 + 0.005) / 100.0;
System.out.println("bdTest: " + doubleVal); //1.75
System.out.println("bdTest1: " + doubleVal2);//0.75

或者只是
double doubleVal = 1.745;
double doubleVal2 = 0.745;
System.out.printf("bdTest: %.2f%n",  doubleVal);
System.out.printf("bdTest1: %.2f%n",  doubleVal2);

两者都可以打印

bdTest: 1.75
bdTest1: 0.75

我喜欢尽可能保持代码简单。;)

正如@mshutov所指出的那样,您需要添加一些内容以确保半值始终向上舍入。这是因为像265.335这样的数字比它们看起来要小一点。


2
你可能会得到意外的结果。例如:Math.round(265.335 * 100) / 100.0 != 265.34,请参考https://dev59.com/-nVC5IYBdhLWcg3w4Vb6#153753中的评论。 - mshutov

0

有多种选项可供选择,例如:

 Double d= 123.12;
BigDecimal b = new BigDecimal(d, MathContext.DECIMAL64); // b = 123.1200000
b = b.setScale(2, BigDecimal.ROUND_HALF_UP);  // b = 123.12

BigDecimal b1 =new BigDecimal(collectionFileData.getAmount(), MathContext.DECIMAL64).setScale(2, BigDecimal.ROUND_HALF_UP)  // b1= 123.12

 d = (double) Math.round(d * 100) / 100;
BigDecimal b2 = new BigDecimal(d.toString());  // b2= 123.12




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