MATH-471 MathUtils.equals(double, double) does not work properly for floats
Added equals(float, float) methods and tests git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/branches/MATH_2_X@1060080 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
7aabd3b5a2
commit
9579702fcf
|
@ -71,6 +71,9 @@ public final class MathUtils {
|
||||||
/** Offset to order signed double numbers lexicographically. */
|
/** Offset to order signed double numbers lexicographically. */
|
||||||
private static final long SGN_MASK = 0x8000000000000000L;
|
private static final long SGN_MASK = 0x8000000000000000L;
|
||||||
|
|
||||||
|
/** Offset to order signed double numbers lexicographically. */
|
||||||
|
private static final int SGN_MASK_FLOAT = 0x80000000;
|
||||||
|
|
||||||
/** All long-representable factorials */
|
/** All long-representable factorials */
|
||||||
private static final long[] FACTORIALS = new long[] {
|
private static final long[] FACTORIALS = new long[] {
|
||||||
1l, 1l, 2l,
|
1l, 1l, 2l,
|
||||||
|
@ -405,6 +408,180 @@ public final class MathUtils {
|
||||||
return (FastMath.exp(x) + FastMath.exp(-x)) / 2.0;
|
return (FastMath.exp(x) + FastMath.exp(-x)) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true iff if both arguments are NaN or neither is NaN
|
||||||
|
* and they are equal as defined by {x==y}.
|
||||||
|
*
|
||||||
|
* @param x first value
|
||||||
|
* @param y second value
|
||||||
|
* @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(float,float)
|
||||||
|
* equalsIncludingNaN}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static boolean equals(float x, float y) {
|
||||||
|
return (Float.isNaN(x) && Float.isNaN(y)) || x == y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if both arguments are NaN or neither is NaN and they are
|
||||||
|
* equal as defined by {@link #equals(float,float,int)}.
|
||||||
|
*
|
||||||
|
* @param x first value
|
||||||
|
* @param y second value
|
||||||
|
* @return {@code true} if the values are equal or both are NaN.
|
||||||
|
*/
|
||||||
|
public static boolean equalsIncludingNaN(float x, float y) {
|
||||||
|
return (Float.isNaN(x) && Float.isNaN(y)) || equals(x, y, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(float x, float y, float eps) {
|
||||||
|
return equals(x, y, 1) || FastMath.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(float x, float y, float eps) {
|
||||||
|
return equalsIncludingNaN(x, y) || (FastMath.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 fewer) 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>
|
||||||
|
*
|
||||||
|
* @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 there are fewer than {@code maxUlps} floating
|
||||||
|
* point values between {@code x} and {@code y}.
|
||||||
|
*/
|
||||||
|
public static boolean equals(float x, float y, int maxUlps) {
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
int xInt = Float.floatToIntBits(x);
|
||||||
|
int yInt = Float.floatToIntBits(y);
|
||||||
|
|
||||||
|
// Make lexicographically ordered as a two's-complement integer.
|
||||||
|
if (xInt < 0) {
|
||||||
|
xInt = SGN_MASK_FLOAT - xInt;
|
||||||
|
}
|
||||||
|
if (yInt < 0) {
|
||||||
|
yInt = SGN_MASK_FLOAT - yInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
|
||||||
|
|
||||||
|
return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if both arguments are NaN or if they are equal as defined
|
||||||
|
* by {@link #equals(float,float,int)}.
|
||||||
|
*
|
||||||
|
* @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(float x, float y, int maxUlps) {
|
||||||
|
return (Float.isNaN(x) && Float.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(float,float)}.
|
||||||
|
*
|
||||||
|
* @param x first array
|
||||||
|
* @param y second array
|
||||||
|
* @return true if the values are both null or have same dimension
|
||||||
|
* 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(float[],float[])
|
||||||
|
* equalsIncludingNaN}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static boolean equals(float[] x, float[] 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 (!equals(x[i], y[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true iff both arguments are null or have same dimensions and all
|
||||||
|
* their elements are equal as defined by
|
||||||
|
* {@link #equalsIncludingNaN(float,float)}.
|
||||||
|
*
|
||||||
|
* @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(float[] x, float[] 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 true iff they are equal as defined by
|
* Returns true iff they are equal as defined by
|
||||||
* {@link #equals(double,double,int) this method}.
|
* {@link #equals(double,double,int) this method}.
|
||||||
|
@ -420,9 +597,9 @@ public final class MathUtils {
|
||||||
* equalsIncludingNaN}.
|
* equalsIncludingNaN}.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static boolean equals(double x, double y) {
|
public static boolean equals(double x, double y) {
|
||||||
return (Double.isNaN(x) && Double.isNaN(y)) || x == y;
|
return (Double.isNaN(x) && Double.isNaN(y)) || x == y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if both arguments are NaN or neither is NaN and they are
|
* Returns true if both arguments are NaN or neither is NaN and they are
|
||||||
|
|
|
@ -52,6 +52,10 @@ The <action> type attribute can be add,update,fix,remove.
|
||||||
If the output is not quite correct, check for invisible trailing spaces!
|
If the output is not quite correct, check for invisible trailing spaces!
|
||||||
-->
|
-->
|
||||||
<release version="2.2" date="TBD" description="TBD">
|
<release version="2.2" date="TBD" description="TBD">
|
||||||
|
<action dev="sebb" type="fix" issue="MATH-491">
|
||||||
|
MATH-491 MathUtils.equals(double, double) does not work properly for floats
|
||||||
|
- add equivalent (float, float) methods and basic tests
|
||||||
|
</action>
|
||||||
<action dev="sebb" type="fix" issue="MATH-482">
|
<action dev="sebb" type="fix" issue="MATH-482">
|
||||||
FastMath.max(50.0f, -50.0f) => -50.0f; should be +50.0f
|
FastMath.max(50.0f, -50.0f) => -50.0f; should be +50.0f
|
||||||
Fixed FastMath.max(float, float) so it returns correct value.
|
Fixed FastMath.max(float, float) so it returns correct value.
|
||||||
|
|
|
@ -359,6 +359,40 @@ public final class MathUtilsTest extends TestCase {
|
||||||
assertFalse(MathUtils.equalsIncludingNaN(152.9374, 153.0000, .0625));
|
assertFalse(MathUtils.equalsIncludingNaN(152.9374, 153.0000, .0625));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests for floating point equality
|
||||||
|
@SuppressWarnings("deprecation") // Math.equals(float, float)
|
||||||
|
public void testFloatEqualsWithAllowedUlps() {
|
||||||
|
assertTrue("+0.0f == -0.0f",MathUtils.equals(0.0f, -0.0f));
|
||||||
|
assertTrue("+0.0f == -0.0f (1 ulp)",MathUtils.equals(0.0f, -0.0f, 1));
|
||||||
|
float oneFloat = 1.0f;
|
||||||
|
assertTrue("1.0f == 1.0f + 1 ulp",MathUtils.equals(oneFloat, Float.intBitsToFloat(1 + Float.floatToIntBits(oneFloat))));
|
||||||
|
assertTrue("1.0f == 1.0f + 1 ulp (1 ulp)",MathUtils.equals(oneFloat, Float.intBitsToFloat(1 + Float.floatToIntBits(oneFloat)), 1));
|
||||||
|
assertFalse("1.0f != 1.0f + 2 ulp (1 ulp)",MathUtils.equals(oneFloat, Float.intBitsToFloat(2 + Float.floatToIntBits(oneFloat)), 1));
|
||||||
|
|
||||||
|
assertTrue(MathUtils.equals(153.0f, 153.0f, 1));
|
||||||
|
|
||||||
|
// These tests need adjusting for floating point precision
|
||||||
|
// assertTrue(MathUtils.equals(153.0f, 153.00000000000003f, 1));
|
||||||
|
// assertFalse(MathUtils.equals(153.0f, 153.00000000000006f, 1));
|
||||||
|
// assertTrue(MathUtils.equals(153.0f, 152.99999999999997f, 1));
|
||||||
|
// assertFalse(MathUtils.equals(153f, 152.99999999999994f, 1));
|
||||||
|
//
|
||||||
|
// assertTrue(MathUtils.equals(-128.0f, -127.99999999999999f, 1));
|
||||||
|
// assertFalse(MathUtils.equals(-128.0f, -127.99999999999997f, 1));
|
||||||
|
// assertTrue(MathUtils.equals(-128.0f, -128.00000000000003f, 1));
|
||||||
|
// assertFalse(MathUtils.equals(-128.0f, -128.00000000000006f, 1));
|
||||||
|
|
||||||
|
assertTrue(MathUtils.equals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, 1));
|
||||||
|
assertTrue(MathUtils.equals(Double.MAX_VALUE, Float.POSITIVE_INFINITY, 1));
|
||||||
|
|
||||||
|
assertTrue(MathUtils.equals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, 1));
|
||||||
|
assertTrue(MathUtils.equals(-Float.MAX_VALUE, Float.NEGATIVE_INFINITY, 1));
|
||||||
|
|
||||||
|
assertFalse(MathUtils.equals(Float.NaN, Float.NaN, 1));
|
||||||
|
|
||||||
|
assertFalse(MathUtils.equals(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, 100000));
|
||||||
|
}
|
||||||
|
|
||||||
public void testEqualsWithAllowedUlps() {
|
public void testEqualsWithAllowedUlps() {
|
||||||
assertTrue(MathUtils.equals(0.0, -0.0, 1));
|
assertTrue(MathUtils.equals(0.0, -0.0, 1));
|
||||||
|
|
||||||
|
@ -435,6 +469,7 @@ public final class MathUtilsTest extends TestCase {
|
||||||
assertFalse(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000));
|
assertFalse(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // specific tests of deprecated methods
|
||||||
public void testArrayEquals() {
|
public void testArrayEquals() {
|
||||||
assertFalse(MathUtils.equals(new double[] { 1d }, null));
|
assertFalse(MathUtils.equals(new double[] { 1d }, null));
|
||||||
assertFalse(MathUtils.equals(null, new double[] { 1d }));
|
assertFalse(MathUtils.equals(null, new double[] { 1d }));
|
||||||
|
@ -476,6 +511,26 @@ public final class MathUtilsTest extends TestCase {
|
||||||
new double[] { FastMath.nextAfter(FastMath.nextAfter(1d, 2d), 2d) }));
|
new double[] { FastMath.nextAfter(FastMath.nextAfter(1d, 2d), 2d) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testFloatArrayEqualsIncludingNaN() {
|
||||||
|
assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f }, null));
|
||||||
|
assertFalse(MathUtils.equalsIncludingNaN(null, new float[] { 1f }));
|
||||||
|
assertTrue(MathUtils.equalsIncludingNaN((float[]) null, (float[]) null));
|
||||||
|
|
||||||
|
assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f }, new float[0]));
|
||||||
|
assertTrue(MathUtils.equalsIncludingNaN(new float[] { 1f }, new float[] { 1f }));
|
||||||
|
assertTrue(MathUtils.equalsIncludingNaN(new float[] {
|
||||||
|
Float.NaN, Float.POSITIVE_INFINITY,
|
||||||
|
Float.NEGATIVE_INFINITY, 1f, 0f
|
||||||
|
}, new float[] {
|
||||||
|
Float.NaN, Float.POSITIVE_INFINITY,
|
||||||
|
Float.NEGATIVE_INFINITY, 1f, 0f
|
||||||
|
}));
|
||||||
|
assertFalse(MathUtils.equalsIncludingNaN(new float[] { Float.POSITIVE_INFINITY },
|
||||||
|
new float[] { Float.NEGATIVE_INFINITY }));
|
||||||
|
// assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f },
|
||||||
|
// new float[] { FastMath.nextAfter(FastMath.nextAfter(1f, 2f), 2f) }));
|
||||||
|
}
|
||||||
|
|
||||||
public void testFactorial() {
|
public void testFactorial() {
|
||||||
for (int i = 1; i < 21; i++) {
|
for (int i = 1; i < 21; i++) {
|
||||||
assertEquals(i + "! ", factorial(i), MathUtils.factorial(i));
|
assertEquals(i + "! ", factorial(i), MathUtils.factorial(i));
|
||||||
|
|
Loading…
Reference in New Issue