BigDecimal - 使用new还是valueOf?

138

我发现有两种方法可以将double d转换为BigDecimal对象。

  1. new BigDecimal(d)
  2. BigDecimal.valueOf(d)

哪一种方法更好?valueOf会创建一个新对象吗?

在一般情况下(不仅仅是BigDecimal),是建议使用new还是valueOf?


10
通常情况下,优先使用valueOf方法(因为它可以通过重用“热门”的实例来避免创建新对象)。但是在处理BigDecimal和double时,不幸的是这两种方法会产生不同的结果,因此您需要选择所需的方法。 - Thilo
5个回答

208
这是两个不同的问题:“我应该使用什么来处理BigDecimal?”和“一般情况下我该怎么做?”
对于BigDecimal:这有点棘手,因为它们没有做相同的事情BigDecimal.valueOf(double)将使用传入的double值的规范化String表示来实例化BigDecimal对象。换句话说:当您执行System.out.println(d)时,BigDecimal对象的值将是您看到的值。
然而,如果您使用new BigDecimal(d),那么BigDecimal将尝试尽可能准确地表示double值。这通常会导致存储比您想要的更多的数字。严格来说,它比valueOf()更正确,但它不太直观。
在JavaDoc中有一个很好的解释:
这个构造函数的结果可能是不可预测的。有人可能认为在Java中写new BigDecimal(0.1)会创建一个完全等于0.1的BigDecimal(1的无标度值,比例为1),但实际上它等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1不能被精确地表示为一个double(或者说任何有限长度的二进制小数)。因此,传递给构造函数的值并不完全等于0.1,尽管表面上看起来是这样的。
通常情况下,如果结果相同(即不是在BigDecimal的情况下,而是在大多数其他情况下),则应该优先使用valueOf():它可以对常见的值进行缓存(如Integer.valueOf()所示),甚至可以在不必更改调用者的情况下更改缓存行为。new将始终实例化一个新值,即使不需要(最好的例子: new Boolean(true) vs. Boolean.valueOf(true))。

3
@Joachim,这不够清楚。new BigDecimal()BigDecimal.valueOf()更好吗? - ryvantage
7
如果一个东西明显比另一个更好,那就不需要两者共存了,我的回答也会变得简短很多。它们的作用不同,所以不能像那样对它们进行排序。 - Joachim Sauer
3
@JoachimSauer,好的,抱歉我应该更具体一些。您介意举个例子说明何时应优先使用 new BigDecimal() 和何时应优先使用 BigDecimal.valueOf() 吗? - ryvantage
2
@BrianKnoblauch:具有讽刺意味的是,“新”的实际上更接近于输入的双精度值,而“toString”输出则通过截断正确但不必要区分一个双精度值与另一个双精度值的数字来“作弊”。 - Joachim Sauer
1
@BeeingJk:是的,我在这个回答中详细解释了为什么会出现这种情况以及如何解决它。 - Joachim Sauer
显示剩余6条评论

61

如果您正在使用BigDecimal对象来存储货币值,那么我强烈建议您在计算中不要涉及任何double值。

如另一个答案所述,double值存在已知的精度问题,而这些问题将会对您造成严重影响。

一旦您解决了这个问题,回答您的问题就很简单了。始终使用带有String值参数的构造方法,因为String没有valueOf方法。

如果您想要证明,请尝试以下操作:

BigDecimal bd1 = new BigDecimal(0.01);
BigDecimal bd2 = new BigDecimal("0.01");
System.out.println("bd1 = " + bd1);
System.out.println("bd2 = " + bd2);

你将获得以下输出:

bd1 = 0.01000000000000000020816681711721685132943093776702880859375
bd2 = 0.01

请参考这个相关问题


完全同意,最佳实践。 - jumping_monkey
println BigDecimal.valueOf(0.01) 的结果是 0.01 - undefined

8

基本上,valueOf(double val) 只是这样做:

return new BigDecimal(Double.toString(val));

因此-> 是的,将创建一个新对象 :).

一般来说,我认为它取决于你的编码风格。如果两者结果相同,我不会混合使用 valueOf 和 "new"。


9
严格来说是正确的,但这将产生巨大的影响。valueOf() 具有更直观的行为方式,而 new BigDecimal(d) 则更为正确。请尝试两者并比较差异。 - Joachim Sauer
从技术上讲是错误的。'new' 关键字总是创建一个新对象,而 javadoc 并没有说明 valueOf 是否总是返回一个新对象。它并不总是这样。它在缓存中有一些值,因此 new BigDecimal(1) != new BigDecimal(1),但是 BigDecimal.valueOf(1) == BigDecimal.valueOf(1) - aalku
2
@user:是的,但由于BigDecimal是不可变的,因此应该像原始包装器(IntegerByte,...)和String一样对待它:对象标识不应该对您的代码产生影响,只有才应该受到关注。 - Joachim Sauer
@Joachim 对的,但是那个内部缓存是有原因的。拥有太多不必要的BigDecimal实例并不是一件好事。我是在回答Dr的问题,他说“将创建一个新对象”。 - aalku
4
是的,这就是我说valueOf() 通常 应该被优先选择的原因。但请注意,BigDecimal.valueOf(double)不会进行任何缓存(而且也可能不值得这样做)。 - Joachim Sauer
@Joachim 是的,它是针对 valueOf(long) 的。正如源代码中所评论的那样,计划将其用于 double,但您是正确的。 - aalku

2
请使用:
new BigDecimal(String val) or BigDecimal.valueOf(double val);

请勿使用:

new BigDecimal(double val);

1

前言

为什么我们要讨论浮点类型、数字和算术?很简单,因为我们是按照十进制计数的,但机器是按照二进制计数的。

BigDecimal - 需要精确表示(而非近似)

如果您正在使用 BigDecimal,这意味着您需要精确表示 0.1 和其他负十次幂(通常涉及货币或小数运算)。

Double 会带来麻烦(涉及 BigDecimal)

如果你发现自己需要使用BigDecimal来操作double(或float)类型的值,那么你就会遇到麻烦,因为在二进制中无法准确表示0.1。机器将双精度浮点数(IEEE-754浮点算术标准)存储为二进制。这里有一篇很好的文章,如果你感兴趣可以了解一下。Duncan's answer阐述了我想说的做法和不做法。

任何你认为可以准确存储0.1的编程语言实际上都是近似值。

System.out.println(0.1d);
//Prints 0.1 or so you think ;-)

//If you are not convinced, try this:
double x = 1.1; double y = 1.0;
if (x-y == 0.1) {// print true } else {// print false}

//or perhaps this:
double amount1 = 2.15;
double amount2 = 1.10;
System.out.println("Difference: " + (amount1 - amount2));

示例

  double smallD = 0.0001;
  double smallDNoScientificNotation = 0.001; //>= 10E-3
  double normalD = 10.345678;
  double bigDNoScientificNotation = 1234567.123456789; //<=10E7 
  double bigD = 56_789_123_456_789.123456789;

  //double
  System.out.println(smallD); //1.0E-4, computerized scientific notation, this is how Double toString works
  System.out.println(smallDNoScientificNotation); //0.001, OK
  System.out.println(normalD); //10.345678, OK
  System.out.println(bigDNoScientificNotation); //1234567.123456789, OK
  System.out.println(bigD); //5.6789123456789125E13, computerized scientific notation, this is how Double toString works
  
  //new BigDecimal(double): not OK, don't use! Attempting to representing the base-2 representation as accurately as possible
  System.out.println(new BigDecimal(smallD)); //0.000100000000000000004792173602385929598312941379845142364501953125
  System.out.println(new BigDecimal(smallDNoScientificNotation)); //0.001000000000000000020816681711721685132943093776702880859375
  System.out.println(new BigDecimal(normalD)); //10.34567799999999948568074614740908145904541015625
  System.out.println(new BigDecimal(bigDNoScientificNotation)); //1234567.12345678894780576229095458984375
  System.out.println(new BigDecimal(bigD)); //56789123456789.125
  
  //BigDecimal.valueOf (Dont use if the range is >= 10E-3, >= 10E7), under the hood it's using Double.toString
  System.out.println(BigDecimal.valueOf(smallD)); //0.00010 - notice the extra 0, stemming from 1.0E-4
  System.out.println(BigDecimal.valueOf(smallDNoScientificNotation)); //0.001
  System.out.println(BigDecimal.valueOf(normalD)); //10.345678
  System.out.println(BigDecimal.valueOf(bigDNoScientificNotation)); //1234567.123456789
  System.out.println(BigDecimal.valueOf(bigD)); //56789123456789.125 //loss of accuracy 

计算机科学计数法 - 更多信息请点击此处

奖励1 - 注意事项

请点击此处

奖励2 - 《Effective Java 第三版》(Joshua Bloch)

条款60:如果需要精确答案,请避免使用float或double

浮点数和双精度类型不适合用于货币计算,因为无法准确地表示0.1(或任何其他负十次幂)作为浮点数或双精度数。
:
然而,使用BigDecimal有两个缺点:它比使用原始算术类型要麻烦得多,而且速度要慢得多。如果您正在解决单个简短的问题,则后者的缺点无关紧要,但前者可能会让您感到烦恼。
:
使用int或long作为BigDecimal的替代方案,具体取决于涉及的金额,并自行跟踪小数点。在这个例子中,显而易见的方法是以分为单位进行所有计算。

数学倾向的额外阅读 ;-)

计算机科学家应该了解的浮点运算知识


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