BigDecimal的setScale方法完全以我期望的相反方式工作

5

考虑下面的代码:

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

public class RoundingTests {

    public static void main(String[] args) {
        BigDecimal bd1 = new BigDecimal(265.345d);
        BigDecimal bd2 = new BigDecimal(265.335d);

        System.out.println("Setting scale 265.345: " + bd1.setScale(2, RoundingMode.HALF_EVEN));
        System.out.println("Setting scale 265.335: " + bd2.setScale(2, RoundingMode.HALF_EVEN));
    }
}

输出结果为:
Setting scale 265.345: 265.35
Setting scale 265.335: 265.33

现在这正好是我所不希望的。使用RoundingMode.HALF_EVEN(也称为银行家舍入)时,我期望这两个值都变成265.34
请注意,我故意没有使用BigDecimal.round方法,因为它也不能满足我的需求。如果我添加此代码(并导入java.math.MathContext):
System.out.println("Rounding 265.345: " + bd1.round(new MathContext(2, RoundingMode.HALF_EVEN)));
System.out.println("Rounding 265.335: " + bd2.round(new MathContext(2, RoundingMode.HALF_EVEN)));

然后输出结果为:
Rounding 265.345: 2.7E+2
Rounding 265.335: 2.7E+2

这是可以预料的,也在BigDecimal setScale and round中解释过,但这意味着它基本上对我的目的毫无用处。

有人能解释一下setScale的问题吗?

更新:所以这只是另一个浮点数问题,没有简单的方法来修复它,除了从一开始就使用BigDecimals。


4
问题在于浮点数常量。尝试使用带引号的字符串而不是浮点数常量作为构造函数参数。 - user207421
浮点数在表示货币价值时总是很麻烦。 - Jonathan Drapeau
它们可能来自许多地方。在这种情况下,它们确实只是3个数字,因为这是我自己制作的测试用例。它是一个具有精确值的java.lang.Double。但我们正在开发一个通用应用程序开发平台,所以它可能意味着任何东西。我将使用我的实际解决方案更新我的答案。 - Sebastiaan van den Broek
问题在于实际的FP值并非您所认为的那样。转换成和从字符串中读取都无法解决该问题。 - user207421
在这种情况下,EJP你会建议做什么?我看了一下https://dev59.com/-nVC5IYBdhLWcg3w4Vb6,但那里的解决方案也包括字符串转换,或者它们没有考虑到我刚遇到的问题。你在那里也有一个回复,但它只显示了问题。你似乎在说唯一的解决方案是从一开始就使用BigDecimal,对吗? - Sebastiaan van den Broek
显示剩余5条评论
1个回答

1

您可能希望将代码更改为使用BigDecimal.valueOf()而不是new BigDecimal()

该代码

  Double dValue = 265.345d;
  Double dValue2 = 265.335d;
  BigDecimal value = BigDecimal.valueOf(dValue);
  BigDecimal bd1 = new BigDecimal(265.345d);
  BigDecimal bd2 = new BigDecimal(265.335d);
  BigDecimal value2 = BigDecimal.valueOf(dValue2);
  System.out.println("BigDecimal.valueOf(dValue);");
  System.out.println(value.toPlainString());
  System.out.println(String.valueOf(dValue));
  value = value.setScale(2, BigDecimal.ROUND_HALF_EVEN);
  System.out.println(value);
  System.out.println("BigDecimal.valueOf(dValue2);");
  System.out.println(value2.toPlainString());
  System.out.println(String.valueOf(dValue2));
  value2 = value2.setScale(2, BigDecimal.ROUND_HALF_EVEN);
  System.out.println(value2);
  System.out.println("BigDecimal bd1 = new BigDecimal(265.345d);");
  System.out.println(bd1.setScale(2, BigDecimal.ROUND_HALF_EVEN));
  System.out.println("BigDecimal bd2 = new BigDecimal(265.335d);");
  System.out.println(bd2.setScale(2, BigDecimal.ROUND_HALF_EVEN));

输出:

BigDecimal.valueOf(dValue);
265.345
265.345
265.34
BigDecimal.valueOf(dValue2);
265.335
265.335
265.34
BigDecimal bd1 = new BigDecimal(265.345d);
265.35
BigDecimal value2 = BigDecimal.valueOf(dValue2);
265.33

谢谢,这个方法比我的方法更好,而且不需要使用事物的字符串值。然而,实际的实现似乎也使用了双精度值的字符串表示。在 http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html 的 Javadoc 中:valueOf(double val) 将一个双精度转换为 BigDecimal,使用 Double.toString(double) 方法提供的双精度规范字符串表示法。. 所以基本上这也无法解决浮点数的问题。 - Sebastiaan van den Broek
显示的输出不是上面代码的输出。例如,没有“BigDecimal bd2 = new BigDecimal(265.335d);”。 - mrg

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