Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/main/java/org/embulk/spi/json/JsonDouble.java
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,11 @@ public String toString() {
* <p>Note that such {@code NaN} instances were usually considered NOT to be equal in the Embulk SPI v0.11 and earlier.
* They were equal only when they were created from the same {@link org.msgpack.value.ImmutableFloatValue} instance.
*
* <p>Two {@link JsonDouble} instances that have {@code 0.0} and {@code -0.0} are NOT considered to be equal by
* {@link #equals(Object)} since the Embulk SPI v0.12, in the same manner as {@link Double#equals(Object)}.
*
* <p>Such instances of {@code 0.0} and {@code -0.0} were considered to be equal in the Embulk SPI v0.11 and earlier.
*
* <p>Two different {@link JsonDouble} instances that have the infinity are considered to be equal by {@link #equals(Object)}
* when both have the positive infinity, or when both have the negative infinity. A combination of the positive infinity
* and the negative infinity is considered not to be equal.
Expand All @@ -557,8 +562,13 @@ public boolean equals(final Object otherObject) {
if (otherObject instanceof JsonDouble) {
final JsonDouble other = (JsonDouble) otherObject;

// NaN is considered to be equal to NaN in the same manner as Double#equals, while NaN != NaN as primitive double.
return this.value == other.value || (Double.isNaN(this.value) && Double.isNaN(other.value));
// It compares the bit patterns in the same manner as Double#equals, not by == of primitive double.
//
// * NaN is considered to be equal to NaN, while NaN != NaN as primitive double.
// * 0.0 and -0.0 are considered NOT to be equal, while 0.0 == -0.0 as primitive double.
//
// Double#doubleToLongBits returns the same bit pattern for any NaN. It is consistent with #hashCode.
return Double.doubleToLongBits(this.value) == Double.doubleToLongBits(other.value);
}

// JsonDouble is never equal to JsonLong even if they represent the same integral number.
Expand Down
74 changes: 72 additions & 2 deletions src/test/java/org/embulk/spi/json/TestJsonDouble.java
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public void testZero() {
assertEquals("0.0", jsonDouble.toJson());
assertEquals("0.0", jsonDouble.toString());
assertEquals(JsonDouble.of(0.0), jsonDouble);
assertEquals(JsonDouble.of(-0.0), jsonDouble);
assertNotEquals(JsonDouble.of(-0.0), jsonDouble);
assertNotEquals(JsonDouble.of(0.000001), jsonDouble);
assertNotEquals(JsonDouble.of(-0.000001), jsonDouble);
assertNotEquals(JsonLong.of(0L), jsonDouble);
Expand Down Expand Up @@ -250,7 +250,7 @@ public void testNegativeZero() {

assertEquals("-0.0", jsonDouble.toJson());
assertEquals("-0.0", jsonDouble.toString());
assertEquals(JsonDouble.of(0.0), jsonDouble);
assertNotEquals(JsonDouble.of(0.0), jsonDouble);
assertEquals(JsonDouble.of(-0.0), jsonDouble);
assertNotEquals(JsonDouble.of(0.000001), jsonDouble);
assertNotEquals(JsonDouble.of(-0.000001), jsonDouble);
Expand Down Expand Up @@ -974,6 +974,76 @@ public void testMinimunPositiveDecimal() {
assertFalse(jsonDouble.equals(FakeJsonDouble.of(jsonDouble.doubleValue())));
}

@Test
public void testEqualityOfZeros() {
// 0.0 and -0.0 are not equal in the same manner as Double#equals.
// Note that they were equal in the Embulk SPI v0.11 and earlier.
final JsonDouble positiveZero = JsonDouble.of(0.0);
final JsonDouble negativeZero = JsonDouble.of(-0.0);
assertFalse(positiveZero.equals(negativeZero));
assertFalse(negativeZero.equals(positiveZero));
assertFalse(Double.valueOf(0.0).equals(Double.valueOf(-0.0)));

assertTrue(positiveZero.equals(JsonDouble.of(0.0)));
assertEquals(positiveZero.hashCode(), JsonDouble.of(0.0).hashCode());
assertTrue(negativeZero.equals(JsonDouble.of(-0.0)));
assertEquals(negativeZero.hashCode(), JsonDouble.of(-0.0).hashCode());

// They are still equal as numbers by JsonNumber#doubleValue.
assertTrue(positiveZero.doubleValue() == negativeZero.doubleValue());

// Their JSON representations are different.
assertEquals("0.0", positiveZero.toJson());
assertEquals("-0.0", negativeZero.toJson());

// It does not matter how they are created.
assertFalse(JsonValue.fromMsgpack(ValueFactory.newFloat(0.0)).equals(JsonValue.fromMsgpack(ValueFactory.newFloat(-0.0))));
assertFalse(JsonDouble.withLiteral(0.0, "0").equals(JsonDouble.withLiteral(-0.0, "0")));

// JsonArray and JsonObject are not equal when they contain 0.0 and -0.0 at the same place.
assertFalse(JsonArray.of(positiveZero).equals(JsonArray.of(negativeZero)));
assertFalse(JsonArray.of(negativeZero).equals(JsonArray.of(positiveZero)));
assertFalse(JsonObject.of("key", positiveZero).equals(JsonObject.of("key", negativeZero)));
assertFalse(JsonObject.of("key", negativeZero).equals(JsonObject.of("key", positiveZero)));
}

@Test
public void testEqualsAndHashCode() {
final double[] values = {
0.0,
-0.0,
1.0,
-1.0,
0.5,
12.41041,
1234567890.123456,
-1234567890.123456,
Double.MIN_VALUE,
-Double.MIN_VALUE,
Double.MAX_VALUE,
-Double.MAX_VALUE,
Double.NaN,
Double.longBitsToDouble(0x7ff8000000000001L), // NaN in a different bit pattern
Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY,
};
for (final double left : values) {
for (final double right : values) {
// A JsonDouble instance can have NaN or the infinity when it is created through JsonValue#fromMsgpack.
final JsonValue jsonLeft = JsonValue.fromMsgpack(ValueFactory.newFloat(left));
final JsonValue jsonRight = JsonValue.fromMsgpack(ValueFactory.newFloat(right));

// JsonDouble#equals is in the same manner as Double#equals.
assertEquals(Double.valueOf(left).equals(Double.valueOf(right)), jsonLeft.equals(jsonRight));

// Equal instances must have the same hash code.
if (jsonLeft.equals(jsonRight)) {
assertEquals(jsonLeft.hashCode(), jsonRight.hashCode());
}
}
}
}

@Test
public void testEqualityWithJsonLong() {
// JsonDouble and JsonLong are never equal even if they represent the same integral number.
Expand Down
Loading