正如先前的评论已经指出的,这是无法避免与无理数。这是因为无法使用标准数字类型来表示无理数(如果有的话)。因为我没有无理数的例子(即使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
。
UPDATE
为了回答您的评论,虽然BigDecimal为任意精度,它仍然是一个精度的限制。它可以是34,也可以是1000000(如果你有足够的内存)。 BigDecimal不知道1/3
是0.33<repeating>
。如果您考虑分部是如何工作的,那么BigDecimal就没有办法确切地知道它是重复的,而没有将分部划分到无限小数位。但由于精度为2表示可以在小数点后2位停止分割,因此只知道1/3
为0.33
。
你有个例子吗? –
会使用有理数字有帮助吗? –
就像@ 0__所说的,如果你想要精确的精确度,你需要使用有理数。如果使用不精确的精度,在某些情况下总会遇到舍入误差。 [Spire](https://github.com/non/spire)为Scala中的理性数字数学提供支持。 – DaoWen