blob: aa350e9bbacd21f4628dac2b9c0623a4865df451 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.legacy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.FixedBitSet;
import org.apache.lucene.util.LongBitSet;
import org.apache.lucene.util.NumericUtils;
import org.apache.solr.SolrTestCase;
public class TestLegacyNumericUtils extends SolrTestCase {
public void testLongConversionAndOrdering() throws Exception {
// generate a series of encoded longs, each numerical one bigger than the one before
BytesRefBuilder last = new BytesRefBuilder();
BytesRefBuilder act = new BytesRefBuilder();
for (long l=-100000L; l<100000L; l++) {
LegacyNumericUtils.longToPrefixCoded(l, 0, act);
if (last!=null) {
// test if smaller
assertTrue("actual bigger than last (BytesRef)", last.get().compareTo(act.get()) < 0 );
assertTrue("actual bigger than last (as String)", last.get().utf8ToString().compareTo(act.get().utf8ToString()) < 0 );
}
// test is back and forward conversion works
assertEquals("forward and back conversion should generate same long", l, LegacyNumericUtils.prefixCodedToLong(act.get()));
// next step
last.copyBytes(act);
}
}
public void testIntConversionAndOrdering() throws Exception {
// generate a series of encoded ints, each numerical one bigger than the one before
BytesRefBuilder act = new BytesRefBuilder();
BytesRefBuilder last = new BytesRefBuilder();
for (int i=-100000; i<100000; i++) {
LegacyNumericUtils.intToPrefixCoded(i, 0, act);
if (last!=null) {
// test if smaller
assertTrue("actual bigger than last (BytesRef)", last.get().compareTo(act.get()) < 0 );
assertTrue("actual bigger than last (as String)", last.get().utf8ToString().compareTo(act.get().utf8ToString()) < 0 );
}
// test is back and forward conversion works
assertEquals("forward and back conversion should generate same int", i, LegacyNumericUtils.prefixCodedToInt(act.get()));
// next step
last.copyBytes(act.get());
}
}
public void testLongSpecialValues() throws Exception {
long[] vals=new long[]{
Long.MIN_VALUE, Long.MIN_VALUE+1, Long.MIN_VALUE+2, -5003400000000L,
-4000L, -3000L, -2000L, -1000L, -1L, 0L, 1L, 10L, 300L, 50006789999999999L, Long.MAX_VALUE-2, Long.MAX_VALUE-1, Long.MAX_VALUE
};
BytesRefBuilder[] prefixVals = new BytesRefBuilder[vals.length];
for (int i=0; i<vals.length; i++) {
prefixVals[i] = new BytesRefBuilder();
LegacyNumericUtils.longToPrefixCoded(vals[i], 0, prefixVals[i]);
// check forward and back conversion
assertEquals( "forward and back conversion should generate same long", vals[i], LegacyNumericUtils.prefixCodedToLong(prefixVals[i].get()) );
// test if decoding values as int fails correctly
final int index = i;
expectThrows(NumberFormatException.class, () -> {
LegacyNumericUtils.prefixCodedToInt(prefixVals[index].get());
});
}
// check sort order (prefixVals should be ascending)
for (int i=1; i<prefixVals.length; i++) {
assertTrue( "check sort order", prefixVals[i-1].get().compareTo(prefixVals[i].get()) < 0 );
}
// check the prefix encoding, lower precision should have the difference to original value equal to the lower removed bits
final BytesRefBuilder ref = new BytesRefBuilder();
for (int i=0; i<vals.length; i++) {
for (int j=0; j<64; j++) {
LegacyNumericUtils.longToPrefixCoded(vals[i], j, ref);
long prefixVal= LegacyNumericUtils.prefixCodedToLong(ref.get());
long mask=(1L << j) - 1L;
assertEquals( "difference between prefix val and original value for "+vals[i]+" with shift="+j, vals[i] & mask, vals[i]-prefixVal );
}
}
}
public void testIntSpecialValues() throws Exception {
int[] vals=new int[]{
Integer.MIN_VALUE, Integer.MIN_VALUE+1, Integer.MIN_VALUE+2, -64765767,
-4000, -3000, -2000, -1000, -1, 0, 1, 10, 300, 765878989, Integer.MAX_VALUE-2, Integer.MAX_VALUE-1, Integer.MAX_VALUE
};
BytesRefBuilder[] prefixVals=new BytesRefBuilder[vals.length];
for (int i=0; i<vals.length; i++) {
prefixVals[i] = new BytesRefBuilder();
LegacyNumericUtils.intToPrefixCoded(vals[i], 0, prefixVals[i]);
// check forward and back conversion
assertEquals( "forward and back conversion should generate same int", vals[i], LegacyNumericUtils.prefixCodedToInt(prefixVals[i].get()) );
// test if decoding values as long fails correctly
final int index = i;
expectThrows(NumberFormatException.class, () -> {
LegacyNumericUtils.prefixCodedToLong(prefixVals[index].get());
});
}
// check sort order (prefixVals should be ascending)
for (int i=1; i<prefixVals.length; i++) {
assertTrue( "check sort order", prefixVals[i-1].get().compareTo(prefixVals[i].get()) < 0 );
}
// check the prefix encoding, lower precision should have the difference to original value equal to the lower removed bits
final BytesRefBuilder ref = new BytesRefBuilder();
for (int i=0; i<vals.length; i++) {
for (int j=0; j<32; j++) {
LegacyNumericUtils.intToPrefixCoded(vals[i], j, ref);
int prefixVal= LegacyNumericUtils.prefixCodedToInt(ref.get());
int mask=(1 << j) - 1;
assertEquals( "difference between prefix val and original value for "+vals[i]+" with shift="+j, vals[i] & mask, vals[i]-prefixVal );
}
}
}
public void testDoubles() throws Exception {
double[] vals=new double[]{
Double.NEGATIVE_INFINITY, -2.3E25, -1.0E15, -1.0, -1.0E-1, -1.0E-2, -0.0,
+0.0, 1.0E-2, 1.0E-1, 1.0, 1.0E15, 2.3E25, Double.POSITIVE_INFINITY, Double.NaN
};
long[] longVals=new long[vals.length];
// check forward and back conversion
for (int i=0; i<vals.length; i++) {
longVals[i]= NumericUtils.doubleToSortableLong(vals[i]);
assertTrue( "forward and back conversion should generate same double", Double.compare(vals[i], NumericUtils.sortableLongToDouble(longVals[i]))==0 );
}
// check sort order (prefixVals should be ascending)
for (int i=1; i<longVals.length; i++) {
assertTrue( "check sort order", longVals[i-1] < longVals[i] );
}
}
public static final double[] DOUBLE_NANs = {
Double.NaN,
Double.longBitsToDouble(0x7ff0000000000001L),
Double.longBitsToDouble(0x7fffffffffffffffL),
Double.longBitsToDouble(0xfff0000000000001L),
Double.longBitsToDouble(0xffffffffffffffffL)
};
public void testSortableDoubleNaN() {
final long plusInf = NumericUtils.doubleToSortableLong(Double.POSITIVE_INFINITY);
for (double nan : DOUBLE_NANs) {
assertTrue(Double.isNaN(nan));
final long sortable = NumericUtils.doubleToSortableLong(nan);
assertTrue("Double not sorted correctly: " + nan + ", long repr: "
+ sortable + ", positive inf.: " + plusInf, sortable > plusInf);
}
}
public void testFloats() throws Exception {
float[] vals=new float[]{
Float.NEGATIVE_INFINITY, -2.3E25f, -1.0E15f, -1.0f, -1.0E-1f, -1.0E-2f, -0.0f,
+0.0f, 1.0E-2f, 1.0E-1f, 1.0f, 1.0E15f, 2.3E25f, Float.POSITIVE_INFINITY, Float.NaN
};
int[] intVals=new int[vals.length];
// check forward and back conversion
for (int i=0; i<vals.length; i++) {
intVals[i]= NumericUtils.floatToSortableInt(vals[i]);
assertTrue( "forward and back conversion should generate same double", Float.compare(vals[i], NumericUtils.sortableIntToFloat(intVals[i]))==0 );
}
// check sort order (prefixVals should be ascending)
for (int i=1; i<intVals.length; i++) {
assertTrue( "check sort order", intVals[i-1] < intVals[i] );
}
}
public static final float[] FLOAT_NANs = {
Float.NaN,
Float.intBitsToFloat(0x7f800001),
Float.intBitsToFloat(0x7fffffff),
Float.intBitsToFloat(0xff800001),
Float.intBitsToFloat(0xffffffff)
};
public void testSortableFloatNaN() {
final int plusInf = NumericUtils.floatToSortableInt(Float.POSITIVE_INFINITY);
for (float nan : FLOAT_NANs) {
assertTrue(Float.isNaN(nan));
final int sortable = NumericUtils.floatToSortableInt(nan);
assertTrue("Float not sorted correctly: " + nan + ", int repr: "
+ sortable + ", positive inf.: " + plusInf, sortable > plusInf);
}
}
// INFO: Tests for trieCodeLong()/trieCodeInt() not needed because implicitely tested by range filter tests
/** Note: The neededBounds Iterable must be unsigned (easier understanding what's happening) */
private void assertLongRangeSplit(final long lower, final long upper, int precisionStep,
final boolean useBitSet, final Iterable<Long> expectedBounds, final Iterable<Integer> expectedShifts
) {
// Cannot use FixedBitSet since the range could be long:
final LongBitSet bits=useBitSet ? new LongBitSet(upper-lower+1) : null;
final Iterator<Long> neededBounds = (expectedBounds == null) ? null : expectedBounds.iterator();
final Iterator<Integer> neededShifts = (expectedShifts == null) ? null : expectedShifts.iterator();
LegacyNumericUtils.splitLongRange(new LegacyNumericUtils.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);
if (useBitSet) for (long l = min; l <= max; l++) {
assertFalse("ranges should not overlap", bits.getAndSet(l - lower));
// extra exit condition to prevent overflow on MAX_VALUE
if (l == max) break;
}
if (neededBounds == null || neededShifts == null)
return;
// make unsigned longs for easier display and understanding
min ^= 0x8000000000000000L;
max ^= 0x8000000000000000L;
//System.out.println("0x"+Long.toHexString(min>>>shift)+"L,0x"+Long.toHexString(max>>>shift)+"L)/*shift="+shift+"*/,");
assertEquals("shift", neededShifts.next().intValue(), shift);
assertEquals("inner min bound", neededBounds.next().longValue(), min >>> shift);
assertEquals("inner max bound", neededBounds.next().longValue(), max >>> shift);
}
}, precisionStep, lower, upper);
if (useBitSet) {
// after flipping all bits in the range, the cardinality should be zero
bits.flip(0,upper-lower+1);
assertEquals("The sub-range concenated should match the whole range", 0, bits.cardinality());
}
}
/** LUCENE-2541: LegacyNumericRangeQuery errors with endpoints near long min and max values */
public void testLongExtremeValues() throws Exception {
// upper end extremes
assertLongRangeSplit(Long.MAX_VALUE, Long.MAX_VALUE, 1, true, Arrays.asList(
0xffffffffffffffffL,0xffffffffffffffffL
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MAX_VALUE, Long.MAX_VALUE, 2, true, Arrays.asList(
0xffffffffffffffffL,0xffffffffffffffffL
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MAX_VALUE, Long.MAX_VALUE, 4, true, Arrays.asList(
0xffffffffffffffffL,0xffffffffffffffffL
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MAX_VALUE, Long.MAX_VALUE, 6, true, Arrays.asList(
0xffffffffffffffffL,0xffffffffffffffffL
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MAX_VALUE, Long.MAX_VALUE, 8, true, Arrays.asList(
0xffffffffffffffffL,0xffffffffffffffffL
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MAX_VALUE, Long.MAX_VALUE, 64, true, Arrays.asList(
0xffffffffffffffffL,0xffffffffffffffffL
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MAX_VALUE-0xfL, Long.MAX_VALUE, 4, true, Arrays.asList(
0xfffffffffffffffL,0xfffffffffffffffL
), Arrays.asList(
4
));
assertLongRangeSplit(Long.MAX_VALUE-0x10L, Long.MAX_VALUE, 4, true, Arrays.asList(
0xffffffffffffffefL,0xffffffffffffffefL,
0xfffffffffffffffL,0xfffffffffffffffL
), Arrays.asList(
0, 4
));
// lower end extremes
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE, 1, true, Arrays.asList(
0x0000000000000000L,0x0000000000000000L
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE, 2, true, Arrays.asList(
0x0000000000000000L,0x0000000000000000L
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE, 4, true, Arrays.asList(
0x0000000000000000L,0x0000000000000000L
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE, 6, true, Arrays.asList(
0x0000000000000000L,0x0000000000000000L
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE, 8, true, Arrays.asList(
0x0000000000000000L,0x0000000000000000L
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE, 64, true, Arrays.asList(
0x0000000000000000L,0x0000000000000000L
), Arrays.asList(
0
));
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE+0xfL, 4, true, Arrays.asList(
0x000000000000000L,0x000000000000000L
), Arrays.asList(
4
));
assertLongRangeSplit(Long.MIN_VALUE, Long.MIN_VALUE+0x10L, 4, true, Arrays.asList(
0x0000000000000010L,0x0000000000000010L,
0x000000000000000L,0x000000000000000L
), Arrays.asList(
0, 4
));
}
public void testRandomSplit() throws Exception {
long num = (long) atLeast(10);
for (long i=0; i < num; i++) {
executeOneRandomSplit(random());
}
}
private void executeOneRandomSplit(final Random random) throws Exception {
long lower = randomLong(random);
long len = random.nextInt(16384*1024); // not too large bitsets, else OOME!
while (lower + len < lower) { // overflow
lower >>= 1;
}
assertLongRangeSplit(lower, lower + len, random.nextInt(64) + 1, true, null, null);
}
private long randomLong(final Random random) {
long val;
switch(random.nextInt(4)) {
case 0:
val = 1L << (random.nextInt(63)); // patterns like 0x000000100000 (-1 yields patterns like 0x0000fff)
break;
case 1:
val = -1L << (random.nextInt(63)); // patterns like 0xfffff00000
break;
default:
val = random.nextLong();
}
val += random.nextInt(5)-2;
if (random.nextBoolean()) {
if (random.nextBoolean()) val += random.nextInt(100)-50;
if (random.nextBoolean()) val = ~val;
if (random.nextBoolean()) val = val<<1;
if (random.nextBoolean()) val = val>>>1;
}
return val;
}
public void testSplitLongRange() throws Exception {
// a hard-coded "standard" range
assertLongRangeSplit(-5000L, 9500L, 4, true, Arrays.asList(
0x7fffffffffffec78L,0x7fffffffffffec7fL,
0x8000000000002510L,0x800000000000251cL,
0x7fffffffffffec8L, 0x7fffffffffffecfL,
0x800000000000250L, 0x800000000000250L,
0x7fffffffffffedL, 0x7fffffffffffefL,
0x80000000000020L, 0x80000000000024L,
0x7ffffffffffffL, 0x8000000000001L
), Arrays.asList(
0, 0,
4, 4,
8, 8,
12
));
// the same with no range splitting
assertLongRangeSplit(-5000L, 9500L, 64, true, Arrays.asList(
0x7fffffffffffec78L,0x800000000000251cL
), Arrays.asList(
0
));
// this tests optimized range splitting, if one of the inner bounds
// is also the bound of the next lower precision, it should be used completely
assertLongRangeSplit(0L, 1024L+63L, 4, true, Arrays.asList(
0x800000000000040L, 0x800000000000043L,
0x80000000000000L, 0x80000000000003L
), Arrays.asList(
4, 8
));
// the full long range should only consist of a lowest precision range; no bitset testing here, as too much memory needed :-)
assertLongRangeSplit(Long.MIN_VALUE, Long.MAX_VALUE, 8, false, Arrays.asList(
0x00L,0xffL
), Arrays.asList(
56
));
// the same with precisionStep=4
assertLongRangeSplit(Long.MIN_VALUE, Long.MAX_VALUE, 4, false, Arrays.asList(
0x0L,0xfL
), Arrays.asList(
60
));
// the same with precisionStep=2
assertLongRangeSplit(Long.MIN_VALUE, Long.MAX_VALUE, 2, false, Arrays.asList(
0x0L,0x3L
), Arrays.asList(
62
));
// the same with precisionStep=1
assertLongRangeSplit(Long.MIN_VALUE, Long.MAX_VALUE, 1, false, Arrays.asList(
0x0L,0x1L
), Arrays.asList(
63
));
// a inverse range should produce no sub-ranges
assertLongRangeSplit(9500L, -5000L, 4, false, Collections.<Long>emptyList(), Collections.<Integer>emptyList());
// a 0-length range should reproduce the range itself
assertLongRangeSplit(9500L, 9500L, 4, false, Arrays.asList(
0x800000000000251cL,0x800000000000251cL
), Arrays.asList(
0
));
}
/** Note: The neededBounds Iterable must be unsigned (easier understanding what's happening) */
private void assertIntRangeSplit(final int lower, final int upper, int precisionStep,
final boolean useBitSet, final Iterable<Integer> expectedBounds, final Iterable<Integer> expectedShifts
) {
final FixedBitSet bits=useBitSet ? new FixedBitSet(upper-lower+1) : null;
final Iterator<Integer> neededBounds = (expectedBounds == null) ? null : expectedBounds.iterator();
final Iterator<Integer> neededShifts = (expectedShifts == null) ? null : expectedShifts.iterator();
LegacyNumericUtils.splitIntRange(new LegacyNumericUtils.IntRangeBuilder() {
@Override
public void addRange(int min, int max, int shift) {
assertTrue("min, max should be inside bounds", min >= lower && min <= upper && max >= lower && max <= upper);
if (useBitSet) for (int i = min; i <= max; i++) {
assertFalse("ranges should not overlap", bits.getAndSet(i - lower));
// extra exit condition to prevent overflow on MAX_VALUE
if (i == max) break;
}
if (neededBounds == null)
return;
// make unsigned ints for easier display and understanding
min ^= 0x80000000;
max ^= 0x80000000;
//System.out.println("0x"+Integer.toHexString(min>>>shift)+",0x"+Integer.toHexString(max>>>shift)+")/*shift="+shift+"*/,");
assertEquals("shift", neededShifts.next().intValue(), shift);
assertEquals("inner min bound", neededBounds.next().intValue(), min >>> shift);
assertEquals("inner max bound", neededBounds.next().intValue(), max >>> shift);
}
}, precisionStep, lower, upper);
if (useBitSet) {
// after flipping all bits in the range, the cardinality should be zero
bits.flip(0, upper-lower+1);
assertEquals("The sub-range concenated should match the whole range", 0, bits.cardinality());
}
}
public void testSplitIntRange() throws Exception {
// a hard-coded "standard" range
assertIntRangeSplit(-5000, 9500, 4, true, Arrays.asList(
0x7fffec78,0x7fffec7f,
0x80002510,0x8000251c,
0x7fffec8, 0x7fffecf,
0x8000250, 0x8000250,
0x7fffed, 0x7fffef,
0x800020, 0x800024,
0x7ffff, 0x80001
), Arrays.asList(
0, 0,
4, 4,
8, 8,
12
));
// the same with no range splitting
assertIntRangeSplit(-5000, 9500, 32, true, Arrays.asList(
0x7fffec78,0x8000251c
), Arrays.asList(
0
));
// this tests optimized range splitting, if one of the inner bounds
// is also the bound of the next lower precision, it should be used completely
assertIntRangeSplit(0, 1024+63, 4, true, Arrays.asList(
0x8000040, 0x8000043,
0x800000, 0x800003
), Arrays.asList(
4, 8
));
// the full int range should only consist of a lowest precision range; no bitset testing here, as too much memory needed :-)
assertIntRangeSplit(Integer.MIN_VALUE, Integer.MAX_VALUE, 8, false, Arrays.asList(
0x00,0xff
), Arrays.asList(
24
));
// the same with precisionStep=4
assertIntRangeSplit(Integer.MIN_VALUE, Integer.MAX_VALUE, 4, false, Arrays.asList(
0x0,0xf
), Arrays.asList(
28
));
// the same with precisionStep=2
assertIntRangeSplit(Integer.MIN_VALUE, Integer.MAX_VALUE, 2, false, Arrays.asList(
0x0,0x3
), Arrays.asList(
30
));
// the same with precisionStep=1
assertIntRangeSplit(Integer.MIN_VALUE, Integer.MAX_VALUE, 1, false, Arrays.asList(
0x0,0x1
), Arrays.asList(
31
));
// a inverse range should produce no sub-ranges
assertIntRangeSplit(9500, -5000, 4, false, Collections.<Integer>emptyList(), Collections.<Integer>emptyList());
// a 0-length range should reproduce the range itself
assertIntRangeSplit(9500, 9500, 4, false, Arrays.asList(
0x8000251c,0x8000251c
), Arrays.asList(
0
));
}
}