Shan Jiang created COMMONSSITE-182:
--------------------------------------

             Summary: `Dfp.equals()` returns `false` for `NaN.equals(NaN)`, 
violating `Object.equals()` reflexivity
                 Key: COMMONSSITE-182
                 URL: https://issues.apache.org/jira/browse/COMMONSSITE-182
             Project: Apache Commons All
          Issue Type: Bug
            Reporter: Shan Jiang


apache/commons-math — Dfp.equals() violates reflexivity for NaN


### Summary

`Dfp.equals()` returns `false` when both operands are NaN, violating the 
reflexivity requirement
of `Object.equals()` (JDK 21 Javadoc):

> "The `equals` method implements an equivalence relation on non-null object 
> references: It is
> *reflexive*: for any non-null reference value `x`, `x.equals(x)` should 
> return `true`."

### Root cause

In [`Dfp.java` lines 
895-907](https://github.com/apache/commons-math/blob/master/commons-math-legacy-core/src/main/java/org/apache/commons/math4/legacy/core/dfp/Dfp.java#L895-L907):

```java
@Override
public boolean equals(final Object other) {
if (other instanceof Dfp) {
finalDfpx = (Dfp) other;
if (isNaN() || x.isNaN() || field.getRadixDigits() != x.field.getRadixDigits()) 
{
returnfalse; // <-- NaN.equals(NaN) returns false
}
returncompare(this, x) == 0;
}
returnfalse;
}
```

### Reproducer

```java
Dfp nan = new DfpField(20).newDfp().newInstance((byte) 1, Dfp.QNAN);
System.out.println(nan.equals(nan)); // false (should be true)
```

### Comparison with JDK

The JDK's `Double.equals()` and `Float.equals()` deliberately return `true` for 
NaN:

> `Double.equals()`: "If `d1` represents `+0.0` while `d2` represents `-0.0`, 
> or vice versa,
> the `equal` test has the value `false`, even though `+0.0==-0.0` has the 
> value `true`. [...]
> If `d1` and `d2` both represent `Double.NaN`, then the `equals` method 
> returns `true`, even
> though `Double.NaN==Double.NaN` has the value `false`."

The JDK explicitly chose to break IEEE 754 NaN semantics in `equals()` to 
preserve the
`Object.equals()` contract. `Dfp` should follow the same convention.

### Impact

Any `Dfp` NaN value placed in a `HashSet`, `HashMap`, or compared with 
`equals()` will behave
incorrectly:
- `set.add(nan); set.contains(nan)` → may return `false`
- `map.put(nan, value); map.get(nan)` → may return `null`

### How this was found

Detected by an automated JDK conformance oracle 
(`ObjectOracles.checkEqualsReflexive`).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to