|
| 1 | +package jdk.incubator.java.util.json; |
| 2 | + |
| 3 | +import org.junit.jupiter.api.Test; |
| 4 | + |
| 5 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 6 | +import static org.junit.jupiter.api.Assertions.assertNotEquals; |
| 7 | +import static org.junit.jupiter.api.Assertions.assertThrows; |
| 8 | + |
| 9 | +/// Evidence matrix for issue #118 (`JsonNumber.of(double)` decimal/exponent |
| 10 | +/// offsets). The historic local fix delegated `of(double)` to `of(String)`; |
| 11 | +/// upstream has since reworked the numeric logic to compute the offsets via |
| 12 | +/// `indexOf`. These tests prove the uplifted upstream implementation handles |
| 13 | +/// the full matrix that motivated #118, making the carried fix unnecessary. |
| 14 | +public class JsonNumberOfDoubleMatrixTest extends JsonTestLoggingConfig { |
| 15 | + |
| 16 | + private static void assertIntegralDouble(double d, long expected) { |
| 17 | + var jn = JsonNumber.of(d); |
| 18 | + // of(double) must be equivalent to the historic of(String) delegation |
| 19 | + var viaString = JsonNumber.of(Double.toString(d)); |
| 20 | + assertEquals(viaString.toString(), jn.toString(), "toString for " + d); |
| 21 | + assertEquals(expected, jn.asLong(), "asLong for " + d); |
| 22 | + assertEquals((int) expected, jn.asInt(), "asInt for " + d); |
| 23 | + assertEquals(d, jn.asDouble(), "asDouble for " + d); |
| 24 | + } |
| 25 | + |
| 26 | + @Test |
| 27 | + void integralDoublesConvertExactly() { |
| 28 | + assertIntegralDouble(123.0, 123L); |
| 29 | + assertIntegralDouble(1.0E2, 100L); |
| 30 | + assertIntegralDouble(42.0, 42L); |
| 31 | + assertIntegralDouble(420e-1, 42L); |
| 32 | + assertIntegralDouble(42e6, 42_000_000L); |
| 33 | + assertIntegralDouble(0.0, 0L); |
| 34 | + assertIntegralDouble(1.0, 1L); |
| 35 | + assertIntegralDouble(5.000, 5L); |
| 36 | + } |
| 37 | + |
| 38 | + @Test |
| 39 | + void negativeIntegralDoublesConvertExactly() { |
| 40 | + assertIntegralDouble(-123.0, -123L); |
| 41 | + assertIntegralDouble(-42e6, -42_000_000L); |
| 42 | + assertIntegralDouble(-1.0, -1L); |
| 43 | + } |
| 44 | + |
| 45 | + @Test |
| 46 | + void negativeZeroBehaves() { |
| 47 | + var jn = JsonNumber.of(-0.0); |
| 48 | + assertEquals("-0.0", jn.toString()); |
| 49 | + assertEquals(0L, jn.asLong()); |
| 50 | + assertEquals(0, jn.asInt()); |
| 51 | + assertEquals(-0.0, jn.asDouble()); |
| 52 | + assertEquals(0.0, jn.asDouble() + 0.0); |
| 53 | + } |
| 54 | + |
| 55 | + @Test |
| 56 | + void fractionalDoublesAreNotIntegral() { |
| 57 | + assertNotIntegral(123.45); |
| 58 | + assertNotIntegral(0.1); |
| 59 | + assertNotIntegral(0.002); |
| 60 | + assertNotIntegral(-123.45); |
| 61 | + } |
| 62 | + |
| 63 | + private static void assertNotIntegral(double d) { |
| 64 | + var jn = JsonNumber.of(d); |
| 65 | + assertThrows(JsonValueException.class, jn::asLong, "asLong for " + d); |
| 66 | + assertThrows(JsonValueException.class, jn::asInt, "asInt for " + d); |
| 67 | + assertEquals(d, jn.asDouble(), "asDouble for " + d); |
| 68 | + // of(double) must match the of(String) representation exactly |
| 69 | + assertEquals(JsonNumber.of(Double.toString(d)).toString(), jn.toString()); |
| 70 | + } |
| 71 | + |
| 72 | + @Test |
| 73 | + void outOfRangeIntegralThrowsJsonValueException() { |
| 74 | + assertNotConvertibleToLong(1e300); |
| 75 | + assertNotConvertibleToLong(-1e300); |
| 76 | + assertNotConvertibleToLong(Double.MAX_VALUE); |
| 77 | + assertNotConvertibleToLong(9.3e18); |
| 78 | + } |
| 79 | + |
| 80 | + private static void assertNotConvertibleToLong(double d) { |
| 81 | + var jn = JsonNumber.of(d); |
| 82 | + assertThrows(JsonValueException.class, jn::asLong, "asLong for " + d); |
| 83 | + assertThrows(JsonValueException.class, jn::asInt, "asInt for " + d); |
| 84 | + assertEquals(d, jn.asDouble(), "asDouble for " + d); |
| 85 | + } |
| 86 | + |
| 87 | + @Test |
| 88 | + void tinyMagnitudesRoundTripAsDouble() { |
| 89 | + var tiny = JsonNumber.of(4.9E-324); |
| 90 | + assertEquals(4.9E-324, tiny.asDouble()); |
| 91 | + assertThrows(JsonValueException.class, tiny::asLong); |
| 92 | + var small = JsonNumber.of(5e-100); |
| 93 | + assertEquals(5e-100, small.asDouble()); |
| 94 | + assertThrows(JsonValueException.class, small::asLong); |
| 95 | + } |
| 96 | + |
| 97 | + @Test |
| 98 | + void ofDoubleMatchesParsedEquivalent() { |
| 99 | + // of(double) goes through Double.toString; parsing that same text must |
| 100 | + // produce the same conversions (the equivalence #118 was about) |
| 101 | + for (double d : new double[]{123.0, 1.0E2, 42e6, 0.0, -0.0, 123.45, 1e300, 4.9E-324}) { |
| 102 | + var factory = JsonNumber.of(d); |
| 103 | + var parsed = Json.parse(Double.toString(d)); |
| 104 | + assertNotEquals(parsed, factory); // identity semantics; compare behaviour |
| 105 | + assertEquals(factory.toString(), parsed.toString(), "toString for " + d); |
| 106 | + assertEquals(factory.asDouble(), ((JsonNumber) parsed).asDouble(), "asDouble for " + d); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + @Test |
| 111 | + void nonFiniteDoublesRejected() { |
| 112 | + assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.NaN)); |
| 113 | + assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.POSITIVE_INFINITY)); |
| 114 | + assertThrows(IllegalArgumentException.class, () -> JsonNumber.of(Double.NEGATIVE_INFINITY)); |
| 115 | + } |
| 116 | +} |
0 commit comments