Move EnhancedDoubleHasher.mod() to a public BitMap API (#396)

* Made mod() public

* Moved mod() method to BitMap class.

* Add Javadoc since tag

* Added mod() tests and updated documentation.

* fixed formatting issues

---------

Co-authored-by: Gary Gregory <garydgregory@users.noreply.github.com>
This commit is contained in:
Claude Warren 2023-06-13 15:48:18 +01:00 committed by GitHub
parent 1d07ca4066
commit 81834a637d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 21 deletions

View File

@ -113,4 +113,24 @@ public class BitMap {
// this will identify an incorrect bit. // this will identify an incorrect bit.
return 1L << bitIndex; return 1L << bitIndex;
} }
/**
* Performs a modulus calculation on an unsigned long and an positive integer divisor.
*
* <p><em>If the divisor is negative the behavior is not defined.</em></p>
*
* @param dividend a unsigned long value to calculate the modulus of.
* @param divisor the divisor for the modulus calculation, must be positive.
* @return the remainder or modulus value.
* @since 4.5
*/
public static int mod(final long dividend, final int divisor) {
// See Hacker's Delight (2nd ed), section 9.3.
// Assume divisor is positive.
// Divide half the unsigned number and then double the quotient result.
final long quotient = (dividend >>> 1) / divisor << 1;
final long remainder = dividend - quotient * divisor;
// remainder in [0, 2 * divisor)
return (int) (remainder >= divisor ? remainder - divisor : remainder);
}
} }

View File

@ -129,22 +129,6 @@ public class EnhancedDoubleHasher implements Hasher {
return increment; return increment;
} }
/**
* Performs a modulus calculation on an unsigned long and an integer divisor.
* @param dividend a unsigned long value to calculate the modulus of.
* @param divisor the divisor for the modulus calculation (must be strictly positive).
* @return the remainder or modulus value.
*/
static int mod(final long dividend, final int divisor) {
// See Hacker's Delight (2nd ed), section 9.3.
// Assume divisor is positive.
// Divide half the unsigned number and then double the quotient result.
final long quotient = (dividend >>> 1) / divisor << 1;
final long remainder = dividend - quotient * divisor;
// remainder in [0, 2 * divisor)
return (int) (remainder >= divisor ? remainder - divisor : remainder);
}
@Override @Override
public IndexProducer indices(final Shape shape) { public IndexProducer indices(final Shape shape) {
Objects.requireNonNull(shape, "shape"); Objects.requireNonNull(shape, "shape");
@ -168,8 +152,8 @@ public class EnhancedDoubleHasher implements Hasher {
// The final hash is: // The final hash is:
// hash[i] = ( h1(x) - i*h2(x) - (i*i*i - i)/6 ) wrapped in [0, bits) // hash[i] = ( h1(x) - i*h2(x) - (i*i*i - i)/6 ) wrapped in [0, bits)
int index = mod(initial, bits); int index = BitMap.mod(initial, bits);
int inc = mod(increment, bits); int inc = BitMap.mod(increment, bits);
final int k = shape.getNumberOfHashFunctions(); final int k = shape.getNumberOfHashFunctions();
if (k > bits) { if (k > bits) {

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.collections4.bloomfilter; package org.apache.commons.collections4.bloomfilter;
import static org.junit.Assert.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@ -102,4 +103,19 @@ public class BitMapTest {
ary[1] = 1; ary[1] = 1;
assertTrue(BitMap.contains(ary, 64)); assertTrue(BitMap.contains(ary, 64));
} }
private void assertMod(long l, int i) {
assertEquals(Math.floorMod(l, i), BitMap.mod(l, i));
}
@Test
public final void testMod() {
assertMod(Long.MAX_VALUE, Integer.MAX_VALUE);
assertMod(Long.MAX_VALUE, Integer.MAX_VALUE-1);
assertThrows(ArithmeticException.class, () -> BitMap.mod(Long.MAX_VALUE, 0));
assertMod(Long.MAX_VALUE-1, Integer.MAX_VALUE);
assertMod(Long.MAX_VALUE-1, Integer.MAX_VALUE-1);
assertMod(0, Integer.MAX_VALUE);
assertNotEquals(Math.floorMod(5, -1), BitMap.mod(5, -1));
}
} }

View File

@ -99,7 +99,7 @@ public class EnhancedDoubleHasherTest extends AbstractHasherTest {
for (final long dividend : new long[] {-1, -2, -3, -6378683, -23567468136887892L, Long.MIN_VALUE, 345, 678686, for (final long dividend : new long[] {-1, -2, -3, -6378683, -23567468136887892L, Long.MIN_VALUE, 345, 678686,
67868768686878924L, Long.MAX_VALUE}) { 67868768686878924L, Long.MAX_VALUE}) {
for (final int divisor : new int[] {1, 2, 3, 5, 13, Integer.MAX_VALUE}) { for (final int divisor : new int[] {1, 2, 3, 5, 13, Integer.MAX_VALUE}) {
assertEquals((int) Long.remainderUnsigned(dividend, divisor), EnhancedDoubleHasher.mod(dividend, divisor), assertEquals((int) Long.remainderUnsigned(dividend, divisor), BitMap.mod(dividend, divisor),
() -> String.format("failure with dividend=%s and divisor=%s.", dividend, divisor)); () -> String.format("failure with dividend=%s and divisor=%s.", dividend, divisor));
} }
} }

View File

@ -66,8 +66,8 @@ final class IncrementingHasher implements Hasher {
// This avoids any modulus operation inside the while loop. It uses a long index // This avoids any modulus operation inside the while loop. It uses a long index
// to avoid overflow. // to avoid overflow.
long index = EnhancedDoubleHasher.mod(initial, bits); long index = BitMap.mod(initial, bits);
final int inc = EnhancedDoubleHasher.mod(increment, bits); final int inc = BitMap.mod(increment, bits);
for (int functionalCount = 0; functionalCount < shape.getNumberOfHashFunctions(); functionalCount++) { for (int functionalCount = 0; functionalCount < shape.getNumberOfHashFunctions(); functionalCount++) {
if (!consumer.test((int) index)) { if (!consumer.test((int) index)) {