【问题标题】:Reciprocals using Scala BigDecimal multiplication and division使用 Scala BigDecimal 乘法和除法的倒数
【发布时间】:2016-12-07 21:26:40
【问题描述】:

我们遇到了一个问题,我们将一个大小数除以另一个大小数,并尝试通过取除数的倒数并乘以除数来验证它。

例如,基本数学会告诉我们: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 中是否有办法处理这个问题?

谢谢!

【问题讨论】:

    标签: java scala


    【解决方案1】:

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

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

    1. 使用有理数类来表示这些值。每个值在整个积分中都有一个分子和一个分母。您需要跟踪 GCD,但您真正需要输入两个数字的唯一时间是向屏幕(或页面)发送值时。
    2. 检查每个值是否存在舍入错误(这可能最好通过对值的String 表示的 RegEx 模式匹配来完成)并进行相应调整。

    【讨论】:

      【解决方案2】:

      不幸的是,没有 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 课程放在一起:

      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
      

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-08-21
          • 1970-01-01
          • 2021-03-13
          • 1970-01-01
          • 2015-12-08
          • 2017-05-23
          • 2023-01-15
          相关资源
          最近更新 更多