diff --git a/agrona/src/main/java/org/agrona/AsciiEncoding.java b/agrona/src/main/java/org/agrona/AsciiEncoding.java index 6b442160..6ac34503 100644 --- a/agrona/src/main/java/org/agrona/AsciiEncoding.java +++ b/agrona/src/main/java/org/agrona/AsciiEncoding.java @@ -15,6 +15,8 @@ */ package org.agrona; +import java.math.BigInteger; + import static java.nio.charset.StandardCharsets.US_ASCII; /** @@ -94,31 +96,66 @@ public final class AsciiEncoding */ public static final int[] LONG_MIN_VALUE_DIGITS = new int[]{ 92233720, 36854775, 808 }; - private static final long[] INT_DIGITS = + /** + * Power of ten for int values. + */ + public static final int[] INT_POW_10 = { - 4294967295L, 8589934582L, 8589934582L, 8589934582L, 12884901788L, 12884901788L, 12884901788L, 17179868184L, - 17179868184L, 17179868184L, 21474826480L, 21474826480L, 21474826480L, 21474826480L, 25769703776L, 25769703776L, - 25769703776L, 30063771072L, 30063771072L, 30063771072L, 34349738368L, 34349738368L, 34349738368L, 34349738368L, - 38554705664L, 38554705664L, 38554705664L, 41949672960L, 41949672960L, 41949672960L, 42949672960L, 42949672960L + 1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000 }; - private static final long[] LONG_DIGITS = + /** + * Power of ten for long values. + */ + public static final long[] LONG_POW_10 = { - 4503599627370495L, 9007199254740982L, 9007199254740982L, 9007199254740982L, 13510798882111438L, - 13510798882111438L, 13510798882111438L, 18014398509481484L, 18014398509481734L, 18014398509481734L, - 22517998136849980L, 22517998136849980L, 22517998136851230L, 22517998136851230L, 27021597764210476L, - 27021597764210476L, 27021597764216726L, 31525197391530972L, 31525197391530972L, 31525197391530972L, - 36028797018651468L, 36028797018651468L, 36028797018651468L, 36028797018651468L, 40532396644771964L, - 40532396644771964L, 40532396644771964L, 45035996258079960L, 45035996265892460L, 45035996265892460L, - 49539595822950456L, 49539595822950456L, 49539595862012956L, 49539595862012956L, 54043195137820952L, - 54043195137820952L, 54043195333133452L, 58546793202691448L, 58546793202691448L, 58546793202691448L, - 63050385017561944L, 63050385017561944L, 63050385017561944L, 63050385017561944L, 67553945582432440L, - 67553945582432440L, 67553945582432440L, 72057105756677936L, 72057349897302936L, 72057349897302936L, - 76558752259048432L, 76558752259048432L, 76559972962173432L, 76559972962173432L, 81052586261418928L, - 81052586261418928L, 81058689777043928L, 85507357763789424L, 85507357763789424L, 85507357763789424L, - 89766816766159920L, 89766816766159920L, 89766816766159920L, 89766816766159920L + 1L, 10L, 100L, 1_000L, 10_000L, 100_000L, 1_000_000L, 10_000_000L, 100_000_000L, 1_000_000_000L, + 10_000_000_000L, 100_000_000_000L, 1_000_000_000_000L, 10_000_000_000_000L, 100_000_000_000_000L, + 1_000_000_000_000_000L, 10_000_000_000_000_000L, 100_000_000_000_000_000L, 1_000_000_000_000_000_000L }; + private static final long[] INT_DIGITS = new long[32]; + + private static final long[] LONG_DIGITS = new long[64]; + + static + { + for (int i = 1; i < 33; i++) + { + final int smallest = 1 << (i - 1); + final long smallestLog10 = (long)Math.ceil(Math.log10(smallest) / Math.log10(10)); + if (1 == i) + { + INT_DIGITS[i - 1] = 1L << 32; + } + else if (i < 31) + { + INT_DIGITS[i - 1] = (1L << 32) - LONG_POW_10[(int)smallestLog10] + (smallestLog10 << 32); + } + else + { + INT_DIGITS[i - 1] = smallestLog10 << 32; + } + } + + final BigInteger tenToNineteen = BigInteger.TEN.pow(19); + for (int i = 0; i < 64; i++) + { + if (0 == i) + { + LONG_DIGITS[i] = 1L << 52; + } + else + { + final int upper = ((i * 1262611) >> 22) + 1; + final long correction = upper < LONG_MAX_DIGITS ? LONG_POW_10[upper] >> (i >> 2) : + tenToNineteen.shiftRight(i >> 2).longValueExact(); + final long value = ((long)(upper + 1) << 52) - correction; + LONG_DIGITS[i] = value; + } + } + } + private AsciiEncoding() { } @@ -191,7 +228,7 @@ public static int digitCount(final int value) */ public static int digitCount(final long value) { - final int floorLog2 = 63 ^ Long.numberOfLeadingZeros(value); + final int floorLog2 = 63 ^ Long.numberOfLeadingZeros(value | 1); return (int)((LONG_DIGITS[floorLog2] + (value >> (floorLog2 >> 2))) >> 52); } diff --git a/agrona/src/test/java/org/agrona/AsciiEncodingTest.java b/agrona/src/test/java/org/agrona/AsciiEncodingTest.java index 195dfec8..59553c50 100644 --- a/agrona/src/test/java/org/agrona/AsciiEncodingTest.java +++ b/agrona/src/test/java/org/agrona/AsciiEncodingTest.java @@ -16,14 +16,11 @@ package org.agrona; import org.agrona.concurrent.UnsafeBuffer; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.math.BigInteger; import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.IntStream; import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN; @@ -175,75 +172,33 @@ void shouldThrowExceptionWhenParsingEmptyLong(final int length) @Test void digitCountIntValue() { - final int[] values = + for (int i = 0; i < INT_MAX_DIGITS; i++) { - 9, 99, 999, 9_999, 99_999, 999_999, 9_999_999, 99_999_999, 999_999_999, Integer.MAX_VALUE - }; - - for (int i = 0; i < values.length; i++) - { - assertEquals(i + 1, digitCount(values[i])); + final int min = 0 == i ? 0 : INT_POW_10[i]; + final int max = INT_MAX_DIGITS - 1 == i ? Integer.MAX_VALUE : INT_POW_10[i + 1] - 1; + final int expectedDigitCount = i + 1; + assertEquals(expectedDigitCount, digitCount(min)); + assertEquals(expectedDigitCount, digitCount(min + 1)); + assertEquals(expectedDigitCount, digitCount(min + (max - min) >>> 1)); + assertEquals(expectedDigitCount, digitCount(max - 1)); + assertEquals(expectedDigitCount, digitCount(max)); } } @Test void digitCountLongValue() { - final long[] values = - { - 9, 99, 999, 9_999, 99_999, 999_999, 9_999_999, 99_999_999, 999_999_999, 9_999_999_999L, - 99_999_999_999L, 999_999_999_999L, 9_999_999_999_999L, 99_999_999_999_999L, 999999_999999999L, - 9_999_999_999_999_999L, 99_999_999_999_999_999L, 999_999_999_999_999_999L, Long.MAX_VALUE - }; - - for (int i = 0; i < values.length; i++) + for (int i = 0; i < LONG_MAX_DIGITS; i++) { - final int iter = i; - assertEquals(i + 1, digitCount(values[i]), () -> iter + " -> " + values[iter]); - } - } - - // prints a lookup table for org.agrona.AsciiEncoding.digitCount(int) - @Test - @Disabled - void printDigitCountIntTable() - { - for (int i = 1; i < 33; i++) - { - final double smallest = Math.pow(2, i - 1); - final long log10 = (long)Math.ceil(Math.log10(smallest)); - final long value = (long)((i < 31 ? (Math.pow(2, 32) - Math.pow(10, log10)) : 0) + (log10 << 32)); - if (i != 1) - { - System.out.println(","); - } - System.out.print(value); - System.out.print("L"); - } - System.out.println(); - } - - // prints a lookup table for org.agrona.AsciiEncoding.digitCount(long) - @Test - @Disabled - void printDigitCountLongTable() - { - final BigInteger[] pow10 = IntStream.rangeClosed(0, 19) - .mapToObj(BigInteger.TEN::pow) - .toArray(BigInteger[]::new); - - for (int i = 0; i < 64; i++) - { - final int upper = i == 0 ? 0 : ((i * 1262611) >> 22) + 1; - final long value = ((long)(upper + 1) << 52) - pow10[upper].shiftRight(i / 4).longValue(); - if (i != 0) - { - System.out.println(","); - } - System.out.print(value); - System.out.print("L"); + final long min = 0 == i ? 0 : LONG_POW_10[i]; + final long max = LONG_MAX_DIGITS - 1 == i ? Long.MAX_VALUE : LONG_POW_10[i + 1] - 1; + final int expectedDigitCount = i + 1; + assertEquals(expectedDigitCount, digitCount(min)); + assertEquals(expectedDigitCount, digitCount(min + 1)); + assertEquals(expectedDigitCount, digitCount(min + (max - min) >>> 1)); + assertEquals(expectedDigitCount, digitCount(max - 1)); + assertEquals(expectedDigitCount, digitCount(max)); } - System.out.println(); } @Test