Skip to content

Commit

Permalink
Implement java.lang.Math.nextAfter, nextUp, nextDown (#9978)
Browse files Browse the repository at this point in the history
Fixes #9977
  • Loading branch information
niloc132 authored Jul 26, 2024
1 parent a78f58f commit 1ef74df
Show file tree
Hide file tree
Showing 2 changed files with 307 additions and 10 deletions.
77 changes: 69 additions & 8 deletions user/super/com/google/gwt/emul/java/lang/Math.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ public final class Math {
// public static int getExponent (double d)
// public static int getExponent (float f)
// public static double IEEEremainder(double f1, double f2)
// public static double nextAfter(double start, double direction)
// public static float nextAfter(float start, float direction)
// public static double nextUp(double start) {
// return nextAfter(start, 1.0d);
// }
// public static float nextUp(float start) {
// return nextAfter(start,1.0f);
// }

public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
Expand Down Expand Up @@ -344,6 +336,75 @@ public static double toRadians(double x) {
return x * PI_OVER_180;
}

public static double nextAfter(double start, double direction) {
// Simple case described by Javadoc:
if (start == direction) {
return direction;
}

// NaN special case, if either is NaN, return NaN.
if (Double.isNaN(start) || Double.isNaN(direction)) {
return Double.NaN;
}

// The javadoc 'special cases' for infinities and min_value are handled already by manipulating
// the bits of the start value below. However, that approach used below doesn't work around
// zeros - we have two zero values to deal with (positive and negative) with very different bit
// representations (zero and Long.MIN_VALUE respectively).
if (start == 0) {
return direction > start ? Double.MIN_VALUE : -Double.MIN_VALUE;
}

// Convert to int bits and increment or decrement - the fact that two positive ieee754 float
// values can be compared as ints (or two negative values, with the comparison inverted) means
// that this trick works as naturally as A + 1 > A. NaNs and zeros were already handled above.
long bits = Double.doubleToLongBits(start);
bits += (direction > start) == (bits >= 0) ? 1 : -1;
return Double.longBitsToDouble(bits);
}

public static float nextAfter(float start, double direction) {
// Simple case described by Javadoc:
if (start == direction) {
return (float) direction;
}

// NaN special case, if either is NaN, return NaN.
if (Float.isNaN(start) || Double.isNaN(direction)) {
return Float.NaN;
}
// The javadoc 'special cases' for INFINITYs, MIN_VALUE, and MAX_VALUE are handled already by
// manipulating the bits of the start value below. However, that approach used below doesn't
// work around zeros - we have two zero values to deal with (positive and negative) with very
// different bit representations (zero and Integer.MIN_VALUE respectively).
if (start == 0) {
return direction > start ? Float.MIN_VALUE : -Float.MIN_VALUE;
}

// Convert to int bits and increment or decrement - the fact that two positive ieee754 float
// values can be compared as ints (or two negative values, with the comparison inverted) means
// that this trick works as naturally as A + 1 > A. NaNs and zeros were already handled above.
int bits = Float.floatToIntBits(start);
bits += (direction > start) == (bits >= 0) ? 1 : -1;
return Float.intBitsToFloat(bits);
}

public static double nextUp(double start) {
return nextAfter(start, Double.POSITIVE_INFINITY);
}

public static float nextUp(float start) {
return nextAfter(start, Double.POSITIVE_INFINITY);
}

public static double nextDown(double start) {
return nextAfter(start, Double.NEGATIVE_INFINITY);
}

public static float nextDown(float start) {
return nextAfter(start, Double.NEGATIVE_INFINITY);
}

private static boolean isSafeIntegerRange(double value) {
return Integer.MIN_VALUE <= value && value <= Integer.MAX_VALUE;
}
Expand Down
240 changes: 238 additions & 2 deletions user/test/com/google/gwt/emultest/java/lang/MathTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
public class MathTest extends GWTTestCase {

private static void assertNegativeZero(double x) {
assertTrue(isNegativeZero(x));
assertEquals(0.0, x);
assertTrue(String.valueOf(x), isNegativeZero(x));
}

private static void assertPositiveZero(double x) {
assertEquals(0.0, x);
assertFalse(isNegativeZero(x));
assertFalse(String.valueOf(x), isNegativeZero(x));
}

private static void assertNaN(double x) {
Expand Down Expand Up @@ -633,4 +634,239 @@ public void testScalb() {
assertEquals(4294967296.0f, Math.scalb(1f, 32));
assertEquals(2.3283064e-10f, Math.scalb(1f, -32), 1e-7f);
}

public void testNextAfterFloat() {
// Test the five "special cases" described by the Javadoc, with both Float and Double
// "direction" values.
assertNaN(Math.nextAfter(Float.NaN, Float.NaN));
assertNaN(Math.nextAfter(Float.NaN, Double.NaN));
assertNaN(Math.nextAfter(Float.NaN, 0));
assertNaN(Math.nextAfter(0, Float.NaN));
assertNaN(Math.nextAfter(0, Double.NaN));

assertNegativeZero(Math.nextAfter(0.0f, -0.0f));
assertNegativeZero(Math.nextAfter(0.0f, -0.0d));
assertNegativeZero(Math.nextAfter(-0.0f, -0.0f));
assertNegativeZero(Math.nextAfter(-0.0f, -0.0d));
assertPositiveZero(Math.nextAfter(0.0f, 0.0f));
assertPositiveZero(Math.nextAfter(0.0f, 0.0d));
assertPositiveZero(Math.nextAfter(-0.0f, 0.0f));
assertPositiveZero(Math.nextAfter(-0.0f, 0.0d));

assertNegativeZero(Math.nextAfter(-Float.MIN_VALUE, 1));
assertPositiveZero(Math.nextAfter(Float.MIN_VALUE, -1));

assertEquals(Float.MAX_VALUE, Math.nextAfter(Float.POSITIVE_INFINITY, -1));
assertEquals(Float.MAX_VALUE,
Math.nextAfter(Float.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY));
assertEquals(Float.MAX_VALUE,
Math.nextAfter(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY));
assertEquals(-Float.MAX_VALUE,
Math.nextAfter(Float.NEGATIVE_INFINITY, 1));
assertEquals(-Float.MAX_VALUE,
Math.nextAfter(Float.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
assertEquals(-Float.MAX_VALUE,
Math.nextAfter(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY));

assertEquals(Float.POSITIVE_INFINITY,
Math.nextAfter(Float.MAX_VALUE, Float.POSITIVE_INFINITY));
assertEquals(Float.POSITIVE_INFINITY,
Math.nextAfter(Float.MAX_VALUE, Double.POSITIVE_INFINITY));
assertEquals(Float.NEGATIVE_INFINITY,
Math.nextAfter(-Float.MAX_VALUE, Float.NEGATIVE_INFINITY));
assertEquals(Float.NEGATIVE_INFINITY,
Math.nextAfter(-Float.MAX_VALUE, Double.NEGATIVE_INFINITY));

// General rules: if values compare as equal, return "direction" (exceptions covered above)
assertEquals(Float.POSITIVE_INFINITY,
Math.nextAfter(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));
assertEquals(Float.POSITIVE_INFINITY,
Math.nextAfter(Float.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
assertEquals(Float.NEGATIVE_INFINITY,
Math.nextAfter(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));
assertEquals(Float.NEGATIVE_INFINITY,
Math.nextAfter(Float.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
assertEquals(Float.MAX_VALUE, Math.nextAfter(Float.MAX_VALUE, Float.MAX_VALUE));

// Return number adjacent to "start" in the relative direction of "direction". Using hex to
// easily see bit patterns in the sample data.
assertEquals(0x1.fffffcp127f, Math.nextAfter(Float.MAX_VALUE, 0));
assertEquals(0x1.fffffcp127f,
Math.nextAfter(Float.MAX_VALUE, Float.NEGATIVE_INFINITY));
assertEquals(-0x1.fffffcp127f, Math.nextAfter(-Float.MAX_VALUE, 0));
assertEquals(-0x1.fffffcp127f,
Math.nextAfter(-Float.MAX_VALUE, Float.POSITIVE_INFINITY));
assertEquals(0x1.fffffep124f, Math.nextAfter(0x1.0p125f, 0));
assertEquals(0x1.0p125f,
Math.nextAfter(0x1.fffffep124f, Float.POSITIVE_INFINITY));

// Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative
assertEquals(Float.MIN_VALUE, Math.nextAfter(0.0f, 1));
assertEquals(Float.MIN_VALUE, Math.nextAfter(-0.0f, 1));
assertEquals(-Float.MIN_VALUE, Math.nextAfter(0.0f, -1));
assertEquals(-Float.MIN_VALUE, Math.nextAfter(-0.0f, -1));

// Test near 1, where exponent sign flips positive/negative
assertEquals(0x1.000002p0f, Math.nextAfter(1.0f, 2));
assertEquals(0x1.fffffep-1f, Math.nextAfter(1.0f, 0));
assertEquals(1.0f, Math.nextAfter(0x1.fffffep-1f, 2));
assertEquals(1.0f, Math.nextAfter(0x1.000002p0f, 0));

// Repeat near -1
assertEquals(-0x1.000002p0f, Math.nextAfter(-1.0f, -2));
assertEquals(-0x1.fffffep-1f, Math.nextAfter(-1.0f, 0));
assertEquals(-1.0f, Math.nextAfter(-0x1.fffffep-1f, -2));
assertEquals(-1.0f, Math.nextAfter(-0x1.000002p0f, 0));
}

public void testNextUpFloat() {
// Special cases from javadoc
assertNaN(Math.nextUp(Float.NaN));
assertEquals(Float.POSITIVE_INFINITY, Math.nextUp(Float.POSITIVE_INFINITY));
assertEquals(Float.MIN_VALUE, Math.nextUp(0.0f));
assertEquals(Float.MIN_VALUE, Math.nextUp(-0.0f));

assertEquals(Float.POSITIVE_INFINITY, Math.nextUp(Float.MAX_VALUE));
assertEquals(-Float.MAX_VALUE, Math.nextUp(Float.NEGATIVE_INFINITY));

assertNegativeZero(Math.nextUp(-Float.MIN_VALUE));

assertEquals(0x1.0p2f, Math.nextUp(0x1.fffffep1f));
assertEquals(0x1.000002p2f, Math.nextUp(0x1.0p2f));
}

public void testNextDownFloat() {
// Special cases from javadoc
assertNaN(Math.nextDown(Float.NaN));
assertEquals(Float.NEGATIVE_INFINITY, Math.nextDown(Float.NEGATIVE_INFINITY));
assertEquals(-Float.MIN_VALUE, Math.nextDown(0.0f));
assertEquals(-Float.MIN_VALUE, Math.nextDown(-0.0f));

assertEquals(Float.NEGATIVE_INFINITY, Math.nextDown(-Float.MAX_VALUE));
assertEquals(Float.MAX_VALUE, Math.nextDown(Float.POSITIVE_INFINITY));

assertPositiveZero(Math.nextDown(Float.MIN_VALUE));

assertEquals(0x1.fffffep1f, Math.nextDown(0x1.0p2f));
assertEquals(0x1.fffffcp1f, Math.nextDown(0x1.fffffep1f));
}

public void testNextAfterDouble() {
// Test the five "special cases" described by the Javadoc
assertNaN(Math.nextAfter(Double.NaN, Double.NaN));
assertNaN(Math.nextAfter(Double.NaN, 0));
assertNaN(Math.nextAfter(0d, Double.NaN));

assertNegativeZero(Math.nextAfter(0.0d, -0.0d));
assertNegativeZero(Math.nextAfter(-0.0d, -0.0d));
assertPositiveZero(Math.nextAfter(0.0d, 0.0d));
assertPositiveZero(Math.nextAfter(-0.0d, 0.0d));

assertNegativeZero(Math.nextAfter(-Double.MIN_VALUE, 1));
assertPositiveZero(Math.nextAfter(Double.MIN_VALUE, -1));

assertEquals(Double.MAX_VALUE, Math.nextAfter(Double.POSITIVE_INFINITY, -1));
assertEquals(Double.MAX_VALUE,
Math.nextAfter(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY));
assertEquals(-Double.MAX_VALUE, Math.nextAfter(Double.NEGATIVE_INFINITY, 1));
assertEquals(-Double.MAX_VALUE,
Math.nextAfter(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));

assertEquals(Double.POSITIVE_INFINITY,
Math.nextAfter(Double.MAX_VALUE, Double.POSITIVE_INFINITY));
assertEquals(Double.NEGATIVE_INFINITY,
Math.nextAfter(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY));

// General rules: if values compare as equal, return "direction" (exceptions covered above)
assertEquals(Double.POSITIVE_INFINITY,
Math.nextAfter(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
assertEquals(Double.NEGATIVE_INFINITY,
Math.nextAfter(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
assertEquals(Double.MAX_VALUE, Math.nextAfter(Double.MAX_VALUE, Double.MAX_VALUE));

// Return number adjacent to "start" in the relative direction of "direction". Using hex to
// easily see bit patterns in the sample data.
assertEquals(0x1.ffffffffffffep1023, Math.nextAfter(Double.MAX_VALUE, 0));
assertEquals(0x1.ffffffffffffep1023,
Math.nextAfter(Double.MAX_VALUE, Double.NEGATIVE_INFINITY));
assertEquals(-0x1.ffffffffffffep1023, Math.nextAfter(-Double.MAX_VALUE, 0));
assertEquals(-0x1.ffffffffffffep1023,
Math.nextAfter(-Double.MAX_VALUE, Double.POSITIVE_INFINITY));
assertEquals(0x1.fffffffffffffp124, Math.nextAfter(0x1.0p125, 0));
assertEquals(0x1.0p125, Math.nextAfter(0x1.fffffffffffffp124, Double.POSITIVE_INFINITY));

// Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative
assertEquals(Double.MIN_VALUE, Math.nextAfter(0.0d, 1));
assertEquals(Double.MIN_VALUE, Math.nextAfter(-0.0d, 1));
assertEquals(-Double.MIN_VALUE, Math.nextAfter(0.0d, -1));
assertEquals(-Double.MIN_VALUE, Math.nextAfter(-0.0d, -1));

// Test near 1, where exponent sign flips positive/negative
assertEquals(0x1.0000000000001p0d, Math.nextAfter(1.0d, 2));
assertEquals(0x1.fffffffffffffp-1d, Math.nextAfter(1.0d, 0));
assertEquals(1.0d, Math.nextAfter(0x1.fffffffffffffp-1d, 2));
assertEquals(1.0d, Math.nextAfter(0x1.0000000000001p0d, 0));

// Repeat near -1
assertEquals(-0x1.0000000000001p0d, Math.nextAfter(-1.0d, -2));
assertEquals(-0x1.fffffffffffffp-1d, Math.nextAfter(-1.0d, 0));
assertEquals(-1.0d, Math.nextAfter(-0x1.fffffffffffffp-1d, -2));
assertEquals(-1.0d, Math.nextAfter(-0x1.0000000000001p0d, 0));
}

public void testNextUpDouble() {
// Special cases from javadoc
assertNaN(Math.nextUp(Double.NaN));
assertEquals(Double.POSITIVE_INFINITY, Math.nextUp(Double.POSITIVE_INFINITY));
assertEquals(Double.MIN_VALUE, Math.nextUp(0.0));
assertEquals(Double.MIN_VALUE, Math.nextUp(-0.0));

assertEquals(Double.POSITIVE_INFINITY, Math.nextUp(Double.MAX_VALUE));
assertEquals(-Double.MAX_VALUE, Math.nextUp(Double.NEGATIVE_INFINITY));

assertNegativeZero(Math.nextUp(-Double.MIN_VALUE));

assertEquals(0x1.0p2d, Math.nextUp(0x1.fffffffffffffp1d));
assertEquals(0x1.0000000000001p2d, Math.nextUp(0x1.0p2d));

// Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative
assertEquals(Double.MIN_VALUE, Math.nextUp(0.0d));
assertEquals(Double.MIN_VALUE, Math.nextUp(-0.0d));

// Test near 1, where exponent sign flips positive/negative
assertEquals(0x1.0000000000001p0d, Math.nextUp(1.0d));
assertEquals(1.0d, Math.nextUp(0x1.fffffffffffffp-1d));

// Repeat near -1
assertEquals(-0x1.fffffffffffffp-1d, Math.nextUp(-1.0d));
assertEquals(-1.0d, Math.nextUp(-0x1.0000000000001p0d));
}

public void testNextDownDouble() {
// Special cases from javadoc
assertNaN(Math.nextDown(Double.NaN));
assertEquals(Double.NEGATIVE_INFINITY, Math.nextDown(Double.NEGATIVE_INFINITY));
assertEquals(-Double.MIN_VALUE, Math.nextDown(0.0d));
assertEquals(-Double.MIN_VALUE, Math.nextDown(-0.0d));

assertEquals(Double.NEGATIVE_INFINITY, Math.nextDown(-Double.MAX_VALUE));
assertEquals(Double.MAX_VALUE, Math.nextDown(Double.POSITIVE_INFINITY));

assertPositiveZero(Math.nextDown(Double.MIN_VALUE));

assertEquals(0x1.fffffffffffffp1d, Math.nextDown(0x1.0p2d));
assertEquals(0x1.ffffffffffffep1d, Math.nextDown(0x1.fffffffffffffp1d));

// Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative
assertEquals(-Double.MIN_VALUE, Math.nextDown(0.0d));
assertEquals(-Double.MIN_VALUE, Math.nextDown(-0.0d));

// Test near 1, where exponent sign flips positive/negative
assertEquals(0x1.fffffffffffffp-1d, Math.nextDown(1.0d));
assertEquals(1.0d, Math.nextDown(0x1.0000000000001p0d));

// Repeat near -1
assertEquals(-0x1.0000000000001p0d, Math.nextDown(-1.0d));
assertEquals(-1.0d, Math.nextDown(-0x1.fffffffffffffp-1d));
}
}

0 comments on commit 1ef74df

Please sign in to comment.