Repository: google/gson (Java)
Date: 2023-04-01 16:10:29+00:00
Task type: Bug Fix

Issue:
BigDecimal equals problem
Dear developers, it looks like the primitive's equals to did not handle BigDecimal's comparison. The following test will fail:

```
  public void testUnequalDecimals() {
      JsonPrimitive small = new JsonPrimitive(1.0);
      JsonPrimitive large = new JsonPrimitive(2.0);
      assertFalse("small = large", small.equals(large));

      BigDecimal dmax = BigDecimal.valueOf(Double.MAX_VALUE);
      JsonPrimitive smallBD =        // dmax + 100.0
          new JsonPrimitive(dmax.add(new BigDecimal("100.0")));
      JsonPrimitive largeBD =        // dmax + 200.0
          new JsonPrimitive(dmax.add(new BigDecimal("200.0")));
      assertFalse("small = large", smallBD.equals(largeBD));
  }
```

Could you consider a fix for this, so it can support big decimal comparisons, too?

Thanks!



Issue discussion:

Solution patch:
diff --git a/gson/src/main/java/com/google/gson/JsonPrimitive.java b/gson/src/main/java/com/google/gson/JsonPrimitive.java
--- a/gson/src/main/java/com/google/gson/JsonPrimitive.java
+++ b/gson/src/main/java/com/google/gson/JsonPrimitive.java
@@ -287,11 +287,16 @@ public boolean equals(Object obj) {
           : this.getAsNumber().longValue() == other.getAsNumber().longValue();
     }
     if (value instanceof Number && other.value instanceof Number) {
-      double a = getAsNumber().doubleValue();
-      // Java standard types other than double return true for two NaN. So, need
-      // special handling for double.
-      double b = other.getAsNumber().doubleValue();
-      return a == b || (Double.isNaN(a) && Double.isNaN(b));
+      if (value instanceof BigDecimal && other.value instanceof BigDecimal) {
+        // Uses compareTo to ignore scale of values, e.g. `0` and `0.00` should be considered equal
+        return this.getAsBigDecimal().compareTo(other.getAsBigDecimal()) == 0;
+      }
+
+      double thisAsDouble = this.getAsDouble();
+      double otherAsDouble = other.getAsDouble();
+      // Don't use Double.compare(double, double) because that considers -0.0 and +0.0 not equal
+      return (thisAsDouble == otherAsDouble)
+          || (Double.isNaN(thisAsDouble) && Double.isNaN(otherAsDouble));
     }
     return value.equals(other.value);
   }


Test patch:
diff --git a/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java b/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java
--- a/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java
+++ b/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java
@@ -321,4 +321,35 @@ public void testDeepCopy() {
     JsonPrimitive a = new JsonPrimitive("a");
     assertThat(a).isSameInstanceAs(a.deepCopy()); // Primitives are immutable!
   }
+
+  @Test
+  public void testBigDecimalEquals() {
+    JsonPrimitive small = new JsonPrimitive(1.0);
+    JsonPrimitive large = new JsonPrimitive(2.0);
+    assertThat(small.equals(large)).isFalse();
+
+    BigDecimal doubleMax = BigDecimal.valueOf(Double.MAX_VALUE);
+    JsonPrimitive smallBD = new JsonPrimitive(doubleMax.add(new BigDecimal("100.0")));
+    JsonPrimitive largeBD = new JsonPrimitive(doubleMax.add(new BigDecimal("200.0")));
+    assertThat(smallBD.equals(largeBD)).isFalse();
+  }
+
+  @Test
+  public void testBigDecimalEqualsZero() {
+    assertThat(
+            new JsonPrimitive(new BigDecimal("0.0"))
+                .equals(new JsonPrimitive(new BigDecimal("0.00"))))
+        .isTrue();
+
+    assertThat(
+            new JsonPrimitive(new BigDecimal("0.00"))
+                .equals(new JsonPrimitive(Double.valueOf("0.00"))))
+        .isTrue();
+  }
+
+  @Test
+  public void testEqualsDoubleNaNAndBigDecimal() {
+    assertThat(new JsonPrimitive(Double.NaN).equals(new JsonPrimitive(new BigDecimal("1.0"))))
+        .isFalse();
+  }
 }