blob: 7dbbfd2db0a127b6309902f81f443d875ea679fc [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.lucene.util;
import java.math.BigInteger;
import java.util.Arrays;
/**
* Tests for NumericUtils static methods.
*/
public class TestNumericUtils extends LuceneTestCase {
/**
* generate a series of encoded longs, each numerical one bigger than the one before.
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testLongConversionAndOrdering() throws Exception {
BytesRef previous = null;
BytesRef current = new BytesRef(new byte[Long.BYTES]);
for (long value = -100000L; value < 100000L; value++) {
NumericUtils.longToSortableBytes(value, current.bytes, current.offset);
if (previous == null) {
previous = new BytesRef(new byte[Long.BYTES]);
} else {
// test if smaller
assertTrue("current bigger than previous: ", previous.compareTo(current) < 0);
}
// test is back and forward conversion works
assertEquals("forward and back conversion should generate same long", value, NumericUtils.sortableBytesToLong(current.bytes, current.offset));
// next step
System.arraycopy(current.bytes, current.offset, previous.bytes, previous.offset, current.length);
}
}
/**
* generate a series of encoded ints, each numerical one bigger than the one before.
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testIntConversionAndOrdering() throws Exception {
BytesRef previous = null;
BytesRef current = new BytesRef(new byte[Integer.BYTES]);
for (int value = -100000; value < 100000; value++) {
NumericUtils.intToSortableBytes(value, current.bytes, current.offset);
if (previous == null) {
previous = new BytesRef(new byte[Integer.BYTES]);
} else {
// test if smaller
assertTrue("current bigger than previous: ", previous.compareTo(current) < 0);
}
// test is back and forward conversion works
assertEquals("forward and back conversion should generate same int", value, NumericUtils.sortableBytesToInt(current.bytes, current.offset));
// next step
System.arraycopy(current.bytes, current.offset, previous.bytes, previous.offset, current.length);
}
}
/**
* generate a series of encoded BigIntegers, each numerical one bigger than the one before.
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testBigIntConversionAndOrdering() throws Exception {
// we need at least 3 bytes of storage.
int size = TestUtil.nextInt(random(), 3, 16);
BytesRef previous = null;
BytesRef current = new BytesRef(new byte[size]);
for (long value = -100000L; value < 100000L; value++) {
NumericUtils.bigIntToSortableBytes(BigInteger.valueOf(value), size, current.bytes, current.offset);
if (previous == null) {
previous = new BytesRef(new byte[size]);
} else {
// test if smaller
assertTrue("current bigger than previous: ", previous.compareTo(current) < 0);
}
// test is back and forward conversion works
assertEquals("forward and back conversion should generate same BigInteger",
BigInteger.valueOf(value),
NumericUtils.sortableBytesToBigInt(current.bytes, current.offset, current.length));
// next step
System.arraycopy(current.bytes, current.offset, previous.bytes, previous.offset, current.length);
}
}
/**
* check extreme values of longs
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testLongSpecialValues() throws Exception {
long[] values = 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
};
BytesRef[] encoded = new BytesRef[values.length];
for (int i = 0; i < values.length; i++) {
encoded[i] = new BytesRef(new byte[Long.BYTES]);
NumericUtils.longToSortableBytes(values[i], encoded[i].bytes, encoded[i].offset);
// check forward and back conversion
assertEquals("forward and back conversion should generate same long",
values[i],
NumericUtils.sortableBytesToLong(encoded[i].bytes, encoded[i].offset));
}
// check sort order (encoded values should be ascending)
for (int i = 1; i < encoded.length; i++) {
assertTrue("check sort order", encoded[i-1].compareTo(encoded[i]) < 0);
}
}
/**
* check extreme values of ints
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testIntSpecialValues() throws Exception {
int[] values = 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
};
BytesRef[] encoded = new BytesRef[values.length];
for (int i = 0; i < values.length; i++) {
encoded[i] = new BytesRef(new byte[Integer.BYTES]);
NumericUtils.intToSortableBytes(values[i], encoded[i].bytes, encoded[i].offset);
// check forward and back conversion
assertEquals("forward and back conversion should generate same int",
values[i],
NumericUtils.sortableBytesToInt(encoded[i].bytes, encoded[i].offset));
}
// check sort order (encoded values should be ascending)
for (int i = 1; i < encoded.length; i++) {
assertTrue("check sort order", encoded[i-1].compareTo(encoded[i]) < 0);
}
}
/**
* check extreme values of big integers (4 bytes)
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testBigIntSpecialValues() throws Exception {
BigInteger[] values = new BigInteger[] {
BigInteger.valueOf(Integer.MIN_VALUE), BigInteger.valueOf(Integer.MIN_VALUE+1),
BigInteger.valueOf(Integer.MIN_VALUE+2), BigInteger.valueOf(-64765767),
BigInteger.valueOf(-4000), BigInteger.valueOf(-3000), BigInteger.valueOf(-2000),
BigInteger.valueOf(-1000), BigInteger.valueOf(-1), BigInteger.valueOf(0),
BigInteger.valueOf(1), BigInteger.valueOf(10), BigInteger.valueOf(300),
BigInteger.valueOf(765878989), BigInteger.valueOf(Integer.MAX_VALUE-2),
BigInteger.valueOf(Integer.MAX_VALUE-1), BigInteger.valueOf(Integer.MAX_VALUE)
};
BytesRef[] encoded = new BytesRef[values.length];
for (int i = 0; i < values.length; i++) {
encoded[i] = new BytesRef(new byte[Integer.BYTES]);
NumericUtils.bigIntToSortableBytes(values[i], Integer.BYTES, encoded[i].bytes, encoded[i].offset);
// check forward and back conversion
assertEquals("forward and back conversion should generate same big integer",
values[i],
NumericUtils.sortableBytesToBigInt(encoded[i].bytes, encoded[i].offset, Integer.BYTES));
}
// check sort order (encoded values should be ascending)
for (int i = 1; i < encoded.length; i++) {
assertTrue("check sort order", encoded[i-1].compareTo(encoded[i]) < 0);
}
}
/**
* check various sorted values of doubles (including extreme values)
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testDoubles() throws Exception {
double[] values = 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[] encoded = new long[values.length];
// check forward and back conversion
for (int i = 0; i < values.length; i++) {
encoded[i] = NumericUtils.doubleToSortableLong(values[i]);
assertTrue("forward and back conversion should generate same double",
Double.compare(values[i], NumericUtils.sortableLongToDouble(encoded[i])) == 0);
}
// check sort order (encoded values should be ascending)
for (int i = 1; i < encoded.length; i++) {
assertTrue("check sort order", encoded[i-1] < encoded[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);
}
}
/**
* check various sorted values of floats (including extreme values)
* check for correct ordering of the encoded bytes and that values round-trip.
*/
public void testFloats() throws Exception {
float[] values = 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[] encoded = new int[values.length];
// check forward and back conversion
for (int i = 0; i < values.length; i++) {
encoded[i] = NumericUtils.floatToSortableInt(values[i]);
assertTrue("forward and back conversion should generate same float",
Float.compare(values[i], NumericUtils.sortableIntToFloat(encoded[i])) == 0);
}
// check sort order (encoded values should be ascending)
for (int i = 1; i < encoded.length; i++) {
assertTrue( "check sort order", encoded[i-1] < encoded[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);
}
}
public void testAdd() throws Exception {
int iters = atLeast(1000);
int numBytes = TestUtil.nextInt(random(), 1, 100);
for(int iter=0;iter<iters;iter++) {
BigInteger v1 = new BigInteger(8*numBytes-1, random());
BigInteger v2 = new BigInteger(8*numBytes-1, random());
byte[] v1Bytes = new byte[numBytes];
byte[] v1RawBytes = v1.toByteArray();
assert v1RawBytes.length <= numBytes;
System.arraycopy(v1RawBytes, 0, v1Bytes, v1Bytes.length-v1RawBytes.length, v1RawBytes.length);
byte[] v2Bytes = new byte[numBytes];
byte[] v2RawBytes = v2.toByteArray();
assert v1RawBytes.length <= numBytes;
System.arraycopy(v2RawBytes, 0, v2Bytes, v2Bytes.length-v2RawBytes.length, v2RawBytes.length);
byte[] result = new byte[numBytes];
NumericUtils.add(numBytes, 0, v1Bytes, v2Bytes, result);
BigInteger sum = v1.add(v2);
assertTrue("sum=" + sum + " v1=" + v1 + " v2=" + v2 + " but result=" + new BigInteger(1, result), sum.equals(new BigInteger(1, result)));
}
}
public void testIllegalAdd() throws Exception {
byte[] bytes = new byte[4];
Arrays.fill(bytes, (byte) 0xff);
byte[] one = new byte[4];
one[3] = 1;
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
NumericUtils.add(4, 0, bytes, one, new byte[4]);
});
assertEquals("a + b overflows bytesPerDim=4", expected.getMessage());
}
public void testSubtract() throws Exception {
int iters = atLeast(1000);
int numBytes = TestUtil.nextInt(random(), 1, 100);
for(int iter=0;iter<iters;iter++) {
BigInteger v1 = new BigInteger(8*numBytes-1, random());
BigInteger v2 = new BigInteger(8*numBytes-1, random());
if (v1.compareTo(v2) < 0) {
BigInteger tmp = v1;
v1 = v2;
v2 = tmp;
}
byte[] v1Bytes = new byte[numBytes];
byte[] v1RawBytes = v1.toByteArray();
assert v1RawBytes.length <= numBytes: "length=" + v1RawBytes.length + " vs numBytes=" + numBytes;
System.arraycopy(v1RawBytes, 0, v1Bytes, v1Bytes.length-v1RawBytes.length, v1RawBytes.length);
byte[] v2Bytes = new byte[numBytes];
byte[] v2RawBytes = v2.toByteArray();
assert v2RawBytes.length <= numBytes;
assert v2RawBytes.length <= numBytes: "length=" + v2RawBytes.length + " vs numBytes=" + numBytes;
System.arraycopy(v2RawBytes, 0, v2Bytes, v2Bytes.length-v2RawBytes.length, v2RawBytes.length);
byte[] result = new byte[numBytes];
NumericUtils.subtract(numBytes, 0, v1Bytes, v2Bytes, result);
BigInteger diff = v1.subtract(v2);
assertTrue("diff=" + diff + " vs result=" + new BigInteger(result) + " v1=" + v1 + " v2=" + v2, diff.equals(new BigInteger(result)));
}
}
public void testIllegalSubtract() throws Exception {
byte[] v1 = new byte[4];
v1[3] = (byte) 0xf0;
byte[] v2 = new byte[4];
v2[3] = (byte) 0xf1;
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
NumericUtils.subtract(4, 0, v1, v2, new byte[4]);
});
assertEquals("a < b", expected.getMessage());
}
/** test round-trip encoding of random integers */
public void testIntsRoundTrip() {
byte[] encoded = new byte[Integer.BYTES];
for (int i = 0; i < 10000; i++) {
int value = random().nextInt();
NumericUtils.intToSortableBytes(value, encoded, 0);
assertEquals(value, NumericUtils.sortableBytesToInt(encoded, 0));
}
}
/** test round-trip encoding of random longs */
public void testLongsRoundTrip() {
byte[] encoded = new byte[Long.BYTES];
for (int i = 0; i < 10000; i++) {
long value = TestUtil.nextLong(random(), Long.MIN_VALUE, Long.MAX_VALUE);
NumericUtils.longToSortableBytes(value, encoded, 0);
assertEquals(value, NumericUtils.sortableBytesToLong(encoded, 0));
}
}
/** test round-trip encoding of random floats */
public void testFloatsRoundTrip() {
byte[] encoded = new byte[Float.BYTES];
for (int i = 0; i < 10000; i++) {
float value = Float.intBitsToFloat(random().nextInt());
NumericUtils.intToSortableBytes(NumericUtils.floatToSortableInt(value), encoded, 0);
float actual = NumericUtils.sortableIntToFloat(NumericUtils.sortableBytesToInt(encoded, 0));
assertEquals(Float.floatToIntBits(value), Float.floatToIntBits(actual));
}
}
/** test round-trip encoding of random doubles */
public void testDoublesRoundTrip() {
byte[] encoded = new byte[Double.BYTES];
for (int i = 0; i < 10000; i++) {
double value = Double.longBitsToDouble(TestUtil.nextLong(random(), Long.MIN_VALUE, Long.MAX_VALUE));
NumericUtils.longToSortableBytes(NumericUtils.doubleToSortableLong(value), encoded, 0);
double actual = NumericUtils.sortableLongToDouble(NumericUtils.sortableBytesToLong(encoded, 0));
assertEquals(Double.doubleToLongBits(value), Double.doubleToLongBits(actual));
}
}
/** test round-trip encoding of random big integers */
public void testBigIntsRoundTrip() {
for (int i = 0; i < 10000; i++) {
BigInteger value = TestUtil.nextBigInteger(random(), 16);
int length = value.toByteArray().length;
// make sure sign extension is tested: sometimes pad to more bytes when encoding.
int maxLength = TestUtil.nextInt(random(), length, length + 3);
byte[] encoded = new byte[maxLength];
NumericUtils.bigIntToSortableBytes(value, maxLength, encoded, 0);
assertEquals(value, NumericUtils.sortableBytesToBigInt(encoded, 0, maxLength));
}
}
/** check sort order of random integers consistent with Integer.compare */
public void testIntsCompare() {
BytesRef left = new BytesRef(new byte[Integer.BYTES]);
BytesRef right = new BytesRef(new byte[Integer.BYTES]);
for (int i = 0; i < 10000; i++) {
int leftValue = random().nextInt();
NumericUtils.intToSortableBytes(leftValue, left.bytes, left.offset);
int rightValue = random().nextInt();
NumericUtils.intToSortableBytes(rightValue, right.bytes, right.offset);
assertEquals(Integer.signum(Integer.compare(leftValue, rightValue)),
Integer.signum(left.compareTo(right)));
}
}
/** check sort order of random longs consistent with Long.compare */
public void testLongsCompare() {
BytesRef left = new BytesRef(new byte[Long.BYTES]);
BytesRef right = new BytesRef(new byte[Long.BYTES]);
for (int i = 0; i < 10000; i++) {
long leftValue = TestUtil.nextLong(random(), Long.MIN_VALUE, Long.MAX_VALUE);
NumericUtils.longToSortableBytes(leftValue, left.bytes, left.offset);
long rightValue = TestUtil.nextLong(random(), Long.MIN_VALUE, Long.MAX_VALUE);
NumericUtils.longToSortableBytes(rightValue, right.bytes, right.offset);
assertEquals(Integer.signum(Long.compare(leftValue, rightValue)),
Integer.signum(left.compareTo(right)));
}
}
/** check sort order of random floats consistent with Float.compare */
public void testFloatsCompare() {
BytesRef left = new BytesRef(new byte[Float.BYTES]);
BytesRef right = new BytesRef(new byte[Float.BYTES]);
for (int i = 0; i < 10000; i++) {
float leftValue = Float.intBitsToFloat(random().nextInt());
NumericUtils.intToSortableBytes(NumericUtils.floatToSortableInt(leftValue), left.bytes, left.offset);
float rightValue = Float.intBitsToFloat(random().nextInt());
NumericUtils.intToSortableBytes(NumericUtils.floatToSortableInt(rightValue), right.bytes, right.offset);
assertEquals(Integer.signum(Float.compare(leftValue, rightValue)),
Integer.signum(left.compareTo(right)));
}
}
/** check sort order of random doubles consistent with Double.compare */
public void testDoublesCompare() {
BytesRef left = new BytesRef(new byte[Double.BYTES]);
BytesRef right = new BytesRef(new byte[Double.BYTES]);
for (int i = 0; i < 10000; i++) {
double leftValue = Double.longBitsToDouble(TestUtil.nextLong(random(), Long.MIN_VALUE, Long.MAX_VALUE));
NumericUtils.longToSortableBytes(NumericUtils.doubleToSortableLong(leftValue), left.bytes, left.offset);
double rightValue = Double.longBitsToDouble(TestUtil.nextLong(random(), Long.MIN_VALUE, Long.MAX_VALUE));
NumericUtils.longToSortableBytes(NumericUtils.doubleToSortableLong(rightValue), right.bytes, right.offset);
assertEquals(Integer.signum(Double.compare(leftValue, rightValue)),
Integer.signum(left.compareTo(right)));
}
}
/** check sort order of random bigintegers consistent with BigInteger.compareTo */
public void testBigIntsCompare() {
for (int i = 0; i < 10000; i++) {
int maxLength = TestUtil.nextInt(random(), 1, 16);
BigInteger leftValue = TestUtil.nextBigInteger(random(), maxLength);
BytesRef left = new BytesRef(new byte[maxLength]);
NumericUtils.bigIntToSortableBytes(leftValue, maxLength, left.bytes, left.offset);
BigInteger rightValue = TestUtil.nextBigInteger(random(), maxLength);
BytesRef right = new BytesRef(new byte[maxLength]);
NumericUtils.bigIntToSortableBytes(rightValue, maxLength, right.bytes, right.offset);
assertEquals(Integer.signum(leftValue.compareTo(rightValue)),
Integer.signum(left.compareTo(right)));
}
}
}