diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastIntegerMath.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastIntegerMath.java index fb5022c4..d77f9f3e 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastIntegerMath.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastIntegerMath.java @@ -129,7 +129,7 @@ static void fillPowersOfNFloor16Recursive(NavigableMap powe return; } // recursion case: - int mid = splitFloor16(from, to); + int mid = splitFloor16(numDigits, to); int n = to - mid; if (!powersOfTen.containsKey(n)) { fillPowersOfNFloor16Recursive(powersOfTen, from, mid); @@ -156,10 +156,16 @@ static UInt128 fullMultiplication(long x, long y) {//since Java 18 return new UInt128(Math.unsignedMultiplyHigh(x, y), x * y); } - static int splitFloor16(int from, int to) { - int mid = (from + to) >>> 1;// split in half - mid = to - (((to - mid + 15) >> 4) << 4);// make numDigits of low a multiple of 16 - return mid; + /** Finds middle of range with upper range half rounded up to multiple of 16. + * + * @param length interval length + * @param to interval end + * @return middle of range with upper range half rounded up to multiple of 16 + */ + static int splitFloor16(int length, int to) { + // divide length by 2 as we want the middle, round up range half to multiples of 16 + int range = (((length + 31) >>> 5) << 4); + return to - range; } static class UInt128 { diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/ParseDigitsTaskByteArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/ParseDigitsTaskByteArray.java index fb3039f4..e165deec 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/ParseDigitsTaskByteArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/ParseDigitsTaskByteArray.java @@ -74,7 +74,7 @@ static BigInteger parseDigitsRecursive(byte[] str, int from, int to, Map splitFloor16testData() { + return Stream.of(// from, to, expectedMid + Arguments.of(0, 0, 0), + Arguments.of(0, 16, 0), + Arguments.of(10, 30, 14), + Arguments.of(0, 32, 16), + Arguments.of(10, 40, 24), + Arguments.of(0, 100, 36), + Arguments.of(1, 101, 37) + ); + } + + @ParameterizedTest + @MethodSource("splitFloor16testData") + void splitFloor16specialValues(int from, int to, int expectedMid) { + assert (to - expectedMid) % 16 == 0; + assert expectedMid <= from + (to - from) / 2; + assert expectedMid >= from + (to - from) / 2 - 15; + + assertEquals(expectedMid, FastIntegerMath.splitFloor16(to - from, to)); + } + + @Test + void splitFloor16() { + for (int from = 0; from < 50; from++) { + for (int to = from; to < 100; to++) { + int actual = FastIntegerMath.splitFloor16(to - from, to); + assertEquals(0, (to - actual) % 16); + assertTrue(actual <= from + (to - from) / 2); + assertTrue(actual >= from + (to - from) / 2 - 15); + + assertEquals(oldSplitFloor16(from, to), actual); + } + } + } + + static int oldSplitFloor16(int from, int to) { + int mid = (from + to) >>> 1;// split in half + mid = to - (((to - mid + 15) >> 4) << 4);// make numDigits of low a multiple of 16 + return mid; + } + } diff --git a/fastdoubleparser-dev/src/test/java/ch/randelshofer/fastdoubleparser/JmhSplitFloor16.java b/fastdoubleparser-dev/src/test/java/ch/randelshofer/fastdoubleparser/JmhSplitFloor16.java new file mode 100644 index 00000000..8a8d5cfe --- /dev/null +++ b/fastdoubleparser-dev/src/test/java/ch/randelshofer/fastdoubleparser/JmhSplitFloor16.java @@ -0,0 +1,51 @@ +/* + * @(#)JmhSplitFloor16.java + * Copyright © 2023 Werner Randelshofer, Switzerland. MIT License. + */ +package ch.randelshofer.fastdoubleparser; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 1) +@Measurement(iterations = 2, time = 1) +@Warmup(iterations = 2, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class JmhSplitFloor16 { + private static final int TO_MAX = 1024 + 1; + private static final int FROM_MAX = 100; + private static final int DATA_LENGTH = TO_MAX - FROM_MAX; + + @Benchmark + @OperationsPerInvocation(DATA_LENGTH) + public void oldSplitFloor16(Blackhole blackhole) { + for (int i = 0; i < FROM_MAX; i++) { + for (int j = i; j < TO_MAX; j++) { + blackhole.consume(FastIntegerMathTest.oldSplitFloor16(i, j)); + } + } + } + @Benchmark + @OperationsPerInvocation(DATA_LENGTH) + public void splitFloor16(Blackhole blackhole) { + for (int i = 0; i < FROM_MAX; i++) { + for (int j = i; j < TO_MAX; j++) { + blackhole.consume(FastIntegerMath.splitFloor16(i, j)); + } + } + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(JmhSplitFloor16.class.getSimpleName()) + .build(); + new Runner(opt).run(); + } +}