使用Scala BigDecimal乘法和除法的倒数

时间:2016-12-07 21:26:40

标签: java scala

我们遇到的问题是,我们用另一个大十字来划分一个大十字,并试图通过取除数的倒数并乘以红利来验证它。

例如,基本数学会告诉我们:a / b = a * (1 / b)

更具体地说,以下是我们要做的事情:6.0075 / 0.89 = 6.0075 * (1 / 0.89) = 6.75

使用BigDecimal增加精度,我们可以得到第一部分:

scala> BigDecimal(6.0075) / BigDecimal(0.89)
res1: scala.math.BigDecimal = 6.75

但不是第二个:

scala> BigDecimal(6.0075) * (BigDecimal(1.00) / BigDecimal(0.89))
res2: scala.math.BigDecimal = 6.749999999999999999999999999999999

进一步深入研究,我们发现BigDecimal(1.00) / BigDecimal(0.89)是一个不精确的结果,Scala的BigDecimal包装器围绕Java的math.BigDecimal指定了一个35位小数的精度,RoundingMode为HALF_EVEN。不幸的是,1.00 / 0.89具有无限多个小数位,因此直接使用java的BigDecimal也无济于事。

还有其他人遇到过这个问题吗?除了尝试重写表达式1.00/0.89之外,Scala或Java中是否有办法处理此问题?

谢谢!

3 个答案:

答案 0 :(得分:3)

不幸的是,没有100%准确的方法来表示具有无限十进制扩展的数字,如1.00/0.89(或1/3),具有浮点数。这是有限机器试图模拟无限属性的限制。

但是,如果您只关心一定数量的数字(如果使用货币,则为2),那么您可以使用Java的MathContextsetScale方法:

scala> import java.math.{RoundingMode => RM, MathContext => MC}
import java.math.{RoundingMode=>RM, MathContext=>MC}

scala> val mc = new MC(100, RM.HALF_UP)
mc: java.math.MathContext = precision=100 roundingMode=HALF_UP

scala> val a = BigDecimal("6.0075")
a: scala.math.BigDecimal = 6.0075

scala> val b = BigDecimal("1")
b: scala.math.BigDecimal = 1

scala> val c = BigDecimal("0.89")
c: scala.math.BigDecimal = 0.89

scala> val d = (a * (b(mc) / c)).setScale(2)
d: scala.math.BigDecimal = 6.75

请注意,我使用的是双打的字符串版本,因为它们是更精确的表示(因为即使0.89也不能完美地用浮点数表示)。

修改

受到@jwvh关于Rational Numbers的评论的启发,我把这个基本的Rational课程放在了一起:

scala> :pa
// Entering paste mode (ctrl-D to finish)

object Rational
{
    def apply(n: BigInt, d: BigInt): Rational =
    {
        val neg_mod = if (d < BigInt(0)) BigInt(-1) else BigInt(1)
        val (n_mod, d_mod) = (neg_mod * n, neg_mod * d)
        val gcd_val = gcd(n_mod, d_mod)
        new Rational(n_mod / gcd_val, d_mod / gcd_val)
    }
    def gcd(a: BigInt, b: BigInt): BigInt = if (b == BigInt(0)) a else gcd(b, a % b)
}
class Rational(val n: BigInt, val d: BigInt)
{
    override def toString: String = if (n == BigInt(0)) "0" else if (d == BigInt(1)) s"$n" else s"$n/$d"

    def toDouble: Double = n.toDouble / d.toDouble

    def *(that: Rational): Rational = Rational(n * that.n, d * that.d)

    def /(that: Rational): Rational = Rational(n * that.d, d * that.n)

    def +(that: Rational): Rational = Rational(n * that.d + that.n * d, d * that.d)

    def -(that: Rational): Rational = this + (-that)

    def unary_- = Rational(-n, d)
}

// Exiting paste mode, now interpreting.

defined object Rational
defined class Rational

scala> val a = Rational(60075, 10000)
a: Rational = 2403/400

scala> val b = Rational(1, 1)
b: Rational = 1

scala> val c = Rational(89, 100)
c: Rational = 89/100

scala> a * (b / c)
res0: Rational = 27/4

scala> (a * (b / c)).toDouble
res1: Double = 6.75

答案 1 :(得分:3)

无法回归无限回归的有限表示会产生舍入误差。 (另见:Is floating point math broken?

我看到两种可能的解决方案,它们都不是微不足道的。

  1. 使用Rational Numbers类来表示这些值。每个值在整个积分中都有分子和分母。您需要跟踪GCD,但实际上您需要潜入两个号码的时间是向屏幕(或页面)发送值。
  2. 检查舍入错误的每个值(这可能最好通过值String表示的RegEx模式匹配来完成)并进行相应调整。

答案 2 :(得分:-1)

您想验证a / b是否等于a * (1 / b)

问题:(1 / b)的值可能不准确。

我的解决方法:
u = 0.00000000000000000000000000000000001(你说它可以工作到35位小数)
然后验证a / b至少a * ((1 / b) - u),最多a * ((1 / b) + u) 如果a为负数,那么您需要交换这些比较的符号。 这不是一个完美的解决方法,但我希望它能做到。