BigDecimal 从 Java 方法返回时不保留实际值

BigDecimal not keeping actual value when being returned from Java method

提问人:Bryan Douglas 提问时间:10/31/2018 最后编辑:Steve Hansen SmytheBryan Douglas 更新时间:11/2/2018 访问量:3837

问:

我正在用 Java 制作一个货币转换应用程序。其他一些很棒的 StackOverflowians 给了我一些建议,让我阅读 BigDecimal 以替换 double 以解决任何精度问题。

我有一个双方法系统;它从起始货币转换为美元,然后将美元价值转换为目标货币。

请注意,我的转化率是这样存储的:

// Conversion Rates - START (as of October 30, 2018 @ 3:19 AM)
// Rates obtained from exchange-rates.org

//Convert to United States Dollar rates
private final BigDecimal CAD_TO_USD = new BigDecimal(0.76135);
private final BigDecimal EUR_TO_USD = new BigDecimal(1.1345);
private final BigDecimal YEN_TO_USD = new BigDecimal(0.008853);
// Conversion Rates - END

在我用各自的大小数替换了我的双打之后,我决定测试一下,看看它是如何工作的。

我的测试器类运行以下方法来启动转换过程。

public BigDecimal convert()
{
    BigDecimal value;

    value = convertToUSD(); //Converts the current currency into USD 
    value = convertFromUSD(value);  //Converts the previous USD currency value into the destination currency

    return value;
}

当我输入我的示例变量(将 2.78 日元转换为加元)时,我逐步完成了该过程,发现在我返回一个值之前,一切都在运行。

从前面提到的方法中,运行并编码如下convertToUSD()

private BigDecimal convertToUSD()
{
    switch (fromCurrency)
    {
        case "USD":
            return fromQuantity.multiply(new BigDecimal(1));

        case "CAD":
            return fromQuantity.multiply(CAD_TO_USD);

        case "EUR":
            return fromQuantity.multiply(EUR_TO_USD);

        case "YEN":
            return fromQuantity.multiply(YEN_TO_USD);
    }

    return new BigDecimal(0);
}

所有值都正确传入,它会逐步执行正确的大小写(“YEN”),变量窗格显示“fromQuantity”BigDecimal 的 intCompact 值为 278(这对我来说很有意义)

enter image description here

一旦断点返回到“convert”方法,它就会被搞砸。它不是返回 ,而是返回 。2.78 * 0.008853 = 0.0246-9223372036854775808

enter image description here

这会导致所有其他计算产生和错误。

我是使用 BigDecimal 的新手,所以我可能犯了一个完全明显的错误;但我很高兴学习,所以我向你们寻求建议:)

任何帮助都是值得赞赏的。

java 精度 bigdecimal

评论

5赞 Nathan Hughes 10/31/2018
不要使用需要双精度值的构造函数。使用接受 String 的那个。double 值不会像您想象的那样映射到 BigDecimal。不确定这是你在这里唯一的问题,但它会让你有一个糟糕的开始。
0赞 Rudy Velthuis 10/31/2018
-9223372036854775808 的 intCompact 值为 Long::MIN_VALUE。如果它具有该值,则意味着它不应用于计算 BigDecimal 值,而 BigInteger intVal 包含 BigDecimal 的实际数字。因此,尽管您使用双精度值来初始化 BigDecimal,但它们的值可能不准确,但不应与事实相差太远。无论如何,intCompact 中的值在这里没有任何意义。该值只是一个忽略 intCompact 的标志。您可以使用 检查 BigDecimal 的实际值。System.out.println(value);

答:

8赞 Basil Bourque 10/31/2018 #1

tl;博士

使用 ,而不是文字。Stringdouble

new BigDecimal( "2.78" )           // Pass "2.78" not 2.78
.multiply(
    new BigDecimal( "0.008853" )   // Pass "0.008853" not 0.008853
)
.toString()

0.02461134

不要传递浮点类型

BigDecimal 类的意义在于避免浮点技术中固有的不准确性。浮点类型,例如 / 和 / 以牺牲精度换取执行速度。相比之下,速度慢但准确。floatFloatdoubleDoubleBigDecimal

您的代码:

new BigDecimal( 0.76135 )
new BigDecimal( 1.1345 )
new BigDecimal( 0.008853 )

...正在传递一个原始文字。在编译过程中,键入的文本将解析为数字,特别是(64 位浮点值)。在这一点上,您引入了此类型固有的不准确之处。换言之,产生的可能不再完全是 。double0.76135doubledouble0.761350.76135

让我们在实例化后立即转储您的实例。BigDecimal

System.out.println( new BigDecimal( 0.76135 ) );    // Passing a `double` primitive.
System.out.println( new BigDecimal( 1.1345 ) );
System.out.println( new BigDecimal( 0.008853 ) );

0.7613499999999999712230192017159424722194671630859375

1.13450000000000006394884621840901672840118408203125

0.0088529999999999997584154698415659368038177490234375

因此,通过创建数字值,您调用了浮点技术,并引入了不准确之处。double

使用字符串

解决方案是什么?使用字符串,完全避免使用类型。double

在这些输入周围加上一些双引号

System.out.println( new BigDecimal( "0.76135" ) );  // Passing a `String` object.
System.out.println( new BigDecimal( "1.1345" ) );
System.out.println( new BigDecimal( "0.008853" ) );

0.76135

1.1345

0.008853

你期望.让我们试试吧。2.78 * 0.008853 = 0.0246

BigDecimal x = new BigDecimal( "2.78" );
BigDecimal y = new BigDecimal( "0.008853" );
BigDecimal z = x.multiply( y );
System.out.println( x + " * " + y + " = " + z );

2.78 * 0.008853 = 0.02461134

接下来,你应该研究四舍五入和截断。在 Stack Overflow 上已经多次介绍过。BigDecimal

评论

0赞 Louis Wasserman 10/31/2018
这准确地解释了为什么代码是错误的,但也应该解释如何修复它。
0赞 Peter O. 10/31/2018
重点不在于它和不准确,而在于后两者使用二进制算术,而使用十进制算术,这比二进制算术更有意义。通常,将二进制算术应用于旨在作为小数的数字有时会导致不直观的结果,如构造函数所示。BigDecimalfloatdoubleBigDecimalBigDecimal(double)
0赞 Basil Bourque 10/31/2018
@PeterO。“不直观的结果”=“不准确”。虽然我在学术意义上理解你的观点,但归根结底,我的客户只关心发票是否正确合计、薪水是否正确以及分类账是否平衡。
0赞 Peter O. 10/31/2018
@Basil : 这就是我的观点:二进制和十进制算术不应该混为一谈。
2赞 Dan N 10/31/2018 #2

问题在于,您假设表示浮点数的非浮点版本。在某些情况下,它确实如此,但在大多数情况下,它不会。intCompact

例如,我试图将您的问题减少到最低限度。

import java.math.BigDecimal;

class Main {
  public static void main(String[] args) {
    final BigDecimal YEN_TO_USD = new BigDecimal(0.008853);
    BigDecimal value = new BigDecimal(2.78);

    value = value.multiply(YEN_TO_USD);
    System.out.println(value);
  }
}

在行上放置一个中断,我得到以下结果:println

break point

你看到的和你的一样()。但在这种情况下,是预期值。intCompact-9223372036854775808stringCache

请参阅 Basil 的答案,了解为什么您应该使用字符串而不是双精度来构造 BigDecimal。