Search code examples
javabigintegerapache-commons-math

Why does the BigFraction class in the Apache-Commons-Math library return incorrect division results?


In the spirit of using existing, tested and stable libraries of code, I started using the Apache-Commons-Math library and its BigFraction class to perform some rational calculations for an Android app I'm writing called RationalCalc.

It works great for every task that I have thrown at it, except for one nagging problem. When dividing certain BigFraction values, I am getting incorrect results.

If I create a BigFraction with the inverse of the divisor and multiply instead, I get the same incorrect answer but perhaps that is what the library is doing internally anyway.

Does anyone know what I am doing wrong?

The division works correctly with a BigFraction of 2.5 but not 2.51, 2.49, etc...

[UPDATE]

This was indeed a bug in the apache-commons-math 2.0 libraries. The bug is fixed in v.2.1.

It is now listed in the Fixed Issues section of the bug tracker:

When multiplying two BigFraction objects with numerators larger than will fit in an java-primitive int the result of BigFraction.ZERO is incorrectly returned..

Thanks to @BartK for attempting to reproduce the issue and setting me on the right track.

[/UPDATE]

// *** incorrect! ***
BigFraction one = new BigFraction(1.524);
//one: 1715871458028159 / 1125899906842624

BigFraction two = new BigFraction(2.51);
//two: 1413004383087493 / 562949953421312

BigFraction three = one.divide(two);
//three: 0

Log.i("solve", three.toString());
//should be 0.607171315  ??
//returns 0


// *** correct! ****
BigFraction four = new BigFraction(1.524);
//four: 1715871458028159 / 1125899906842624

BigFraction five = new BigFraction(2.5);
//five: 5 / 2

BigFraction six = four.divide(five);
//six: 1715871458028159 / 2814749767106560

Log.i("solve", six.toString());
//should be 0.6096  ??
//returns 0.6096

Solution

  • Providing double's in the constructors lead to round-off errors. Using exact numerators and denominators will result in the expected outcome:

    public class CommonsMathTest {
    
        public static void main(String[] args) {
    
            BigFraction one = new BigFraction(1524, 1000);
            System.out.println("one   = " + one);
    
            BigFraction two = new BigFraction(251, 100);
            System.out.println("two   = " + two);
    
            BigFraction three = one.divide(two);
            System.out.println("three = " + three);
    
            BigFraction four = new BigFraction(1524, 1000);
            System.out.println("four  = " + four);
    
            BigFraction five = new BigFraction(5, 2);
            System.out.println("five  = " + five);
    
            BigFraction six = four.divide(five);
            System.out.println("six   = " + six + " = " + six.bigDecimalValue());
        }
    }
    

    produces:

    one   = 381 / 250
    two   = 251 / 100
    three = 762 / 1255
    four  = 381 / 250
    five  = 5 / 2
    six   = 381 / 625 = 0.6096
    

    EDIT

    By the way, I could not reproduce your output. Using Commons-Math 2.1, the following:

    BigFraction one = new BigFraction(1.524);
    BigFraction two = new BigFraction(2.51);
    BigFraction three = one.divide(two);
    System.out.println(three.toString() + " = " +three.doubleValue());
    

    does not produce 0 as you said, but prints:

    1715871458028159 / 2826008766174986 = 0.6071713147410359