正如之前的评论已经指出的那样,无理数是无法避免的。这是因为没有办法使用标准数字类型(如果有的话)来表示无理数。由于我没有无理数的示例(即使 PI 也仅限于固定位数,因此可以表示为 2 个整数的商,使其有理数),我将使用重复小数来说明这个问题。我改成N*a/N
是a/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 / 3
是0.33<repeating>
. 如果您考虑除法的工作原理,BigDecimal 无法在不执行无限小数位除法的情况下最终知道它正在重复。但由于精度为 2 表示它可以在小数点后 2 位后停止除法,它只知道1 / 3
是0.33
。