/* * 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(10000); 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(10000); 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))); } } }