Index: lucene/src/java/org/apache/lucene/util/NumericUtils.java =================================================================== --- lucene/src/java/org/apache/lucene/util/NumericUtils.java (revision 964951) +++ lucene/src/java/org/apache/lucene/util/NumericUtils.java (working copy) @@ -21,6 +21,7 @@ import org.apache.lucene.document.NumericField; // for javadocs import org.apache.lucene.search.NumericRangeQuery; // for javadocs import org.apache.lucene.search.NumericRangeFilter; // for javadocs +import java.math.BigInteger; // TODO: Remove the commented out methods before release! @@ -411,7 +412,7 @@ public static void splitLongRange(final LongRangeBuilder builder, final int precisionStep, final long minBound, final long maxBound ) { - splitRange(builder, 64, precisionStep, minBound, maxBound); + splitRange(builder, 64, precisionStep, BigInteger.valueOf(minBound), BigInteger.valueOf(maxBound)); } /** @@ -425,39 +426,53 @@ public static void splitIntRange(final IntRangeBuilder builder, final int precisionStep, final int minBound, final int maxBound ) { - splitRange(builder, 32, precisionStep, minBound, maxBound); + splitRange(builder, 32, precisionStep, BigInteger.valueOf(minBound), BigInteger.valueOf(maxBound)); } + private static final BigInteger[] SHIFTS = new BigInteger[64]; + static { + for (int i=0; i < SHIFTS.length; i++) { + SHIFTS[i] = BigInteger.ONE.shiftLeft(i); + } + } + /** This helper does the splitting for both 32 and 64 bit. */ private static void splitRange( final Object builder, final int valSize, - final int precisionStep, long minBound, long maxBound + final int precisionStep, BigInteger minBound, BigInteger maxBound ) { if (precisionStep < 1) throw new IllegalArgumentException("precisionStep must be >=1"); - if (minBound > maxBound) return; + if (minBound.compareTo(maxBound) > 0) return; for (int shift=0; ; shift += precisionStep) { + if (shift+precisionStep>=valSize) { + // We are in the lowest precision + addRange(builder, valSize, minBound, maxBound, shift); + // exit the split recursion loop + break; + } + // calculate new bounds for inner precision - final long diff = 1L << (shift+precisionStep), - mask = ((1L<=valSize || nextMinBound>nextMaxBound) { - // We are in the lowest precision or the next precision is not available. + hasLower = !minBound.and(mask).equals(BigInteger.ZERO), + hasUpper = !maxBound.and(mask).equals(mask); + final BigInteger + nextMinBound = (hasLower ? minBound.add(diff) : minBound).andNot(mask), + nextMaxBound = (hasUpper ? maxBound.subtract(diff) : maxBound).andNot(mask); + + if (nextMinBound.compareTo(nextMaxBound) > 0) { + // The next precision is not available addRange(builder, valSize, minBound, maxBound, shift); // exit the split recursion loop break; } if (hasLower) - addRange(builder, valSize, minBound, minBound | mask, shift); + addRange(builder, valSize, minBound, minBound.or(mask), shift); if (hasUpper) - addRange(builder, valSize, maxBound & ~mask, maxBound, shift); + addRange(builder, valSize, maxBound.andNot(mask), maxBound, shift); // recurse to next precision minBound = nextMinBound; @@ -468,21 +483,21 @@ /** Helper that delegates to correct range builder */ private static void addRange( final Object builder, final int valSize, - long minBound, long maxBound, + BigInteger minBound, BigInteger maxBound, final int shift ) { // for the max bound set all lower bits (that were shifted away): // this is important for testing or other usages of the splitted range // (e.g. to reconstruct the full range). The prefixEncoding will remove // the bits anyway, so they do not hurt! - maxBound |= (1L << shift) - 1L; + maxBound = maxBound.or(SHIFTS[shift].subtract(BigInteger.ONE)); // delegate to correct range builder switch(valSize) { case 64: - ((LongRangeBuilder)builder).addRange(minBound, maxBound, shift); + ((LongRangeBuilder)builder).addRange(minBound.longValue(), maxBound.longValue(), shift); break; case 32: - ((IntRangeBuilder)builder).addRange((int)minBound, (int)maxBound, shift); + ((IntRangeBuilder)builder).addRange(minBound.intValue(), maxBound.intValue(), shift); break; default: // Should not happen! Index: lucene/src/test/org/apache/lucene/util/TestNumericUtils.java =================================================================== --- lucene/src/test/org/apache/lucene/util/TestNumericUtils.java (revision 964951) +++ lucene/src/test/org/apache/lucene/util/TestNumericUtils.java (working copy) @@ -179,6 +179,29 @@ // INFO: Tests for trieCodeLong()/trieCodeInt() not needed because implicitely tested by range filter tests + public void testLongExtremeValues() throws Exception { + // use MAX_VALUE - 1 here, else the for-loop below overflows + assertRangeComplete(Long.MAX_VALUE - 1L, Long.MAX_VALUE - 1L, 4); + } + + private void assertRangeComplete(final long lower, final long upper, int precisionStep) { + final OpenBitSet bits=new OpenBitSet(upper-lower+1); + + NumericUtils.splitLongRange(new NumericUtils.LongRangeBuilder() { + @Override + public void addRange(long min, long max, int shift) { + assertTrue("min, max should be inside bounds", min>=lower && min<=upper && max>=lower && max<=upper); + for (long l=min; l<=max; l++) { + assertFalse("ranges should not overlap", bits.getAndSet(l-lower) ); + } + } + }, precisionStep, lower, upper); + + // after flipping all bits in the range, the cardinality should be zero + bits.flip(0,upper-lower+1); + assertTrue("The sub-range concenated should match the whole range", bits.isEmpty()); + } + /** Note: The neededBounds iterator must be unsigned (easier understanding what's happening) */ protected void assertLongRangeSplit(final long lower, final long upper, int precisionStep, final boolean useBitSet, final Iterator neededBounds