This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git

commit 51973c3294ca0cdfbe711c8549e2609b40d31634
Author: aherbert <aherb...@apache.org>
AuthorDate: Wed Apr 8 13:14:37 2020 +0100

    Fix fraction hashCode
    
    The sign must be incorporated into the hash as a fraction with the same
    numerical value can have different representations:
    
    -1/3 == 1/-3
    
    Using the sign ensures that -1/3 and 1/3 have different hash codes. This
    would not be true if the hash were constructed using only the absolute
    values of numerator and denominator.
---
 .../org/apache/commons/numbers/fraction/BigFraction.java     | 12 +++++++++++-
 .../java/org/apache/commons/numbers/fraction/Fraction.java   | 12 +++++++++++-
 .../org/apache/commons/numbers/fraction/BigFractionTest.java | 10 +++++++---
 .../org/apache/commons/numbers/fraction/FractionTest.java    | 10 +++++++---
 4 files changed, 36 insertions(+), 8 deletions(-)

diff --git 
a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/BigFraction.java
 
b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/BigFraction.java
index cee2a6e..3962e2e 100644
--- 
a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/BigFraction.java
+++ 
b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/BigFraction.java
@@ -1019,7 +1019,17 @@ public final class BigFraction
 
     @Override
     public int hashCode() {
-        return 37 * (37 * 17 + numerator.hashCode()) + denominator.hashCode();
+        // Incorporate the sign and absolute values of the numerator and 
denominator.
+        // Equivalent to
+        // Arrays.hashCode(new int[] {signum(), numerator.abs(), 
denominator.abs()})
+        // int hash = 1;
+        // hash = 31 * hash + signum();
+        // hash = 31 * hash + numerator.abs().hashCode();
+        // hash = 31 * hash + denominator.abs().hashCode();
+        // Note: BigInteger.hashCode() * BigInteger.signum() == 
BigInteger.abs().hashCode().
+        final int numS = numerator.signum();
+        final int denS = denominator.signum();
+        return 31 * (31 * (31 + numS * denS) + numerator.hashCode() * numS) + 
denominator.hashCode() * denS;
     }
 
     /**
diff --git 
a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/Fraction.java
 
b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/Fraction.java
index 31e7cde..be85fb3 100644
--- 
a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/Fraction.java
+++ 
b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/Fraction.java
@@ -706,7 +706,17 @@ public final class Fraction
 
     @Override
     public int hashCode() {
-        return 37 * (37 * 17 + numerator) + denominator;
+        // Incorporate the sign and absolute values of the numerator and 
denominator.
+        // Equivalent to
+        // Arrays.hashCode(new int[] {signum(), Math.abs(numerator), 
Math.abs(denominator)})
+        // int hash = 1;
+        // hash = 31 * hash + signum();
+        // hash = 31 * hash + Math.abs(numerator);
+        // hash = 31 * hash + Math.abs(denominator);
+        // Note: x * Integer.signum(x) is equivalent to Math.abs(x).
+        final int numS = Integer.signum(numerator);
+        final int denS = Integer.signum(denominator);
+        return 31 * (31 * (31 + numS * denS) + numerator * numS) + denominator 
* denS;
     }
 
     /**
diff --git 
a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java
 
b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java
index e3a611b..4d3377c 100644
--- 
a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java
+++ 
b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/BigFractionTest.java
@@ -19,13 +19,12 @@ package org.apache.commons.numbers.fraction;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.RoundingMode;
+import java.util.Arrays;
 import org.apache.commons.numbers.core.TestUtils;
 
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
-
 public class BigFractionTest {
 
     private static void assertFraction(long expectedNumerator, long 
expectedDenominator, BigFraction actual) {
@@ -595,7 +594,6 @@ public class BigFractionTest {
         Assertions.assertEquals(new BigDecimal("0.333"), BigFraction.of(1, 
3).bigDecimalValue(3, RoundingMode.DOWN));
     }
 
-    @Disabled
     @Test
     public void testEqualsAndHashCode() {
         BigFraction zero = BigFraction.of(0, 1);
@@ -649,6 +647,12 @@ public class BigFractionTest {
         Assertions.assertNotSame(f1, f2, "Do not call this assertion with the 
same object");
         Assertions.assertEquals(f1, f2);
         Assertions.assertEquals(f1.hashCode(), f2.hashCode(), "Equal fractions 
have different hashCode");
+        // Check the hashcode computation.
+        // This is not mandated but is a recommendation.
+        final int expected = Arrays.hashCode(new Object[] {f1.signum(),
+                                                           
f1.getNumerator().abs(),
+                                                           
f1.getDenominator().abs()});
+        Assertions.assertEquals(expected, f1.hashCode(), "Hashcode not equal 
to using Arrays.hashCode");
     }
 
     @Test
diff --git 
a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/FractionTest.java
 
b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/FractionTest.java
index 0888a75..ada4269 100644
--- 
a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/FractionTest.java
+++ 
b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/FractionTest.java
@@ -16,13 +16,12 @@
  */
 package org.apache.commons.numbers.fraction;
 
+import java.util.Arrays;
 import org.apache.commons.numbers.core.TestUtils;
 
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
-
 /**
  */
 public class FractionTest {
@@ -427,7 +426,6 @@ public class FractionTest {
         );
     }
 
-    @Disabled
     @Test
     public void testEqualsAndHashCode() {
         Fraction zero = Fraction.of(0, 1);
@@ -492,6 +490,12 @@ public class FractionTest {
         Assertions.assertNotSame(f1, f2, "Do not call this assertion with the 
same object");
         Assertions.assertEquals(f1, f2);
         Assertions.assertEquals(f1.hashCode(), f2.hashCode(), "Equal fractions 
have different hashCode");
+        // Check the hashcode computation.
+        // This is not mandated but is a recommendation.
+        final int expected = Arrays.hashCode(new int[] {f1.signum(),
+                                                        
Math.abs(f1.getNumerator()),
+                                                        
Math.abs(f1.getDenominator())});
+        Assertions.assertEquals(expected, f1.hashCode(), "Hashcode not equal 
to using Arrays.hashCode");
     }
 
     @Test

Reply via email to