From a4b1948b7cb476d17d38b324fe46a60be4104490 Mon Sep 17 00:00:00 2001 From: Sebastian Bazley Date: Thu, 13 Jan 2011 02:57:57 +0000 Subject: [PATCH] MATH-471 MathUtils.equals(double, double) does not work properly for floats - add equivalent (float, float) methods and basic tests git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1058397 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/commons/math/util/MathUtils.java | 157 ++++++++++++++++++ .../commons/math/util/MathUtilsTest.java | 33 ++++ 2 files changed, 190 insertions(+) diff --git a/src/main/java/org/apache/commons/math/util/MathUtils.java b/src/main/java/org/apache/commons/math/util/MathUtils.java index bd6852389..f015cdb70 100644 --- a/src/main/java/org/apache/commons/math/util/MathUtils.java +++ b/src/main/java/org/apache/commons/math/util/MathUtils.java @@ -82,6 +82,9 @@ public final class MathUtils { /** Offset to order signed double numbers lexicographically. */ 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 */ private static final long[] FACTORIALS = new long[] { 1l, 1l, 2l, @@ -414,6 +417,160 @@ public final class MathUtils { return (FastMath.exp(x) + FastMath.exp(-x)) / 2.0; } + /** + * Returns true iff they are equal as defined by + * {@link #equals(float,float,int) equals(x, y, 1)}. + * + * @param x first value + * @param y second value + * @return {@code true} if the values are equal. + */ + public static boolean equals(float x, float y) { + return equals(x, y, 1); + } + + /** + * Returns true if both arguments are NaN or neither is NaN and they are + * equal as defined by {@link #equals(float,float) this method}. + * + * @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 + * Bruce Dawson + * + * @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) 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(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) 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 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(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(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 * {@link #equals(double,double,int) equals(x, y, 1)}. diff --git a/src/test/java/org/apache/commons/math/util/MathUtilsTest.java b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java index 847b0d74f..fe1f3411a 100644 --- a/src/test/java/org/apache/commons/math/util/MathUtilsTest.java +++ b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java @@ -363,6 +363,39 @@ public final class MathUtilsTest extends TestCase { assertFalse(MathUtils.equalsIncludingNaN(152.9374, 153.0000, .0625)); } + // Tests for floating point equality + 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() { assertTrue(MathUtils.equals(0.0, -0.0, 1));