Skip to content

Commit

Permalink
[Java] Fix digit count for zero. (#251)
Browse files Browse the repository at this point in the history
* [Java] Fix digitCount(0) to return 1.

* [Java] Generate lookup tables for digitCount functions upon startup.
  • Loading branch information
vyazelenko committed Nov 18, 2021
1 parent 5b784ed commit 4d75d85
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 83 deletions.
77 changes: 57 additions & 20 deletions agrona/src/main/java/org/agrona/AsciiEncoding.java
Expand Up @@ -15,6 +15,8 @@
*/
package org.agrona;

import java.math.BigInteger;

import static java.nio.charset.StandardCharsets.US_ASCII;

/**
Expand Down Expand Up @@ -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()
{
}
Expand Down Expand Up @@ -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);
}

Expand Down
81 changes: 18 additions & 63 deletions agrona/src/test/java/org/agrona/AsciiEncodingTest.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 4d75d85

Please sign in to comment.