Fixed an error in computing gcd and lcm for some extreme values at integer range boundaries.
JIRA: MATH-243 git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@746511 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
39a80bf2cd
commit
8bf548c5e8
3
pom.xml
3
pom.xml
|
@ -165,6 +165,9 @@
|
|||
<contributor>
|
||||
<name>Christopher Schuck</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Christian Semrau</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Mauro Talevi</name>
|
||||
</contributor>
|
||||
|
|
|
@ -44,6 +44,10 @@ public class MessagesResources_fr
|
|||
/** Non-translated/translated messages arrays. */
|
||||
static final Object[][] contents = {
|
||||
|
||||
// org.apache.commons.math.util.MathUtils
|
||||
{ "overflow: gcd({0}, {1}) is 2^31",
|
||||
"d\u00e9passement de capacit\u00e9 : le PGCD de {0} et {1} vaut 2^31" },
|
||||
|
||||
// org.apache.commons.math.FunctionEvaluationException
|
||||
{ "Evaluation failed for argument = {0}",
|
||||
"Erreur d''\u00e9valuation pour l''argument {0}" },
|
||||
|
|
|
@ -20,6 +20,9 @@ package org.apache.commons.math.util;
|
|||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.math.MathException;
|
||||
import org.apache.commons.math.MathRuntimeException;
|
||||
|
||||
/**
|
||||
* Some useful additions to the built-in functions in {@link Math}.
|
||||
* @version $Revision$ $Date$
|
||||
|
@ -510,14 +513,38 @@ public final class MathUtils {
|
|||
* operations. See Knuth 4.5.2 algorithm B. This algorithm is due to Josef
|
||||
* Stein (1961).
|
||||
* </p>
|
||||
* Special cases:
|
||||
* <ul>
|
||||
* <li>The invocations
|
||||
* <code>gcd(Integer.MIN_VALUE, Integer.MIN_VALUE)</code>,
|
||||
* <code>gcd(Integer.MIN_VALUE, 0)</code> and
|
||||
* <code>gcd(0, Integer.MIN_VALUE)</code> throw an
|
||||
* <code>ArithmeticException</code>, because the result would be 2^31, which
|
||||
* is too large for an int value.</li>
|
||||
* <li>The result of <code>gcd(x, x)</code>, <code>gcd(0, x)</code> and
|
||||
* <code>gcd(x, 0)</code> is the absolute value of <code>x</code>, except
|
||||
* for the special cases above.
|
||||
* <li>The invocation <code>gcd(0, 0)</code> is the only one which returns
|
||||
* <code>0</code>.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param u a non-zero number
|
||||
* @param v a non-zero number
|
||||
* @return the greatest common divisor, never zero
|
||||
* @param u any number
|
||||
* @param v any number
|
||||
* @return the greatest common divisor, never negative
|
||||
* @throws ArithmeticException
|
||||
* if the result cannot be represented as a nonnegative int
|
||||
* value
|
||||
* @since 1.1
|
||||
*/
|
||||
public static int gcd(int u, int v) {
|
||||
public static int gcd(final int p, final int q) {
|
||||
int u = p;
|
||||
int v = q;
|
||||
if ((u == 0) || (v == 0)) {
|
||||
if ((u == Integer.MIN_VALUE) || (v == Integer.MIN_VALUE)) {
|
||||
throw MathRuntimeException.createArithmeticException(
|
||||
"overflow: gcd({0}, {1}) is 2^31",
|
||||
new Object[] { p, q });
|
||||
}
|
||||
return (Math.abs(u) + Math.abs(v));
|
||||
}
|
||||
// keep u and v negative, as negative integers range down to
|
||||
|
@ -540,7 +567,9 @@ public final class MathUtils {
|
|||
k++; // cast out twos.
|
||||
}
|
||||
if (k == 31) {
|
||||
throw new ArithmeticException("overflow: gcd is 2^31");
|
||||
throw MathRuntimeException.createArithmeticException(
|
||||
"overflow: gcd({0}, {1}) is 2^31",
|
||||
new Object[] { p, q });
|
||||
}
|
||||
// B2. Initialize: u and v have been divided by 2^k and at least
|
||||
// one is odd.
|
||||
|
@ -660,16 +689,37 @@ public final class MathUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the least common multiple between two integer values.
|
||||
* <p>
|
||||
* Returns the least common multiple of the absolute value of two numbers,
|
||||
* using the formula <code>lcm(a,b) = (a / gcd(a,b)) * b</code>.
|
||||
* </p>
|
||||
* Special cases:
|
||||
* <ul>
|
||||
* <li>The invocations <code>lcm(Integer.MIN_VALUE, n)</code> and
|
||||
* <code>lcm(n, Integer.MIN_VALUE)</code>, where <code>abs(n)</code> is a
|
||||
* power of 2, throw an <code>ArithmeticException</code>, because the result
|
||||
* would be 2^31, which is too large for an int value.</li>
|
||||
* <li>The result of <code>lcm(0, x)</code> and <code>lcm(x, 0)</code> is
|
||||
* <code>0</code> for any <code>x</code>.
|
||||
* </ul>
|
||||
*
|
||||
* @param a the first integer value.
|
||||
* @param b the second integer value.
|
||||
* @return the least common multiple between a and b.
|
||||
* @throws ArithmeticException if the lcm is too large to store as an int
|
||||
* @param a any number
|
||||
* @param b any number
|
||||
* @return the least common multiple, never negative
|
||||
* @throws ArithmeticException
|
||||
* if the result cannot be represented as a nonnegative int
|
||||
* value
|
||||
* @since 1.1
|
||||
*/
|
||||
public static int lcm(int a, int b) {
|
||||
return Math.abs(mulAndCheck(a / gcd(a, b), b));
|
||||
if (a==0 || b==0){
|
||||
return 0;
|
||||
}
|
||||
int lcm = Math.abs(mulAndCheck(a / gcd(a, b), b));
|
||||
if (lcm == Integer.MIN_VALUE){
|
||||
throw new ArithmeticException("overflow: lcm is 2^31");
|
||||
}
|
||||
return lcm;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,6 +39,10 @@ The <action> type attribute can be add,update,fix,remove.
|
|||
</properties>
|
||||
<body>
|
||||
<release version="2.0" date="TBD" description="TBD">
|
||||
<action dev="luc" type="fix" issue="MATH-243" due-to="Christian Semrau">
|
||||
Fixed an error in computing gcd and lcm for some extreme values at integer
|
||||
range boundaries.
|
||||
</action>
|
||||
<action dev="luc" type="add" issue="MATH-247" due-to="Benjamin McCann">
|
||||
Added a MathUtils method to check equality given some error bounds.
|
||||
</action>
|
||||
|
|
|
@ -429,12 +429,28 @@ public final class MathUtilsTest extends TestCase {
|
|||
assertEquals(3 * (1<<15), MathUtils.gcd(3 * (1<<20), 9 * (1<<15)));
|
||||
|
||||
assertEquals(Integer.MAX_VALUE, MathUtils.gcd(Integer.MAX_VALUE, 0));
|
||||
// abs(Integer.MIN_VALUE) == Integer.MIN_VALUE
|
||||
assertEquals(Integer.MIN_VALUE, MathUtils.gcd(Integer.MIN_VALUE, 0));
|
||||
assertEquals(Integer.MAX_VALUE, MathUtils.gcd(-Integer.MAX_VALUE, 0));
|
||||
assertEquals(1<<30, MathUtils.gcd(1<<30, -Integer.MIN_VALUE));
|
||||
try {
|
||||
MathUtils.gcd(Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||
// gcd(Integer.MIN_VALUE, 0) > Integer.MAX_VALUE
|
||||
MathUtils.gcd(Integer.MIN_VALUE, 0);
|
||||
fail("expecting ArithmeticException");
|
||||
} catch (ArithmeticException expected) {
|
||||
//
|
||||
// expected
|
||||
}
|
||||
try {
|
||||
// gcd(0, Integer.MIN_VALUE) > Integer.MAX_VALUE
|
||||
MathUtils.gcd(0, Integer.MIN_VALUE);
|
||||
fail("expecting ArithmeticException");
|
||||
} catch (ArithmeticException expected) {
|
||||
// expected
|
||||
}
|
||||
try {
|
||||
// gcd(Integer.MIN_VALUE, Integer.MIN_VALUE) > Integer.MAX_VALUE
|
||||
MathUtils.gcd(Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||
fail("expecting ArithmeticException");
|
||||
} catch (ArithmeticException expected) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -558,8 +574,32 @@ public final class MathUtilsTest extends TestCase {
|
|||
assertEquals(150, MathUtils.lcm(a, b));
|
||||
assertEquals(150, MathUtils.lcm(-a, b));
|
||||
assertEquals(150, MathUtils.lcm(a, -b));
|
||||
assertEquals(150, MathUtils.lcm(-a, -b));
|
||||
assertEquals(2310, MathUtils.lcm(a, c));
|
||||
|
||||
// Assert that no intermediate value overflows:
|
||||
// The naive implementation of lcm(a,b) would be (a*b)/gcd(a,b)
|
||||
assertEquals((1<<20)*15, MathUtils.lcm((1<<20)*3, (1<<20)*5));
|
||||
|
||||
// Special case
|
||||
assertEquals(0, MathUtils.lcm(0, 0));
|
||||
|
||||
try {
|
||||
// lcm == abs(MIN_VALUE) cannot be represented as a nonnegative int
|
||||
MathUtils.lcm(Integer.MIN_VALUE, 1);
|
||||
fail("Expecting ArithmeticException");
|
||||
} catch (ArithmeticException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
// lcm == abs(MIN_VALUE) cannot be represented as a nonnegative int
|
||||
MathUtils.lcm(Integer.MIN_VALUE, 1<<20);
|
||||
fail("Expecting ArithmeticException");
|
||||
} catch (ArithmeticException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
MathUtils.lcm(Integer.MAX_VALUE, Integer.MAX_VALUE - 1);
|
||||
fail("Expecting ArithmeticException");
|
||||
|
|
Loading…
Reference in New Issue