Java BigDecimal.divide 期间抛出的 ArithmeticException

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2749375/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 12:25:31  来源:igfitidea点击:

ArithmeticException thrown during BigDecimal.divide

javaprecisiondivisionbigdecimalfinancial

提问by polygenelubricants

I thought java.math.BigDecimalis supposed to be The Answer™ to the need of performing infinite precision arithmetic with decimal numbers.

我认为java.math.BigDecimal应该是需要用十进制数执行无限精度算术的答案™。

Consider the following snippet:

考虑以下片段:

import java.math.BigDecimal;
//...

final BigDecimal one = BigDecimal.ONE;
final BigDecimal three = BigDecimal.valueOf(3);
final BigDecimal third = one.divide(three);

assert third.multiply(three).equals(one); // this should pass, right?

I expect the assertto pass, but in fact the execution doesn't even get there: one.divide(three)causes ArithmeticExceptionto be thrown!

我希望assert通过,但实际上执行甚至没有到达那里:one.divide(three)原因ArithmeticException被抛出!

Exception in thread "main" java.lang.ArithmeticException:
Non-terminating decimal expansion; no exact representable decimal result.
    at java.math.BigDecimal.divide

It turns out that this behavior is explicitly documented in the API:

事实证明,这种行为在API 中明确记录:

In the case of divide, the exact quotient could have an infinitely long decimal expansion; for example, 1 divided by 3. If the quotient has a non-terminating decimal expansion and the operation is specified to return an exact result, an ArithmeticExceptionis thrown. Otherwise, the exact result of the division is returned, as done for other operations.

在 的情况下divide,精确商可以有无限长的十进制展开;例如,1 除以 3。如果商有一个非终止的十进制展开并且指定操作返回一个精确的结果,ArithmeticException则抛出an 。否则,将返回除法的确切结果,就像其他操作一样。

Browsing around the API further, one finds that in fact there are various overloads of dividethat performs inexactdivision, i.e.:

进一步浏览API,发现实际上有各种重载divide执行不精确除法,即:

final BigDecimal third = one.divide(three, 33, RoundingMode.DOWN);
System.out.println(three.multiply(third));
// prints "0.999999999999999999999999999999999"

Of course, the obvious question now is "What's the point???". I thought BigDecimalis the solution when we need exactarithmetic, e.g. for financial calculations. If we can't even divideexactly, then how useful can this be? Does it actually serve a general purpose, or is it only useful in a very niche application where you fortunately just don't need to divideat all?

当然,现在显而易见的问题是“有什么意义???”。我认为这BigDecimal是我们需要精确算术时的解决方案,例如用于财务计算。如果我们甚至不能divide完全做到,那么这有多大用处?它实际上服务于通用目的,还是仅在您幸运地根本不需要的非常小众的应用程序中有用divide

If this is not the right answer, what CANwe use for exact division in financial calculation? (I mean, I don't have a finance major, but they still use division, right???).

如果这不是正确的答案,什么CAN我们使用的财务核算准确划分的?(我的意思是,我没有金融专业,但他们仍然使用除法,对吧???)。

采纳答案by Stephen C

If this is not the right answer, what CAN we use for exact division in financial calculation? (I mean, I don't have a finance major, but they still use division, right???).

如果这不是正确的答案,我们可以在财务计算中使用什么进行精确除法?(我的意思是,我没有金融专业,但他们仍然使用除法,对吧???)。

Then I was in primary school1, they taught me that when you divide by 1 by 3 you get a 0.33333... i.e. a recurringdecimal. Division of numbers represented in decimal form is NOT exact. In fact for any fixed basethere will be fractions (the result of dividing one integer by another) that cannot be represented exactlyas a finite precision floating point number in that base. (The number will have a recurring part ...)

然后,我在小学1,他们告诉我,当你除以1由3你得到一个0.33333 ...即反复出现小数。以十进制形式表示的数字的除法不准确。事实上,对于任何固定的基座将存在不能表示级分(将一个整数由另一个的结果)恰好为一个有限精度浮点在于基点数。(该数字将有重复出现的部分...)

When you do financial calculations involving division, you haveto consider the what to do with a recurring fraction. You can round it up, or down, or to the nearest whole number, or something else, but basically you cannotjust forget about the issue.

当您进行涉及除法的财务计算时,您必须考虑如何处理循环分数。您可以将其四舍五入,或向下取整,或取到最接近的整数或其他数字,但基本上您不能忘记这个问题。

The BigDecimal javadoc says this:

BigDecimal javadoc 说:

The BigDecimal class gives its user complete control over rounding behavior. If no rounding mode is specified and the exact result cannot be represented, an exception is thrown; otherwise, calculations can be carried out to a chosen precision and rounding mode by supplying an appropriate MathContext object to the operation.

BigDecimal 类使用户可以完全控制舍入行为。如果没有指定舍入方式且无法表示准确结果,则抛出异常;否则,可以通过向操作提供适当的 MathContext 对象,以选定的精度和舍入模式执行计算。

In other words, it is your responsibilityto tell BigDecimal what to do about rounding.

换句话说,您有责任告诉 BigDecimal 如何处理舍入。

EDIT- in response to these followups from the OP.

编辑- 响应 OP 的这些后续行动。

How does BigDecimal detect infinite recurring decimal?

BigDecimal 如何检测无限循环小数?

It does not explicitly detect the recurring decimal. It simply detects that the result of some operation cannot be represented exactly using the specified precision; e.g. too many digits are required after the decimal point for an exact representation.

它没有明确检测循环小数。它只是检测无法使用指定的精度准确表示某些操作的结果;例如,为了精确表示,小数点后需要太多数字。

It must keep track of and detect a cycle in the dividend. It COULD HAVE chosen to handle this another way, by marking where the recurring portion is, etc.

它必须跟踪并检测红利中的一个周期。它可以选择以另一种方式处理这个问题,通过标记重复部分的位置等。

I suppose that BigDecimalcould have been specified to represent a recurring decimal exactly; i.e. as a BigRationalclass. However, this would make the implementation more complicated and more expensive to use2. And since most people expect numbers to be displayed in decimal, and the problem of recurring decimal recurs at that point.

我想这BigDecimal可以被指定为精确地表示一个循环小数;即作为一个BigRational类。但是,这会使实现更复杂,使用2也更昂贵。并且由于大多数人希望数字以十进制显示,因此重复出现小数的问题在这一点上再次出现。

The bottom line is that this extra complexity and runtime cost would be inappropriate for typical use-cases for BigDecimal. This includes financial calculations, where accounting conventions do not allow you to use recurring decimals.

最重要的是,这种额外的复杂性和运行时成本对于BigDecimal. 这包括财务计算,其中会计惯例不允许您使用循环小数。



1 - It was an excellent primary school ...

1 - 这是一所优秀的小学......

2 - Either you try to remove common factors of the divisor and dividend (computationally expensive), or allow them to grow without bounds (expensive in space usage ... and computationally for later operations).

2 - 您要么尝试删除除数和被除数的公因数(计算成本高),要么允许它们无限增长(空间使用成本高......并且为以后的操作进行计算)。

回答by Martijn Courteaux

Notice we are using a computer... A computer has a lot of ram and precision takes ram. So when you want an infinite precision you need
(infinite * infinite) ^ (infinite * Integer.MAX_VALUE)terrabyte ram...

请注意,我们正在使用计算机......计算机有很多内存,而精度需要内存。因此,当您想要无限精度时,您需要
(infinite * infinite) ^ (infinite * Integer.MAX_VALUE)太字节内存...

I know 1 / 3is 0.333333...and it should be possible to store it in ram like "one divided by three" and then you can multiply it back and you should have 1. But I don't think Java has something like that...
Maybe you have to win the Nobel Price for writing something doing that. ;-)

我知道1 / 30.333333...并且应该可以将它存储在 ram 中,就像“一除以三”一样,然后你可以将它乘回去,你应该有1. 但我不认为 Java 有这样的东西......
也许你必须因为写一些这样的东西而赢得诺贝尔奖。;-)

回答by Adrian Mouat

I accept that Java doesn't have great support for representing fractions, but you have to realise that it is impossibleto keep things entirely precise when working with computers. At least in this case, the exception is telling you that precision is being lost.

我承认 Java 对表示分数没有很好的支持,但是您必须意识到在使用计算机时不可能保持完全精确。至少在这种情况下,例外是告诉您精度正在丢失。

As far as I know, "infinite precision arithmetic with decimal numbers" just isn't going to happen. If you have to work with decimals, what you're doing is probably fine, just catch the exceptions. Otherwise, a quick google search finds some interesting resources for working with fractions in Java:

据我所知,“十进制数的无限精度算术”是不会发生的。如果您必须使用小数,那么您所做的可能很好,只需捕获异常即可。否则,快速谷歌搜索会发现一些有趣的资源,用于在 Java 中使用分数:

http://commons.apache.org/math/userguide/fraction.html

http://commons.apache.org/math/userguide/fraction.html

http://www.merriampark.com/fractions.htm

http://www.merriampark.com/fractions.htm

Best way to represent a fraction in Java?

在 Java 中表示分数的最佳方式?

回答by stacker

By the way, I'd really appreciate insight from people who've worked with financial software. I often heard BigDecimal being advocated over double

顺便说一句,我真的很感激那些使用过财务软件的人的见解。我经常听到 BigDecimal 被提倡使用 double

In financial reports we use alwasy BigDecimal with scale = 2 and ROUND_HALF_UP, since all printed values in a report must be lead to a reproducable result. If someone checks this using a simple calculator.

在财务报告中,我们总是使用 BigDecimal 与 scale = 2 和ROUND_HALF_UP,因为报告中的所有打印值都必须导致可重现的结果。如果有人使用一个简单的计算器来检查这个。

In switzerland they round to 0.05 since they no longer have 1 or 2 Rappencoins.

在瑞士,它们四舍五入为 0.05,因为它们不再有 1 或 2 个Rappen硬币。

回答by COME FROM

If you want to work with decimals, not rational numbers, and you need exact arithmetics before the final rounding (rounding to cents or something), here's a little trick.

如果您想使用小数而不是有理数,并且您需要在最终舍入(舍入到美分或其他)之前进行精确算术,这里有一个小技巧。

You can always manipulate your formulas so that there's only one final division. That way you won't lose precision during calculations and you'll always get the correctly rounded result. For instance

你总是可以操纵你的公式,这样就只有一个最后的除法。这样你就不会在计算过程中失去精度,而且你总是会得到正确的舍入结果。例如

a/b + c

equals

等于

(a + bc) / b.

回答by Michael Konietzka

Is there a need for

有没有必要

a=1/3;
b=a*3;

resulting in

b==1;

in financial systems? I guess not. In financial systems it is defined, which roundmode and scale has to be used, when doing calculations. In some situations, the roundmode and scale is defined in the law. All components can rely on such a defined behaviour. Returning b==1 would be a failure, because it would not fulfill the specified behaviour. This is very important when calculating prices etc.

在金融系统?我猜不是。在金融系统中,它定义了在进行计算时必须使用的圆形模式和比例。在某些情况下,法律中定义了圆形模式和比例。所有组件都可以依赖于这种定义的行为。返回 b==1 将失败,因为它不会满足指定的行为。这在计算价格等时非常重要。

It is like the IEEE 754 specifications for representing floats in binary digits. A component must not optimize a "better" representation without loss of information, because this will break the contract.

它类似于 IEEE 754 规范,用于以二进制数字表示浮点数。组件不能在不丢失信息的情况下优化“更好”的表示,因为这会破坏合同。

回答by tkr

You should prefer BigDecimal for finance calculations. Rounding should be specified by the business. E.g. an amount (100,00$) has to be split equally across three accounts. There has to be a business rule which account takes the extra cent.

您应该更喜欢 BigDecimal 进行财务计算。四舍五入应由企业指定。例如,金额 (100,00$) 必须在三个帐户中平均分配。必须有一个业务规则,其中帐户需要额外的一分钱。

Double, floats are not approriate for use in financial applications because they can not represent fractions of 1 precisely that are not exponentials of 2. E.g. consider 0.6 = 6/10 = 1*1/2 + 0*1/4 + 0*1/8 + 1*1/16 + ... = 0.1001...b

双精度浮点数不适用于金融应用程序,因为它们不能精确表示 1 的分数,而不是 2 的指数。例如,考虑 0.6 = 6/10 = 1*1/2 + 0*1/4 + 0*1 /8 + 1*1/16 + ... = 0.1001...b

For mathematic calculations you can use a symbolic number, e.g. storing denominator and numerator or even a whole expression (e.g. this number is sqrt(5)+3/4). As this is not the main use case of the java api you won' find it there.

对于数学计算,您可以使用符号数,例如存储分母和分子,甚至是整个表达式(例如,此数字是 sqrt(5)+3/4)。由于这不是 java api 的主要用例,因此您不会在那里找到它。

回答by Kevin Brock

The class is BigDecimalnot BigFractional. From some of your comments it sounds like you just want to complain that someone didn't build in all possible number handling algorithms into this class. Financial apps do not need infinite decimal precision; just perfectly accurate values to the precision required (typically 0, 2, 4, or 5 decimal digits).

班级BigDecimal不是BigFractional。从您的一些评论来看,您似乎只是想抱怨有人没有将所有可能的数字处理算法内置到此类中。金融应用不需要无限小数精度;只是达到所需精度的完全准确值(通常为 0、2、4 或 5 个十进制数字)。

Actually I have dealt with many financial applications that use double. I don't like it but that was the way they are written (not in Java either). When there are exchange rates and unit conversions then there are both the potential of rounding and bruising problems. BigDecimaleliminates the later but there is still the former for division.

实际上,我处理过许多使用double. 我不喜欢它,但这就是它们的编写方式(也不是用 Java 编写的)。当存在汇率和单位转换时,就有可能出现四舍五入和模糊问题。BigDecimal消除了后者,但仍有前者进行除法。

回答by Raymond Cidad

To divide save, you have to set the MATHcontext,

要分割保存,您必须设置MATHcontext,

BigDecimal bd = new BigDecimal(12.12, MathContext.DECIMAL32).divide(new BigDecimal(2)).setScale(2, RoundingMode.HALF_UP);

BigDecimal bd = new BigDecimal(12.12, MathContext.DECIMAL32).divide(new BigDecimal(2)).setScale(2, RoundingMode.HALF_UP);