2

We are running into an issue where we are dividing a bigdecimal by another bigdecimal, and trying to verify it by taking the reciprocal of the divisor and multiplying the dividend.

For example, basic math would tell us that: a / b = a * (1 / b)

More specifically, here is what we are trying to do: 6.0075 / 0.89 = 6.0075 * (1 / 0.89) = 6.75

Using BigDecimal for added precision, we can get the first part:

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

But not the second:

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

Digging in further, we see that BigDecimal(1.00) / BigDecimal(0.89) is an imprecise result and that Scala's BigDecimal wrapper around Java's math.BigDecimal specifies a precision of 35 decimals with a RoundingMode of HALF_EVEN. Unfortunately, 1.00 / 0.89 has an infinite number of decimal places so using java's BigDecimal directly won't help either.

Has anyone else encountered this issue? Is there a way in either Scala or Java to handle this aside from attempting to rewrite the the expression 1.00/0.89?

Thanks!

evan.oman
  • 5,922
  • 22
  • 43
plim8481
  • 53
  • 5

3 Answers3

3

Unfortunately there is no 100% accurate way to represent numbers with an infinite decimal expansion like 1.00/0.89 (or 1/3 for that matter) with floating point numbers. This is a limitation of finite machines trying to emulate infinite properties.

However if you only care about accuracy to a certain number of digits (like 2 if you are using currency) then you could use Java's MathContext and the setScale method:

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

Note that I am using the string version of your doubles because they are a more precise representation (because even 0.89 cannot be represented in floating point numbers perfectly).

EDIT:

Inspired by @jwvh's comment about Rational Numbers I threw this basic Rational class together:

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
evan.oman
  • 5,922
  • 22
  • 43
3

There's no getting around the fact that a finite representation of an infinite regression will have rounding errors. (See also: Is floating point math broken?)

I see two possible solutions, neither of them trivial.

  1. Use a Rational Numbers class to represent these values. Every value will have both a numerator and a denominator in whole integrals. You'll need to track the GCD, but the only time you'll actually need to dived two numbers is when sending a value to the screen (or page).
  2. Check each value for rounding errors (this would probably be best done via RegEx pattern matching on theString representation of the value) and adjust accordingly.
Community
  • 1
  • 1
jwvh
  • 50,871
  • 7
  • 38
  • 64
-1

You want to verify that a / b is equal to a * (1 / b)

Problem: value for (1 / b) might not be exact.

My workaround:
Let u = 0.00000000000000000000000000000000001 (you say it works to 35 decimal places)
Then verify that a / b is at least a * ((1 / b) - u) and at most a * ((1 / b) + u)
If a is negative, then you will need to swap the signs of those comparisons. This is not a perfect workaround, but I hope it will do.

Robert Lozyniak
  • 322
  • 1
  • 7