Partially fixing MATH-370 (full resolution is delayed to a major release).

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@952949 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gilles Sadowski 2010-06-09 10:41:26 +00:00
parent 6f54422e96
commit 4039a14460
8 changed files with 237 additions and 65 deletions

View File

@ -165,8 +165,8 @@ public abstract class AbstractStorelessUnivariateStatistic
return false;
}
AbstractStorelessUnivariateStatistic stat = (AbstractStorelessUnivariateStatistic) object;
return MathUtils.equals(stat.getResult(), this.getResult()) &&
MathUtils.equals(stat.getN(), this.getN());
return MathUtils.equalsIncludingNaN(stat.getResult(), this.getResult()) &&
MathUtils.equalsIncludingNaN(stat.getN(), this.getN());
}
/**

View File

@ -369,14 +369,14 @@ public class MultivariateSummaryStatistics
return false;
}
MultivariateSummaryStatistics stat = (MultivariateSummaryStatistics) object;
return MathUtils.equals(stat.getGeometricMean(), getGeometricMean()) &&
MathUtils.equals(stat.getMax(), getMax()) &&
MathUtils.equals(stat.getMean(), getMean()) &&
MathUtils.equals(stat.getMin(), getMin()) &&
MathUtils.equals(stat.getN(), getN()) &&
MathUtils.equals(stat.getSum(), getSum()) &&
MathUtils.equals(stat.getSumSq(), getSumSq()) &&
MathUtils.equals(stat.getSumLog(), getSumLog()) &&
return MathUtils.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
MathUtils.equalsIncludingNaN(stat.getMax(), getMax()) &&
MathUtils.equalsIncludingNaN(stat.getMean(), getMean()) &&
MathUtils.equalsIncludingNaN(stat.getMin(), getMin()) &&
MathUtils.equalsIncludingNaN(stat.getN(), getN()) &&
MathUtils.equalsIncludingNaN(stat.getSum(), getSum()) &&
MathUtils.equalsIncludingNaN(stat.getSumSq(), getSumSq()) &&
MathUtils.equalsIncludingNaN(stat.getSumLog(), getSumLog()) &&
stat.getCovariance().equals( getCovariance());
}

View File

@ -135,12 +135,12 @@ public class StatisticalSummaryValues implements Serializable,
return false;
}
StatisticalSummaryValues stat = (StatisticalSummaryValues) object;
return MathUtils.equals(stat.getMax(), getMax()) &&
MathUtils.equals(stat.getMean(), getMean()) &&
MathUtils.equals(stat.getMin(), getMin()) &&
MathUtils.equals(stat.getN(), getN()) &&
MathUtils.equals(stat.getSum(), getSum()) &&
MathUtils.equals(stat.getVariance(), getVariance());
return MathUtils.equalsIncludingNaN(stat.getMax(), getMax()) &&
MathUtils.equalsIncludingNaN(stat.getMean(), getMean()) &&
MathUtils.equalsIncludingNaN(stat.getMin(), getMin()) &&
MathUtils.equalsIncludingNaN(stat.getN(), getN()) &&
MathUtils.equalsIncludingNaN(stat.getSum(), getSum()) &&
MathUtils.equalsIncludingNaN(stat.getVariance(), getVariance());
}
/**

View File

@ -360,14 +360,14 @@ public class SummaryStatistics implements StatisticalSummary, Serializable {
return false;
}
SummaryStatistics stat = (SummaryStatistics)object;
return MathUtils.equals(stat.getGeometricMean(), getGeometricMean()) &&
MathUtils.equals(stat.getMax(), getMax()) &&
MathUtils.equals(stat.getMean(), getMean()) &&
MathUtils.equals(stat.getMin(), getMin()) &&
MathUtils.equals(stat.getN(), getN()) &&
MathUtils.equals(stat.getSum(), getSum()) &&
MathUtils.equals(stat.getSumsq(), getSumsq()) &&
MathUtils.equals(stat.getVariance(), getVariance());
return MathUtils.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
MathUtils.equalsIncludingNaN(stat.getMax(), getMax()) &&
MathUtils.equalsIncludingNaN(stat.getMean(), getMean()) &&
MathUtils.equalsIncludingNaN(stat.getMin(), getMin()) &&
MathUtils.equalsIncludingNaN(stat.getN(), getN()) &&
MathUtils.equalsIncludingNaN(stat.getSum(), getSum()) &&
MathUtils.equalsIncludingNaN(stat.getSumsq(), getSumsq()) &&
MathUtils.equalsIncludingNaN(stat.getVariance(), getVariance());
}
/**

View File

@ -403,36 +403,68 @@ public final class MathUtils {
}
/**
* Returns true iff both arguments are NaN or neither is NaN and they are
* equal
* Returns true iff they are equal as defined by
* {@link #equals(double,double,int) this method}.
*
* @param x first value
* @param y second value
* @return true if the values are equal or both are NaN
* @return {@code true} if the values are equal.
* @deprecated This method considers that {@code NaN == NaN}. In release
* 3.0, the semantics will change in order to comply with IEEE754 where it
* is specified that {@code NaN != NaN}.
* New methods have been added for those cases wher the old semantics is
* useful (see e.g. {@link equalsIncludingNaN(double,double)
* equalsIncludingNaN}.
*/
public static boolean equals(double x, double y) {
return (Double.isNaN(x) && Double.isNaN(y)) || x == y;
}
/**
* Returns true iff both arguments are equal or within the range of allowed
* error (inclusive).
* <p>
* Two NaNs are considered equals, as are two infinities with same sign.
* </p>
* Returns true if both arguments are NaN or neither is NaN and they are
* equal as defined by {@link #equals(double,double) this method}.
*
* @param x first value
* @param y second value
* @param eps the amount of absolute error to allow
* @return true if the values are equal or within range of each other
* @return {@code true} if the values are equal or both are NaN.
*/
public static boolean equals(double x, double y, double eps) {
return equals(x, y) || (Math.abs(y - x) <= eps);
public static boolean equalsIncludingNaN(double x, double y) {
return (Double.isNaN(x) && Double.isNaN(y)) || equals(x, y, 1);
}
/**
* Returns true iff both arguments are equal or within the range of allowed
* Returns true if both arguments are equal or within the range of allowed
* error (inclusive).
*
* @param x first value
* @param y second value
* @param eps the amount of absolute error to allow.
* @return {@code true} if the values are equal or within range of each other.
*/
public static boolean equals(double x, double y, double eps) {
return equals(x, y, 1) || Math.abs(y - x) <= eps;
}
/**
* Returns true if both arguments are NaN or are equal or within the range
* of allowed error (inclusive).
*
* @param x first value
* @param y second value
* @param eps the amount of absolute error to allow.
* @return {@code true} if the values are equal or within range of each other,
* or both are NaN.
*/
public static boolean equalsIncludingNaN(double x, double y, double eps) {
return equalsIncludingNaN(x, y) || (Math.abs(y - x) <= eps);
}
/**
* Returns true if both arguments are equal or within the range of allowed
* error (inclusive).
* Two float numbers are considered equal if there are {@code (maxUlps - 1)}
* (or less) floating point numbers between them (i.e. two adjacent floating
* point numbers are considered equal.
* Adapted from <a
* href="http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm">
* Bruce Dawson</a>
@ -442,11 +474,11 @@ public final class MathUtils {
* @param maxUlps {@code (maxUlps - 1)} is the number of floating point
* values between {@code x} and {@code y}.
* @return {@code true} if there are less than {@code maxUlps} floating
* point values between {@code x} and {@code y}
* point values between {@code x} and {@code y}.
*/
public static boolean equals(double x, double y, int maxUlps) {
// Check that "maxUlps" is non-negative and small enough so that the
// default NAN won't compare as equal to anything.
// Check that "maxUlps" is non-negative and small enough so that
// NaN won't compare as equal to anything (except another NaN).
assert maxUlps > 0 && maxUlps < NAN_GAP;
long xInt = Double.doubleToLongBits(x);
@ -460,18 +492,41 @@ public final class MathUtils {
yInt = SGN_MASK - yInt;
}
return Math.abs(xInt - yInt) <= maxUlps;
final boolean isEqual = (Math.abs(xInt - yInt) <= maxUlps);
return isEqual && !Double.isNaN(x) && !Double.isNaN(y);
}
/**
* Returns true iff both arguments are null or have same dimensions
* and all their elements are {@link #equals(double,double) equals}
* Returns true if both arguments are NaN or if they are equal as defined
* by {@link #equals(double,double,int) this method}.
*
* @param x first value
* @param y second value
* @param maxUlps {@code (maxUlps - 1)} is the number of floating point
* values between {@code x} and {@code y}.
* @return {@code true} if both arguments are NaN or if there are less than
* {@code maxUlps} floating point values between {@code x} and {@code y}.
*/
public static boolean equalsIncludingNaN(double x, double y, int maxUlps) {
return (Double.isNaN(x) && Double.isNaN(y)) || equals(x, y, maxUlps);
}
/**
* Returns true iff both arguments are null or have same dimensions and all
* their elements are equal as defined by
* {@link #equals(double,double) this method}.
*
* @param x first array
* @param y second array
* @return true if the values are both null or have same dimension
* and equal elements
* @since 1.2
* and equal elements.
* @deprecated This method considers that {@code NaN == NaN}. In release
* 3.0, the semantics will change in order to comply with IEEE754 where it
* is specified that {@code NaN != NaN}.
* New methods have been added for those cases wher the old semantics is
* useful (see e.g. {@link equalsIncludingNaN(double[],double[])
* equalsIncludingNaN}.
*/
public static boolean equals(double[] x, double[] y) {
if ((x == null) || (y == null)) {
@ -488,6 +543,31 @@ public final class MathUtils {
return true;
}
/**
* Returns true iff both arguments are null or have same dimensions and all
* their elements are equal as defined by
* {@link #equalsIncludingNaN(double,double) this method}.
*
* @param x first array
* @param y second array
* @return true if the values are both null or have same dimension and
* equal elements
*/
public static boolean equalsIncludingNaN(double[] x, double[] y) {
if ((x == null) || (y == null)) {
return !((x == null) ^ (y == null));
}
if (x.length != y.length) {
return false;
}
for (int i = 0; i < x.length; ++i) {
if (!equalsIncludingNaN(x[i], y[i])) {
return false;
}
}
return true;
}
/**
* Returns n!. Shorthand for <code>n</code> <a
* href="http://mathworld.wolfram.com/Factorial.html"> Factorial</a>, the

View File

@ -52,6 +52,11 @@ The <action> type attribute can be add,update,fix,remove.
If the output is not quite correct, check for invisible trailing spaces!
-->
<release version="2.2" date="TBD" description="TBD">
<action dev="erans" type="update" issue="MATH-370">
Added new "equalsIncludingNaN" methods that have the same semantics as the old "equals" methods.
These are deprecated, and their semantics will be modified (in the next major release) such that
NaNs are not considered equal (to be more compliant with IEEE754).
</action>
<action dev="luc" type="fix" issue="MATH-352" >
Added a setQRRankingThreshold method to Levenberg-Marquardt optimizer to improve robustness
of rank determination.

View File

@ -210,11 +210,11 @@ public class DescriptiveStatisticsTest extends TestCase {
dstat.addValue(i);
}
assertTrue(MathUtils.equals(mean1, dstat.getMean()));
assertTrue(MathUtils.equalsIncludingNaN(mean1, dstat.getMean()));
dstat.replaceMostRecentValue(0);
assertTrue(MathUtils.equals(mean2, dstat.getMean()));
assertTrue(MathUtils.equalsIncludingNaN(mean2, dstat.getMean()));
dstat.removeMostRecentValue();
assertTrue(MathUtils.equals(mean3, dstat.getMean()));
assertTrue(MathUtils.equalsIncludingNaN(mean3, dstat.getMean()));
}

View File

@ -314,7 +314,7 @@ public final class MathUtilsTest extends TestCase {
assertTrue(Double.isNaN(MathUtils.cosh(Double.NaN)));
}
public void testEquals() {
public void testEqualsIncludingNaN() {
double[] testArray = {
Double.NaN,
Double.POSITIVE_INFINITY,
@ -324,11 +324,11 @@ public final class MathUtilsTest extends TestCase {
for (int i = 0; i < testArray.length; i++) {
for (int j = 0; j < testArray.length; j++) {
if (i == j) {
assertTrue(MathUtils.equals(testArray[i], testArray[j]));
assertTrue(MathUtils.equals(testArray[j], testArray[i]));
assertTrue(MathUtils.equalsIncludingNaN(testArray[i], testArray[j]));
assertTrue(MathUtils.equalsIncludingNaN(testArray[j], testArray[i]));
} else {
assertTrue(!MathUtils.equals(testArray[i], testArray[j]));
assertTrue(!MathUtils.equals(testArray[j], testArray[i]));
assertTrue(!MathUtils.equalsIncludingNaN(testArray[i], testArray[j]));
assertTrue(!MathUtils.equalsIncludingNaN(testArray[j], testArray[i]));
}
}
}
@ -338,7 +338,7 @@ public final class MathUtilsTest extends TestCase {
assertTrue(MathUtils.equals(153.0000, 153.0000, .0625));
assertTrue(MathUtils.equals(153.0000, 153.0625, .0625));
assertTrue(MathUtils.equals(152.9375, 153.0000, .0625));
assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1.0));
assertFalse(MathUtils.equals(Double.NaN, Double.NaN, 1.0));
assertTrue(MathUtils.equals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0));
assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
@ -346,18 +346,44 @@ public final class MathUtilsTest extends TestCase {
assertFalse(MathUtils.equals(152.9374, 153.0000, .0625));
}
public void testEqualsWithAllowedUlps() {
assertTrue(MathUtils.equals(153, 153, 1));
public void testEqualsIncludingNaNWithAllowedDelta() {
assertTrue(MathUtils.equalsIncludingNaN(153.0000, 153.0000, .0625));
assertTrue(MathUtils.equalsIncludingNaN(153.0000, 153.0625, .0625));
assertTrue(MathUtils.equalsIncludingNaN(152.9375, 153.0000, .0625));
assertTrue(MathUtils.equalsIncludingNaN(Double.NaN, Double.NaN, 1.0));
assertTrue(MathUtils.equalsIncludingNaN(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
assertTrue(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0));
assertFalse(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
assertFalse(MathUtils.equalsIncludingNaN(153.0000, 153.0625, .0624));
assertFalse(MathUtils.equalsIncludingNaN(152.9374, 153.0000, .0625));
}
assertTrue(MathUtils.equals(153, 153.00000000000003, 1));
assertFalse(MathUtils.equals(153, 153.00000000000006, 1));
assertTrue(MathUtils.equals(153, 152.99999999999997, 1));
public void testEqualsWithAllowedUlps() {
assertTrue(MathUtils.equals(0.0, -0.0, 1));
assertTrue(MathUtils.equals(1.0, 1 + Math.ulp(1d), 1));
assertFalse(MathUtils.equals(1.0, 1 + 2 * Math.ulp(1d), 1));
final double nUp1 = Math.nextUp(1d);
final double nnUp1 = Math.nextUp(nUp1);
assertTrue(MathUtils.equals(1.0, nUp1, 1));
assertTrue(MathUtils.equals(nUp1, nnUp1, 1));
assertFalse(MathUtils.equals(1.0, nnUp1, 1));
assertTrue(MathUtils.equals(0.0, Math.ulp(0d), 1));
assertTrue(MathUtils.equals(0.0, -Math.ulp(0d), 1));
assertTrue(MathUtils.equals(153.0, 153.0, 1));
assertTrue(MathUtils.equals(153.0, 153.00000000000003, 1));
assertFalse(MathUtils.equals(153.0, 153.00000000000006, 1));
assertTrue(MathUtils.equals(153.0, 152.99999999999997, 1));
assertFalse(MathUtils.equals(153, 152.99999999999994, 1));
assertTrue(MathUtils.equals(-128, -127.99999999999999, 1));
assertFalse(MathUtils.equals(-128, -127.99999999999997, 1));
assertTrue(MathUtils.equals(-128, -128.00000000000003, 1));
assertFalse(MathUtils.equals(-128, -128.00000000000006, 1));
assertTrue(MathUtils.equals(-128.0, -127.99999999999999, 1));
assertFalse(MathUtils.equals(-128.0, -127.99999999999997, 1));
assertTrue(MathUtils.equals(-128.0, -128.00000000000003, 1));
assertFalse(MathUtils.equals(-128.0, -128.00000000000006, 1));
assertTrue(MathUtils.equals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1));
assertTrue(MathUtils.equals(Double.MAX_VALUE, Double.POSITIVE_INFINITY, 1));
@ -365,12 +391,53 @@ public final class MathUtilsTest extends TestCase {
assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1));
assertTrue(MathUtils.equals(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY, 1));
assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1));
assertFalse(MathUtils.equals(Double.NaN, Double.NaN, 1));
assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000));
}
public void testEqualsIncludingNaNWithAllowedUlps() {
assertTrue(MathUtils.equalsIncludingNaN(0.0, -0.0, 1));
assertTrue(MathUtils.equalsIncludingNaN(1.0, 1 + Math.ulp(1d), 1));
assertFalse(MathUtils.equalsIncludingNaN(1.0, 1 + 2 * Math.ulp(1d), 1));
final double nUp1 = Math.nextUp(1d);
final double nnUp1 = Math.nextUp(nUp1);
assertTrue(MathUtils.equalsIncludingNaN(1.0, nUp1, 1));
assertTrue(MathUtils.equalsIncludingNaN(nUp1, nnUp1, 1));
assertFalse(MathUtils.equalsIncludingNaN(1.0, nnUp1, 1));
assertTrue(MathUtils.equalsIncludingNaN(0.0, Math.ulp(0d), 1));
assertTrue(MathUtils.equalsIncludingNaN(0.0, -Math.ulp(0d), 1));
assertTrue(MathUtils.equalsIncludingNaN(153.0, 153.0, 1));
assertTrue(MathUtils.equalsIncludingNaN(153.0, 153.00000000000003, 1));
assertFalse(MathUtils.equalsIncludingNaN(153.0, 153.00000000000006, 1));
assertTrue(MathUtils.equalsIncludingNaN(153.0, 152.99999999999997, 1));
assertFalse(MathUtils.equalsIncludingNaN(153, 152.99999999999994, 1));
assertTrue(MathUtils.equalsIncludingNaN(-128.0, -127.99999999999999, 1));
assertFalse(MathUtils.equalsIncludingNaN(-128.0, -127.99999999999997, 1));
assertTrue(MathUtils.equalsIncludingNaN(-128.0, -128.00000000000003, 1));
assertFalse(MathUtils.equalsIncludingNaN(-128.0, -128.00000000000006, 1));
assertTrue(MathUtils.equalsIncludingNaN(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1));
assertTrue(MathUtils.equalsIncludingNaN(Double.MAX_VALUE, Double.POSITIVE_INFINITY, 1));
assertTrue(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1));
assertTrue(MathUtils.equalsIncludingNaN(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY, 1));
assertTrue(MathUtils.equalsIncludingNaN(Double.NaN, Double.NaN, 1));
assertFalse(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000));
}
/**
* @deprecated To be removed in release 3.0 (replaced by {@link
* #testArrayEqualsIncludingNaN()}.
*/
public void testArrayEquals() {
assertFalse(MathUtils.equals(new double[] { 1d }, null));
assertFalse(MathUtils.equals(null, new double[] { 1d }));
@ -392,6 +459,26 @@ public final class MathUtilsTest extends TestCase {
}
public void testArrayEqualsIncludingNaN() {
assertFalse(MathUtils.equalsIncludingNaN(new double[] { 1d }, null));
assertFalse(MathUtils.equalsIncludingNaN(null, new double[] { 1d }));
assertTrue(MathUtils.equalsIncludingNaN((double[]) null, (double[]) null));
assertFalse(MathUtils.equalsIncludingNaN(new double[] { 1d }, new double[0]));
assertTrue(MathUtils.equalsIncludingNaN(new double[] { 1d }, new double[] { 1d }));
assertTrue(MathUtils.equalsIncludingNaN(new double[] {
Double.NaN, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY, 1d, 0d
}, new double[] {
Double.NaN, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY, 1d, 0d
}));
assertFalse(MathUtils.equalsIncludingNaN(new double[] { Double.POSITIVE_INFINITY },
new double[] { Double.NEGATIVE_INFINITY }));
assertFalse(MathUtils.equalsIncludingNaN(new double[] { 1d },
new double[] { MathUtils.nextAfter(MathUtils.nextAfter(1d, 2d), 2d) }));
}
public void testFactorial() {
for (int i = 1; i < 21; i++) {
assertEquals(i + "! ", factorial(i), MathUtils.factorial(i));