4

我在 scala 中偶然发现了一些有趣的东西(也许只对我来说)。总之,如果我们有一个 BigDecimal(让我们说十进制字符串val a = BigDecimal(someValue)在哪里),则运算结果someValue

N * a / N == a

不会总是产生true。我想它与 BigDecimals 上的任何操作有关。我知道在 scala 中,BigDecimals 是在默认 MathContext 设置为DECIMAL128HALF_EVEN舍入和精度等于 34)的情况下创建的。我在点后超过 30 位的小数上发现了这种行为

我的问题是为什么我会得到这样的结果。我能以某种方式控制它们吗?

例子

-0.007633587786259541984732824427480916

4

1 回答 1

4

正如之前的评论已经指出的那样,无理数是无法避免的。这是因为没有办法使用标准数字类型(如果有的话)来表示无理数。由于我没有无理数的示例(即使 PI 也仅限于固定位数,因此可以表示为 2 个整数的商,使其有理数),我将使用重复小数来说明这个问题。我改成N*a/Na/N*N因为它用整数更好地说明了这个问题,但它们是等价的:

a = BigDecimal(1)
N = BigDecimal(3)
a/N = 0.333...
a/N*N = 0.999...

正如您在上面的示例中看到的,您可以使用尽可能多的小数位和任何舍入模式,但结果永远不会等于 1。(尽管每次操作可以使用不同的舍入模式得到 1,即BigDecimal(3, roundHalfEven) * (BigDecimal(1, roundUp) / 3))

控制数字比较可以做的一件事是在执行算术运算时使用更高的精度,并在比较时四舍五入到所需的(较低)精度:

val HighPrecision = new java.math.MathContext(36, java.math.RoundingMode.HALF_EVEN);
val TargetPrecision = java.math.MathContext.DECIMAL128;

val a = BigDecimal(1, HighPrecision)
val N = BigDecimal(3, HighPrecision)
(a/N*N).round(TargetPrecision) == a.round(TargetPrecision)

在上面的示例中,最后一个表达式的计算结果为true

更新

要回答您的评论,尽管 BigDecimal 是任意精度,但它仍然受到精度的限制。它可以是 34 也可以是 1000000(如果你有足够的内存)。BigDecimal 不知道那1 / 30.33<repeating>. 如果您考虑除法的工作原理,BigDecimal 无法在不执行无限小数位除法的情况下最终知道它正在重复。但由于精度为 2 表示它可以在小数点后 2 位后停止除法,它只知道1 / 30.33

于 2013-10-30T22:55:09.850 回答