You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@commons.apache.org by "Ruiqi Dong (Jira)" <ji...@apache.org> on 2026/05/21 10:25:00 UTC

[jira] [Created] (MATH-1690) SparseGradient.equals() uses ULP-based comparison but hashCode() uses exact doubles

Ruiqi Dong created MATH-1690:
--------------------------------

             Summary: SparseGradient.equals() uses ULP-based comparison but hashCode() uses exact doubles
                 Key: MATH-1690
                 URL: https://issues.apache.org/jira/browse/MATH-1690
             Project: Commons Math
          Issue Type: Bug
          Components: legacy
            Reporter: Ruiqi Dong


*Summary*
SparseGradient.equals(Object) treats values within 1 ULP as equal, but hashCode() hashes the exact double values. This breaks the Java contract that equal objects must have equal hash codes.
 
*Affected code*
File: commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/differentiation/SparseGradient.java
{code:java}
if (other instanceof SparseGradient) {
    final SparseGradient rhs = (SparseGradient)other;
    if (!Precision.equals(value, rhs.value, 1)) {
        return false;
    }
    ...
    if (!Precision.equals(entry.getValue(), rhs.derivatives.get(entry.getKey()), 1)) {
        return false;
    }
    return true;
}

@Override
public int hashCode() {
    return 743 + 809 * Double.hashCode(value) + 167 * derivatives.hashCode();
} {code}
*Reproducer*
Add the following test to commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/differentiation/SparseGradientTest.java:
{code:java}
@Test
public void testHashCodeMatchesEqualsForUlpAdjacentValues() {
    final SparseGradient constantA = SparseGradient.createConstant(1.0);
    final SparseGradient constantB = SparseGradient.createConstant(JdkMath.nextUp(1.0));
    Assert.assertEquals(constantA, constantB);
    Assert.assertEquals(constantA.hashCode(), constantB.hashCode());

    final SparseGradient derivativeA = SparseGradient.createVariable(0, 0.0).multiply(1.0);
    final SparseGradient derivativeB =
            SparseGradient.createVariable(0, 0.0).multiply(JdkMath.nextUp(1.0));
    Assert.assertEquals(derivativeA, derivativeB);
    Assert.assertEquals(derivativeA.hashCode(), derivativeB.hashCode());
} {code}
Run:
{code:java}
mvn -q -pl commons-math-legacy -Dtest=org.apache.commons.math4.legacy.analysis.differentiation.SparseGradientTest test {code}
Observed behavior:
{code:java}
SparseGradientTest.testHashCodeMatchesEqualsForUlpAdjacentValues:60
expected:<225444583> but was:<225445392> {code}
Expected behavior:
If two SparseGradient instances are equal according to equals(), they need to return the same hash code.
 
The current implementation mixes approximate equality with exact hashing, which is a direct equals/hashCode contract violation.



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