33

我认为java.math.BigDecimal应该是使用十进制数执行无限精度算术的 The Answer™。

考虑以下代码段:

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?

我希望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

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

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

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

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

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

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

4

9 回答 9

24

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

然后我在小学1,他们告诉我,当你除以 3 时,你会得到 0.33333... 即循环小数。以十进制形式表示的数字除法不准确。事实上,对于任何固定基数,都会有分数(一个整数除以另一个整数)不能精确地表示为该基数中的有限精度浮点数。(这个数字会有一个重复的部分......)

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

BigDecimal javadoc 是这样说的:

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

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

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

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

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

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

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

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


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

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

于 2010-05-01T10:50:27.803 回答
10

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

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

于 2010-05-01T17:09:47.513 回答
6

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

您始终可以操纵您的公式,以便只有一个最终除法。这样您就不会在计算过程中失去精度,并且您将始终获得正确的舍入结果。例如

a/b + c

等于

(a + bc) / b.
于 2010-05-01T11:31:14.957 回答
5

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

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

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

于 2010-05-01T10:19:36.287 回答
3

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

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

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

于 2010-05-01T16:24:27.423 回答
2

有没有必要

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

resulting in

b==1;

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

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

于 2010-05-01T13:01:43.283 回答
2

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

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

于 2012-06-27T17:25:18.683 回答
1

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

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

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

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

用Java表示分数的最佳方法?

于 2010-05-01T09:37:56.563 回答
0

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

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

于 2010-05-01T09:19:12.387 回答