Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Java] Fix digit count for zero. #251

Merged
merged 2 commits into from Nov 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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