You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by ps...@apache.org on 2005/05/04 07:15:00 UTC

svn commit: r168072 - in /jakarta/commons/proper/math/trunk: src/java/org/apache/commons/math/fraction/Fraction.java src/java/org/apache/commons/math/util/MathUtils.java src/test/org/apache/commons/math/fraction/FractionTest.java src/test/org/apache/commons/math/util/MathUtilsTest.java xdocs/changes.xml

Author: psteitz
Date: Tue May  3 22:14:59 2005
New Revision: 168072

URL: http://svn.apache.org/viewcvs?rev=168072&view=rev
Log:
Ported numerics improvements in commons lang Fraction implementation.
Added utility methods for overflow-checked integer arithmetic and
improved gcd method in MathUtils.

Modified:
    jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/fraction/Fraction.java
    jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java
    jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/fraction/FractionTest.java
    jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java
    jakarta/commons/proper/math/trunk/xdocs/changes.xml

Modified: jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/fraction/Fraction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/fraction/Fraction.java?rev=168072&r1=168071&r2=168072&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/fraction/Fraction.java (original)
+++ jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/fraction/Fraction.java Tue May  3 22:14:59 2005
@@ -15,6 +15,7 @@
  */
 package org.apache.commons.math.fraction;
 
+import java.math.BigInteger;
 import org.apache.commons.math.ConvergenceException;
 import org.apache.commons.math.util.MathUtils;
 
@@ -118,9 +119,21 @@
      * reduced to lowest terms.
      * @param num the numerator.
      * @param den the denominator.
+     * @throws ArithmeticException if the denomiator is <code>zero</code>
      */
     public Fraction(int num, int den) {
         super();
+        if (den == 0) {
+            throw new ArithmeticException("The denominator must not be zero");
+        }
+        if (den < 0) {
+            if (num == Integer.MIN_VALUE ||
+                    den == Integer.MIN_VALUE) {
+                throw new ArithmeticException("overflow: can't negate");
+            }
+            num = -num;
+            den = -den;
+        }
         this.numerator = num;
         this.denominator = den;
         reduce();
@@ -141,20 +154,6 @@
     }
     
     /**
-     * Return the sum of this fraction and the given fraction.  The returned
-     * fraction is reduced to lowest terms.
-     *
-     * @param rhs the other fraction.
-     * @return the fraction sum in lowest terms.
-     */
-    public Fraction add(Fraction rhs) {
-        int den = MathUtils.lcm(denominator, rhs.denominator);
-        int num = (numerator * (den / denominator)) +
-            (rhs.numerator * (den / rhs.denominator));
-        return new Fraction(num, den);
-    }
-    
-    /**
      * Compares this object to another based on size.
      * @param object the object to compare to
      * @return -1 if this is less than <tt>object</tt>, +1 if this is greater
@@ -177,16 +176,6 @@
         
         return ret;
     }
-
-    /**
-     * Return the quotient of this fraction and the given fraction.  The
-     * returned fraction is reduced to lowest terms.
-     * @param rhs the other fraction.
-     * @return the fraction quotient in lowest terms.
-     */
-    public Fraction divide(Fraction rhs) {
-        return multiply(rhs.reciprocal());
-    }
     
     /**
      * Gets the fraction as a <tt>double</tt>. This calculates the fraction as
@@ -281,21 +270,13 @@
     }
     
     /**
-     * Return the product of this fraction and the given fraction.  The returned
-     * fraction is reduced to lowest terms.
-     * @param rhs the other fraction.
-     * @return the fraction product in lowest terms.
-     */
-    public Fraction multiply(Fraction rhs) {
-        return new Fraction(numerator * rhs.numerator, 
-                denominator * rhs.denominator);
-    }
-    
-    /**
      * Return the additive inverse of this fraction.
      * @return the negation of this fraction.
      */
     public Fraction negate() {
+        if (numerator==Integer.MIN_VALUE) {
+            throw new ArithmeticException("overflow: too large to negate");
+        }
         return new Fraction(-numerator, denominator);
     }
 
@@ -308,13 +289,171 @@
     }
     
     /**
-     * Return the difference between this fraction and the given fraction.  The
-     * returned fraction is reduced to lowest terms.
-     * @param rhs the other fraction.
-     * @return the fraction difference in lowest terms.
+     * <p>Adds the value of this fraction to another, returning the result in reduced form.
+     * The algorithm follows Knuth, 4.5.1.</p>
+     *
+     * @param fraction  the fraction to add, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator exceeds
+     *  <code>Integer.MAX_VALUE</code>
      */
-    public Fraction subtract(Fraction rhs) {
-        return add(rhs.negate());
+    public Fraction add(Fraction fraction) {
+        return addSub(fraction, true /* add */);
+    }
+
+    /**
+     * <p>Subtracts the value of another fraction from the value of this one, 
+     * returning the result in reduced form.</p>
+     *
+     * @param fraction  the fraction to subtract, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator
+     *   cannot be represented in an <code>int</code>.
+     */
+    public Fraction subtract(Fraction fraction) {
+        return addSub(fraction, false /* subtract */);
+    }
+
+    /** 
+     * Implement add and subtract using algorithm described in Knuth 4.5.1.
+     * 
+     * @param fraction the fraction to subtract, must not be <code>null</code>
+     * @param isAdd true to add, false to subtract
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator
+     *   cannot be represented in an <code>int</code>.
+     */
+    private Fraction addSub(Fraction fraction, boolean isAdd) {
+        if (fraction == null) {
+            throw new IllegalArgumentException("The fraction must not be null");
+        }
+        // zero is identity for addition.
+        if (numerator == 0) {
+            return isAdd ? fraction : fraction.negate();
+        }
+        if (fraction.numerator == 0) {
+            return this;
+        }     
+        // if denominators are randomly distributed, d1 will be 1 about 61%
+        // of the time.
+        int d1 = MathUtils.gcd(denominator, fraction.denominator);
+        if (d1==1) {
+            // result is ( (u*v' +/- u'v) / u'v')
+            int uvp = MathUtils.mulAndCheck(numerator, fraction.denominator);
+            int upv = MathUtils.mulAndCheck(fraction.numerator, denominator);
+            return new Fraction
+                (isAdd ? MathUtils.addAndCheck(uvp, upv) : 
+                 MathUtils.subAndCheck(uvp, upv),
+                 MathUtils.mulAndCheck(denominator, fraction.denominator));
+        }
+        // the quantity 't' requires 65 bits of precision; see knuth 4.5.1
+        // exercise 7.  we're going to use a BigInteger.
+        // t = u(v'/d1) +/- v(u'/d1)
+        BigInteger uvp = BigInteger.valueOf(numerator)
+        .multiply(BigInteger.valueOf(fraction.denominator/d1));
+        BigInteger upv = BigInteger.valueOf(fraction.numerator)
+        .multiply(BigInteger.valueOf(denominator/d1));
+        BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv);
+        // but d2 doesn't need extra precision because
+        // d2 = gcd(t,d1) = gcd(t mod d1, d1)
+        int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue();
+        int d2 = (tmodd1==0)?d1:MathUtils.gcd(tmodd1, d1);
+
+        // result is (t/d2) / (u'/d1)(v'/d2)
+        BigInteger w = t.divide(BigInteger.valueOf(d2));
+        if (w.bitLength() > 31) {
+            throw new ArithmeticException
+            ("overflow: numerator too large after multiply");
+        }
+        return new Fraction (w.intValue(), 
+                MathUtils.mulAndCheck(denominator/d1, 
+                        fraction.denominator/d2));
+    }
+
+    /**
+     * <p>Multiplies the value of this fraction by another, returning the 
+     * result in reduced form.</p>
+     *
+     * @param fraction  the fraction to multiply by, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator exceeds
+     *  <code>Integer.MAX_VALUE</code>
+     */
+    public Fraction multiply(Fraction fraction) {
+        if (fraction == null) {
+            throw new IllegalArgumentException("The fraction must not be null");
+        }
+        if (numerator == 0 || fraction.numerator == 0) {
+            return ZERO;
+        }
+        // knuth 4.5.1
+        // make sure we don't overflow unless the result *must* overflow.
+        int d1 = MathUtils.gcd(numerator, fraction.denominator);
+        int d2 = MathUtils.gcd(fraction.numerator, denominator);
+        return getReducedFraction
+        (MathUtils.mulAndCheck(numerator/d1, fraction.numerator/d2),
+                MathUtils.mulAndCheck(denominator/d2, fraction.denominator/d1));
+    }
+
+    /**
+     * <p>Divide the value of this fraction by another.</p>
+     *
+     * @param fraction  the fraction to divide by, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the fraction to divide by is zero
+     * @throws ArithmeticException if the resulting numerator or denominator exceeds
+     *  <code>Integer.MAX_VALUE</code>
+     */
+    public Fraction divide(Fraction fraction) {
+        if (fraction == null) {
+            throw new IllegalArgumentException("The fraction must not be null");
+        }
+        if (fraction.numerator == 0) {
+            throw new ArithmeticException("The fraction to divide by must not be zero");
+        }
+        return multiply(fraction.reciprocal());
+    }
+    
+    /**
+     * <p>Creates a <code>Fraction</code> instance with the 2 parts
+     * of a fraction Y/Z.</p>
+     *
+     * <p>Any negative signs are resolved to be on the numerator.</p>
+     *
+     * @param numerator  the numerator, for example the three in 'three sevenths'
+     * @param denominator  the denominator, for example the seven in 'three sevenths'
+     * @return a new fraction instance, with the numerator and denominator reduced
+     * @throws ArithmeticException if the denominator is <code>zero</code>
+     */
+    public static Fraction getReducedFraction(int numerator, int denominator) {
+        if (denominator == 0) {
+            throw new ArithmeticException("The denominator must not be zero");
+        }
+        if (numerator==0) {
+            return ZERO; // normalize zero.
+        }
+        // allow 2^k/-2^31 as a valid fraction (where k>0)
+        if (denominator==Integer.MIN_VALUE && (numerator&1)==0) {
+            numerator/=2; denominator/=2;
+        }
+        if (denominator < 0) {
+            if (numerator==Integer.MIN_VALUE ||
+                    denominator==Integer.MIN_VALUE) {
+                throw new ArithmeticException("overflow: can't negate");
+            }
+            numerator = -numerator;
+            denominator = -denominator;
+        }
+        // simplify fraction.
+        int gcd = MathUtils.gcd(numerator, denominator);
+        numerator /= gcd;
+        denominator /= gcd;
+        return new Fraction(numerator, denominator);
     }
     
     /**

Modified: jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java?rev=168072&r1=168071&r2=168072&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java (original)
+++ jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java Tue May  3 22:14:59 2005
@@ -535,31 +535,110 @@
     }
 
     /**
-     * Returns the greatest common divisor between two integer values.
-     * @param a the first integer value.
-     * @param b the second integer value.
-     * @return the greatest common divisor between a and b.
+     * <p>Gets the greatest common divisor of the absolute value of
+     * two numbers, using the "binary gcd" method which avoids
+     * division and modulo operations.  See Knuth 4.5.2 algorithm B.
+     * This algorithm is due to Josef Stein (1961).</p>
+     *
+     * @param u  a non-zero number
+     * @param v  a non-zero number
+     * @return the greatest common divisor, never zero
      */
-    public static int gcd(int a, int b) {
-        int ret;
-        
-        if (a == 0) {
-            ret = Math.abs(b);
-        } else if (b == 0) {
-            ret = Math.abs(a);
-        } else if (a < 0) {
-            ret = gcd(-a, b);
-        } else if (b < 0) {
-            ret = gcd(a, -b);
-        } else {
-            int r = 0;
-            while(b > 0){
-                r = a % b;
-                a = b;
-                b = r;
+    public static int gcd(int u, int v) {
+        if (u * v == 0) {
+            return (Math.abs(u) + Math.abs(v));
+        }
+        // keep u and v negative, as negative integers range down to
+        // -2^31, while positive numbers can only be as large as 2^31-1
+        // (i.e. we can't necessarily negate a negative number without
+        // overflow)
+        /* assert u!=0 && v!=0; */
+        if (u>0) { u=-u; } // make u negative
+        if (v>0) { v=-v; } // make v negative
+        // B1. [Find power of 2]
+        int k=0;
+        while ((u&1)==0 && (v&1)==0 && k<31) { // while u and v are both even...
+            u/=2; v/=2; k++; // cast out twos.
+        }
+        if (k==31) {
+            throw new ArithmeticException("overflow: gcd is 2^31");
+        }
+        // B2. Initialize: u and v have been divided by 2^k and at least
+        //     one is odd.
+        int t = ((u&1)==1) ? v : -(u/2)/*B3*/;
+        // t negative: u was odd, v may be even (t replaces v)
+        // t positive: u was even, v is odd (t replaces u)
+        do {
+            /* assert u<0 && v<0; */
+            // B4/B3: cast out twos from t.
+            while ((t&1)==0) { // while t is even..
+                t/=2; // cast out twos
             }
-            ret = a;
+            // B5 [reset max(u,v)]
+            if (t>0) {
+                u = -t;
+            } else {
+                v = t;
+            }
+            // B6/B3. at this point both u and v should be odd.
+            t = (v - u)/2;
+            // |u| larger: t positive (replace u)
+            // |v| larger: t negative (replace v)
+        } while (t!=0);
+        return -u*(1<<k); // gcd is u*2^k
+    }
+
+    /** 
+     * Multiply two integers, checking for overflow.
+     * 
+     * @param x a factor
+     * @param y a factor
+     * @return the product <code>x*y</code>
+     * @throws ArithmeticException if the result can not be represented as
+     *                             an int
+     */
+    public static int mulAndCheck(int x, int y) {
+        long m = ((long)x)*((long)y);
+        if (m < Integer.MIN_VALUE ||
+                m > Integer.MAX_VALUE) {
+            throw new ArithmeticException("overflow: mul");
+        }
+        return (int)m;
+    }
+    
+    /** 
+     * Add two integers, checking for overflow.
+     * 
+     * @param x an addend
+     * @param y an addend
+     * @return the sum <code>x+y</code>
+     * @throws ArithmeticException if the result can not be represented as
+     * an int
+     */
+    public static int addAndCheck(int x, int y) {
+        long s = (long)x+(long)y;
+        if (s < Integer.MIN_VALUE ||
+                s > Integer.MAX_VALUE) {
+            throw new ArithmeticException("overflow: add");
+        }
+        return (int)s;
+    }
+    
+    /** 
+     * Subtract two integers, checking for overflow.
+     * 
+     * @param x the minuend
+     * @param y the subtrahend
+     * @return the difference <code>x-y</code>
+     * @throws ArithmeticException if the result can not be represented as
+     * an int
+     */
+    public static int subAndCheck(int x, int y) {
+        long s = (long)x-(long)y;
+        if (s < Integer.MIN_VALUE ||
+                s > Integer.MAX_VALUE) {
+            throw new ArithmeticException("overflow: add");
         }
-        return ret;
+        return (int)s;
     }
 }

Modified: jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/fraction/FractionTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/fraction/FractionTest.java?rev=168072&r1=168071&r2=168072&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/fraction/FractionTest.java (original)
+++ jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/fraction/FractionTest.java Tue May  3 22:14:59 2005
@@ -66,6 +66,63 @@
         assertFraction(10, 21, c.abs());
     }
     
+    public void testReciprocal() {
+        Fraction f = null;
+        
+        f = new Fraction(50, 75);
+        f = f.reciprocal();
+        assertEquals(3, f.getNumerator());
+        assertEquals(2, f.getDenominator());
+        
+        f = new Fraction(4, 3);
+        f = f.reciprocal();
+        assertEquals(3, f.getNumerator());
+        assertEquals(4, f.getDenominator());
+        
+        f = new Fraction(-15, 47);
+        f = f.reciprocal();
+        assertEquals(-47, f.getNumerator());
+        assertEquals(15, f.getDenominator());
+        
+        f = new Fraction(0, 3);
+        try {
+            f = f.reciprocal();
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        // large values
+        f = new Fraction(Integer.MAX_VALUE, 1);
+        f = f.reciprocal();
+        assertEquals(1, f.getNumerator());
+        assertEquals(Integer.MAX_VALUE, f.getDenominator());
+    }
+    
+    public void testNegate() {
+        Fraction f = null;
+        
+        f = new Fraction(50, 75);
+        f = f.negate();
+        assertEquals(-2, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+        
+        f = new Fraction(-50, 75);
+        f = f.negate();
+        assertEquals(2, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+
+        // large values
+        f = new Fraction(Integer.MAX_VALUE-1, Integer.MAX_VALUE);
+        f = f.negate();
+        assertEquals(Integer.MIN_VALUE+2, f.getNumerator());
+        assertEquals(Integer.MAX_VALUE, f.getDenominator());
+
+        f = new Fraction(Integer.MIN_VALUE, 1);
+        try {
+            f = f.negate();
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+    }
+    
     public void testAdd() {
         Fraction a = new Fraction(1, 2);
         Fraction b = new Fraction(2, 3);
@@ -74,6 +131,75 @@
         assertFraction(7, 6, a.add(b));
         assertFraction(7, 6, b.add(a));
         assertFraction(4, 3, b.add(b));
+        
+        Fraction f1 = new Fraction(Integer.MAX_VALUE - 1, 1);
+        Fraction f2 = Fraction.ONE;
+        Fraction f = f1.add(f2);
+        assertEquals(Integer.MAX_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+        
+        f1 = new Fraction(-1, 13*13*2*2);
+        f2 = new Fraction(-2, 13*17*2);
+        f = f1.add(f2);
+        assertEquals(13*13*17*2*2, f.getDenominator());
+        assertEquals(-17 - 2*13*2, f.getNumerator());
+        
+        try {
+            f.add(null);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+        
+        // if this fraction is added naively, it will overflow.
+        // check that it doesn't.
+        f1 = new Fraction(1,32768*3);
+        f2 = new Fraction(1,59049);
+        f = f1.add(f2);
+        assertEquals(52451, f.getNumerator());
+        assertEquals(1934917632, f.getDenominator());
+
+        f1 = new Fraction(Integer.MIN_VALUE, 3);
+        f2 = new Fraction(1,3);
+        f = f1.add(f2);
+        assertEquals(Integer.MIN_VALUE+1, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+        
+        f1 = new Fraction(Integer.MAX_VALUE - 1, 1);
+        f2 = Fraction.ONE;
+        f = f1.add(f2);
+        assertEquals(Integer.MAX_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+        
+        try {
+            f = f.add(Fraction.ONE); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+        
+        // denominator should not be a multiple of 2 or 3 to trigger overflow
+        f1 = new Fraction(Integer.MIN_VALUE, 5);
+        f2 = new Fraction(-1,5);
+        try {
+            f = f1.add(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+        
+        try {
+            f= new Fraction(-Integer.MAX_VALUE, 1);
+            f = f.add(f);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+        
+        try {
+            f= new Fraction(-Integer.MAX_VALUE, 1);
+            f = f.add(f);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+        
+        f1 = new Fraction(3,327680);
+        f2 = new Fraction(2,59049);
+        try {
+            f = f1.add(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
     }
     
     public void testDivide() {
@@ -84,6 +210,51 @@
         assertFraction(3, 4, a.divide(b));
         assertFraction(4, 3, b.divide(a));
         assertFraction(1, 1, b.divide(b));
+        
+        Fraction f1 = new Fraction(3, 5);
+        Fraction f2 = Fraction.ZERO;
+        try {
+            Fraction f = f1.divide(f2);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+        
+        f1 = new Fraction(0, 5);
+        f2 = new Fraction(2, 7);
+        Fraction f = f1.divide(f2);
+        assertSame(Fraction.ZERO, f);
+        
+        f1 = new Fraction(2, 7);
+        f2 = Fraction.ONE;
+        f = f1.divide(f2);
+        assertEquals(2, f.getNumerator());
+        assertEquals(7, f.getDenominator());
+        
+        f1 = new Fraction(1, Integer.MAX_VALUE);
+        f = f1.divide(f1);  
+        assertEquals(1, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+        
+        f1 = new Fraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        f2 = new Fraction(1, Integer.MAX_VALUE);
+        f = f1.divide(f2);
+        assertEquals(Integer.MIN_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        try {
+            f.divide(null);
+            fail("IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+        
+        try {
+            f1 = new Fraction(1, Integer.MAX_VALUE);
+            f = f1.divide(f1.reciprocal());  // should overflow
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+        try {
+            f1 = new Fraction(1, -Integer.MAX_VALUE);
+            f = f1.divide(f1.reciprocal());  // should overflow
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
     }
     
     public void testMultiply() {
@@ -94,6 +265,17 @@
         assertFraction(1, 3, a.multiply(b));
         assertFraction(1, 3, b.multiply(a));
         assertFraction(4, 9, b.multiply(b));
+        
+        Fraction f1 = new Fraction(Integer.MAX_VALUE, 1);
+        Fraction f2 = new Fraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        Fraction f = f1.multiply(f2);
+        assertEquals(Integer.MIN_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        try {
+            f.multiply(null);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
     }
     
     public void testSubtract() {
@@ -104,5 +286,65 @@
         assertFraction(-1, 6, a.subtract(b));
         assertFraction(1, 6, b.subtract(a));
         assertFraction(0, 1, b.subtract(b));
+        
+        Fraction f = new Fraction(1,1);
+        try {
+            f.subtract(null);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+        
+        // if this fraction is subtracted naively, it will overflow.
+        // check that it doesn't.
+        Fraction f1 = new Fraction(1,32768*3);
+        Fraction f2 = new Fraction(1,59049);
+        f = f1.subtract(f2);
+        assertEquals(-13085, f.getNumerator());
+        assertEquals(1934917632, f.getDenominator());
+
+        f1 = new Fraction(Integer.MIN_VALUE, 3);
+        f2 = new Fraction(1,3).negate();
+        f = f1.subtract(f2);
+        assertEquals(Integer.MIN_VALUE+1, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+        
+        f1 = new Fraction(Integer.MAX_VALUE, 1);
+        f2 = Fraction.ONE;
+        f = f1.subtract(f2);
+        assertEquals(Integer.MAX_VALUE-1, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        try {
+            f1 = new Fraction(1, Integer.MAX_VALUE);
+            f2 = new Fraction(1, Integer.MAX_VALUE - 1);
+            f = f1.subtract(f2);
+            fail("expecting ArithmeticException");  //should overflow
+        } catch (ArithmeticException ex) {}
+        
+        // denominator should not be a multiple of 2 or 3 to trigger overflow
+        f1 = new Fraction(Integer.MIN_VALUE, 5);
+        f2 = new Fraction(1,5);
+        try {
+            f = f1.subtract(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+        
+        try {
+            f= new Fraction(Integer.MIN_VALUE, 1);
+            f = f.subtract(Fraction.ONE);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+        
+        try {
+            f= new Fraction(Integer.MAX_VALUE, 1);
+            f = f.subtract(Fraction.ONE.negate());
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+        
+        f1 = new Fraction(3,327680);
+        f2 = new Fraction(2,59049);
+        try {
+            f = f1.subtract(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
     }
 }

Modified: jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java?rev=168072&r1=168071&r2=168072&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java (original)
+++ jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java Tue May  3 22:14:59 2005
@@ -41,6 +41,42 @@
         return suite;
     }
     
+    public void testAddAndCheck() {
+        int big = Integer.MAX_VALUE;
+        int bigNeg = Integer.MIN_VALUE;
+        assertEquals(big, MathUtils.addAndCheck(big, 0));
+        try {
+            int res = MathUtils.addAndCheck(big, 1);
+        } catch (ArithmeticException ex) {}
+        try {
+            int res = MathUtils.addAndCheck(bigNeg, -1);
+        } catch (ArithmeticException ex) {}
+    }
+    
+    public void testMulAndCheck() {
+        int big = Integer.MAX_VALUE;
+        int bigNeg = Integer.MIN_VALUE;
+        assertEquals(big, MathUtils.mulAndCheck(big, 1));
+        try {
+            int res = MathUtils.mulAndCheck(big, 2);
+        } catch (ArithmeticException ex) {}
+        try {
+            int res = MathUtils.mulAndCheck(bigNeg, 2);
+        } catch (ArithmeticException ex) {}
+    }
+    
+    public void testSubAndCheck() {
+        int big = Integer.MAX_VALUE;
+        int bigNeg = Integer.MIN_VALUE;
+        assertEquals(big, MathUtils.subAndCheck(big, 0));
+        try {
+            int res = MathUtils.subAndCheck(big, -1);
+        } catch (ArithmeticException ex) {}
+        try {
+            int res = MathUtils.subAndCheck(bigNeg, 1);
+        } catch (ArithmeticException ex) {}
+    }
+    
     public void testBinomialCoefficient() {
         long[] bcoef5 = {1,5,10,10,5,1};
         long[] bcoef6 = {1,6,15,20,15,6,1};

Modified: jakarta/commons/proper/math/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/math/trunk/xdocs/changes.xml?rev=168072&r1=168071&r2=168072&view=diff
==============================================================================
--- jakarta/commons/proper/math/trunk/xdocs/changes.xml (original)
+++ jakarta/commons/proper/math/trunk/xdocs/changes.xml Tue May  3 22:14:59 2005
@@ -44,7 +44,12 @@
         incorrectly using heteroscedastic t statistic.  Also improved sensitivity
         of test cases.
       </action>
-       <action dev="psteitz" type="fix" issue="34448"  due-to="Gilles Gaillard">
+      <action dev="psteitz" type="update">
+        Ported numerics improvements in commons lang Fraction implementation.
+        Added utility methods for overflow-checked integer arithmetic and
+        improved gcd method in MathUtils.
+      </action>
+      <action dev="psteitz" type="fix" issue="34448"  due-to="Gilles Gaillard">
         Fixed javadoc errors. One-sided t-test significance adjustment was
         reversed in javadoc for boolean-valued test methods.
       </action>



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org