ch.randelshofer.fastdoubleparser.FastDoubleSwar Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fastdoubleparser Show documentation
Show all versions of fastdoubleparser Show documentation
A Java port of Daniel Lemire's fast_float project.
/*
* @(#)FastDoubleSwar.java
* Copyright © 2023 Werner Randelshofer, Switzerland. MIT License.
*/
package ch.randelshofer.fastdoubleparser;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
/**
* This class provides methods for parsing multiple characters at once using
* the "SIMD with a register" (SWAR) technique.
*
* References:
*
* - Leslie Lamport, Multiple Byte Processing with Full-Word Instructions
* - azurewebsites.net
*
* - Daniel Lemire, fast_double_parser, 4x faster than strtod.
* Apache License 2.0 or Boost Software License.
* - github.com
*
* - Daniel Lemire, fast_float number parsing library: 4x faster than strtod.
* Apache License 2.0.
* - github.com
*
* - Daniel Lemire, Number Parsing at a Gigabyte per Second,
* Software: Practice and Experience 51 (8), 2021.
* arXiv.2101.11408v3 [cs.DS] 24 Feb 2021
* - arxiv.org
*
*
*/
class FastDoubleSwar {
private final static VarHandle readLongLE =
MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN).withInvokeExactBehavior();
private final static VarHandle readIntLE =
MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN).withInvokeExactBehavior();
private final static VarHandle readIntBE =
MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN).withInvokeExactBehavior();
private final static VarHandle readLongBE =
MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN).withInvokeExactBehavior();
/**
* Checks if '0' <= c && c <= '9'.
*
* @param c a character
* @return true if c is a digit
*/
protected static boolean isDigit(char c) {
// We take advantage of the fact that char is an unsigned numeric type:
// subtracted values wrap around.
return (char) (c - '0') < 10;
}
/**
* Checks if '0' <= c && c <= '9'.
*
* @param c a character
* @return true if c is a digit
*/
protected static boolean isDigit(byte c) {
// We check if '0' <= c && c <= '9'.
// We take advantage of the fact that char is an unsigned numeric type:
// subtracted values wrap around.
return (char) (c - '0') < 10;
}
public static boolean isEightDigits(byte[] a, int offset) {
return isEightDigitsUtf8((long) readLongLE.get(a, offset));
}
/**
* Checks if the string contains eight digits at the specified
* offset.
*
* @param a a string
* @param offset offset into string
* @return true if eight digits
* @throws IndexOutOfBoundsException if offset is larger than 2^29.
*/
public static boolean isEightDigits(char[] a, int offset) {
long first = a[offset]
| (long) a[offset + 1] << 16
| (long) a[offset + 2] << 32
| (long) a[offset + 3] << 48;
long second = a[offset + 4]
| (long) a[offset + 5] << 16
| (long) a[offset + 6] << 32
| (long) a[offset + 7] << 48;
return isEightDigitsUtf16(first, second);
}
public static boolean isEightDigits(CharSequence a, int offset) {
boolean success = true;
for (int i = 0; i < 8; i++) {
char ch = a.charAt(i + offset);
success &= isDigit(ch);
}
return success;
}
public static boolean isEightDigitsUtf16(long first, long second) {
long fval = first - 0x0030_0030_0030_0030L;
long sval = second - 0x0030_0030_0030_0030L;
// Create a predicate for all bytes which are smaller than '0' (0x0030)
// or greater than '9' (0x0039).
// We have 0x007f - 0x0039 = 0x0046.
// The predicate is true if the hsb of a byte is set: (predicate & 0xff80) != 0.
long fpre = first + 0x0046_0046_0046_0046L | fval;
long spre = second + 0x0046_0046_0046_0046L | sval;
return ((fpre | spre) & 0xff80_ff80_ff80_ff80L) == 0L;
}
public static boolean isEightDigitsUtf8(long chunk) {
long val = chunk - 0x3030303030303030L;
long predicate = ((chunk + 0x4646464646464646L) | val) & 0x8080808080808080L;
return predicate == 0L;
}
public static boolean isEightZeroes(byte[] a, int offset) {
return isEightZeroesUtf8((long) readLongLE.get(a, offset));
}
public static boolean isEightZeroes(CharSequence a, int offset) {
boolean success = true;
for (int i = 0; i < 8; i++) {
success &= '0' == a.charAt(i + offset);
}
return success;
}
/**
* Checks if the string contains eight zeroes at the specified
* offset.
*
* @param a a string
* @param offset offset into string
* @return true if eight digits
* @throws IndexOutOfBoundsException if offset is larger than 2^29.
*/
public static boolean isEightZeroes(char[] a, int offset) {
long first = a[offset]
| (long) a[offset + 1] << 16
| (long) a[offset + 2] << 32
| (long) a[offset + 3] << 48;
long second = a[offset + 4]
| (long) a[offset + 5] << 16
| (long) a[offset + 6] << 32
| (long) a[offset + 7] << 48;
return isEightZeroesUtf16(first, second);
}
public static boolean isEightZeroesUtf16(long first, long second) {
return first == 0x0030_0030_0030_0030L
&& second == 0x0030_0030_0030_0030L;
}
public static boolean isEightZeroesUtf8(long chunk) {
return chunk == 0x3030303030303030L;
}
public static int readIntBE(byte[] a, int offset) {
return (int) readIntBE.get(a, offset);
}
public static long readLongBE(byte[] a, int offset) {
return (long) readLongBE.get(a, offset);
}
public static long readLongLE(byte[] a, int offset) {
return (long) readLongLE.get(a, offset);
}
/**
* Tries to parse eight decimal digits from a char array using the
* 'SIMD within a register technique' (SWAR).
*
* @param a contains 8 utf-16 characters starting at offset
* @param offset the offset into the array
* @return the parsed number,
* returns a negative value if {@code value} does not contain 8 hex digits
* @throws IndexOutOfBoundsException if offset is larger than 2^
*/
public static int tryToParseEightDigits(char[] a, int offset) {
long first = a[offset]
| (long) a[offset + 1] << 16
| (long) a[offset + 2] << 32
| (long) a[offset + 3] << 48;
long second = a[offset + 4]
| (long) a[offset + 5] << 16
| (long) a[offset + 6] << 32
| (long) a[offset + 7] << 48;
return FastDoubleSwar.tryToParseEightDigitsUtf16(first, second);
}
public static int tryToParseEightDigits(byte[] a, int offset) {
return FastDoubleSwar.tryToParseEightDigitsUtf8((long) readLongLE.get(a, offset));
}
/**
* Tries to parse eight digits at once using the
* 'SIMD within a register technique' (SWAR).
*
* @param str a character sequence
* @param offset the index of the first character in the character sequence
* @return the parsed digits or -1
*/
public static int tryToParseEightDigits(CharSequence str, int offset) {
long first = str.charAt(offset)
| (long) str.charAt(offset + 1) << 16
| (long) str.charAt(offset + 2) << 32
| (long) str.charAt(offset + 3) << 48;
long second = str.charAt(offset + 4)
| (long) str.charAt(offset + 5) << 16
| (long) str.charAt(offset + 6) << 32
| (long) str.charAt(offset + 7) << 48;
return FastDoubleSwar.tryToParseEightDigitsUtf16(first, second);
}
/**
* Tries to parse eight decimal digits at once using the
* 'SIMD within a register technique' (SWAR).
*
* {@literal
* char[] chars = ...;
* long first = chars[0]|(chars[1]<<16)|(chars[2]<<32)|(chars[3]<<48);
* long second = chars[4]|(chars[5]<<16)|(chars[6]<<32)|(chars[7]<<48);
* }
*
* @param first the first four characters in big endian order
* @param second the second four characters in big endian order
* @return the parsed digits or -1
*/
public static int tryToParseEightDigitsUtf16(long first, long second) {
long fval = first - 0x0030_0030_0030_0030L;
long sval = second - 0x0030_0030_0030_0030L;
// Create a predicate for all bytes which are smaller than '0' (0x0030)
// or greater than '9' (0x0039).
// We have 0x007f - 0x0039 = 0x0046.
// The predicate is true if the hsb of a byte is set: (predicate & 0xff80) != 0.
long fpre = first + 0x0046_0046_0046_0046L | fval;
long spre = second + 0x0046_0046_0046_0046L | sval;
if (((fpre | spre) & 0xff80_ff80_ff80_ff80L) != 0L) {
return -1;
}
return (int) (sval * 0x03e8_0064_000a_0001L >>> 48)
+ (int) (fval * 0x03e8_0064_000a_0001L >>> 48) * 10000;
}
/**
* Tries to parse eight decimal digits from a byte array using the
* 'SIMD within a register technique' (SWAR).
*
* @param a contains 8 ascii characters
* @param offset the offset of the first character in {@code a}
* @return the parsed number,
* returns a negative value if {@code value} does not contain 8 digits
*/
public static int tryToParseEightDigitsUtf8(byte[] a, int offset) {
return tryToParseEightDigitsUtf8((long) readLongLE.get(a, offset));
}
/**
* Tries to parse eight digits from a long using the
* 'SIMD within a register technique' (SWAR).
*
* {@literal
* byte[] bytes = ...;
* long value = ((bytes[7]&0xffL)<<56)
* | ((bytes[6]&0xffL)<<48)
* | ((bytes[5]&0xffL)<<40)
* | ((bytes[4]&0xffL)<<32)
* | ((bytes[3]&0xffL)<<24)
* | ((bytes[2]&0xffL)<<16)
* | ((bytes[1]&0xffL)<< 8)
* | (bytes[0]&0xffL);
* }
*
* @param chunk contains 8 ascii characters in little endian order
* @return the parsed number, or a value < 0 if not all characters are
* digits.
*/
public static int tryToParseEightDigitsUtf8(long chunk) {
// Subtract the character '0' from all characters.
long val = chunk - 0x3030303030303030L;
// Create a predicate for all bytes which are greater than '0' (0x30).
// The predicate is true if the hsb of a byte is set: (predicate & 0x80) != 0.
long predicate = ((chunk + 0x4646464646464646L) | val) & 0x8080808080808080L;
if (predicate != 0L) {
return -1;
}
// The last 2 multiplications are independent of each other.
long mask = 0xff_000000ffL;
long mul1 = 100 + (100_0000L << 32);
long mul2 = 1 + (1_0000L << 32);
val = val * 10 + (val >>> 8);// same as: val = val * (1 + (10 << 8)) >>> 8;
val = (val & mask) * mul1 + (val >>> 16 & mask) * mul2 >>> 32;
return (int) val;
}
/**
* Tries to parse eight digits at once using the
* 'SIMD within a register technique' (SWAR).
*
* @param str a character sequence
* @param offset the index of the first character in the character sequence
* @return the parsed digits or -1
*/
public static long tryToParseEightHexDigits(CharSequence str, int offset) {
long first = (long) str.charAt(offset) << 48
| (long) str.charAt(offset + 1) << 32
| (long) str.charAt(offset + 2) << 16
| (long) str.charAt(offset + 3);
long second = (long) str.charAt(offset + 4) << 48
| (long) str.charAt(offset + 5) << 32
| (long) str.charAt(offset + 6) << 16
| (long) str.charAt(offset + 7);
return FastDoubleSwar.tryToParseEightHexDigitsUtf16(first, second);
}
/**
* Tries to parse eight hex digits from a char array using the
* 'SIMD within a register technique' (SWAR).
*
* @param chars contains 8 utf-16 characters starting at offset
* @param offset the offset into the array
* @return the parsed number,
* returns a negative value if {@code value} does not contain 8 hex digits
*/
public static long tryToParseEightHexDigits(char[] chars, int offset) {
long first = (long) chars[offset] << 48
| (long) chars[offset + 1] << 32
| (long) chars[offset + 2] << 16
| (long) chars[offset + 3];
long second = (long) chars[offset + 4] << 48
| (long) chars[offset + 5] << 32
| (long) chars[offset + 6] << 16
| (long) chars[offset + 7];
return FastDoubleSwar.tryToParseEightHexDigitsUtf16(first, second);
}
/**
* Tries to parse eight hex digits from a byte array using the
* 'SIMD within a register technique' (SWAR).
*
* @param a contains 8 ascii characters
* @param offset the offset of the first character in {@code a}
* returns a negative value if {@code value} does not contain 8 digits
*/
public static long tryToParseEightHexDigits(byte[] a, int offset) {
return tryToParseEightHexDigitsUtf8((long) readLongBE.get(a, offset));
}
/**
* Tries to parse eight hex digits from two longs using the
* 'SIMD within a register technique' (SWAR).
*
* {@code
* char[] chars = ...;
* long first = (long) chars[0] << 48
* | (long) chars[1] << 32
* | (long) chars[2] << 16
* | (long) chars[3];
*
* long second = (long) chars[4] << 48
* | (long) chars[5] << 32
* | (long) chars[6] << 16
* | (long) chars[7];
* }
*
* @param first contains 4 utf-16 characters in big endian order
* @param second contains 4 utf-16 characters in big endian order
* @return the parsed number,
* returns a negative value if the two longs do not contain 8 hex digits
*/
public static long tryToParseEightHexDigitsUtf16(long first, long second) {
long highBytes = (first | second) & 0xff80ff80_ff80ff80L;
if (highBytes != 0L) {
return -1L;
}
long utf8Bytes = (Long.compress(first, 0x00ff00ff_00ff00ffL) << 32)
| Long.compress(second, 0x00ff00ff_00ff00ffL);
return tryToParseEightHexDigitsUtf8(utf8Bytes);
}
/**
* Tries to parse eight digits from a long using the
* 'SIMD within a register technique' (SWAR).
*
* @param chunk contains 8 ascii characters in big endian order
* @return the parsed number,
* returns a negative value if {@code value} does not contain 8 digits
*/
public static long tryToParseEightHexDigitsUtf8(long chunk) {
// The following code is based on the technique presented in the paper
// by Leslie Lamport.
// We can convert upper case characters to lower case by setting the 0x20 bit.
// (This does not have an impact on decimal digits, which is very handy!).
// Subtract character '0' (0x30) from each of the eight characters
long vec = (chunk | 0x20_20_20_20_20_20_20_20L) - 0x30_30_30_30_30_30_30_30L;
// Create a predicate for all bytes which are greater than '9'-'0' (0x09).
// The predicate is true if the hsb of a byte is set: (predicate & 0x80) != 0.
long gt_09 = vec + (0x09_09_09_09_09_09_09_09L ^ 0x7f_7f_7f_7f_7f_7f_7f_7fL);
gt_09 &= 0x80_80_80_80_80_80_80_80L;
// Create a predicate for all bytes which are greater or equal 'a'-'0' (0x30).
// The predicate is true if the hsb of a byte is set.
long ge_30 = vec + (0x30303030_30303030L ^ 0x7f_7f_7f_7f_7f_7f_7f_7fL);
ge_30 &= 0x80_80_80_80_80_80_80_80L;
// Create a predicate for all bytes which are smaller equal than 'f'-'0' (0x37).
long le_37 = 0x37_37_37_37_37_37_37_37L + (vec ^ 0x7f_7f_7f_7f_7f_7f_7f_7fL);
// we don't need to 'and' with 0x80…L here, because we 'and' this with ge_30 anyway.
//le_37 &= 0x80_80_80_80_80_80_80_80L;
// If a character is greater than '9' then it must be greater equal 'a'
// and smaller 'f'.
if (gt_09 != (ge_30 & le_37)) {
return -1;
}
// Expand the predicate to a byte mask
long gt_09mask = (gt_09 >>> 7) * 0xffL;
// Subtract 'a'-'0'+10 (0x27) from all bytes that are greater than 0x09.
long v = vec & ~gt_09mask | vec - (0x27272727_27272727L & gt_09mask);
// Compact all nibbles
return Long.compress(v, 0x0f0f0f0f_0f0f0f0fL);// since Java 19
}
public static int tryToParseFourDigits(char[] a, int offset) {
long first = a[offset]
| (long) a[offset + 1] << 16
| (long) a[offset + 2] << 32
| (long) a[offset + 3] << 48;
return FastDoubleSwar.tryToParseFourDigitsUtf16(first);
}
public static int tryToParseFourDigits(CharSequence str, int offset) {
long first = str.charAt(offset)
| (long) str.charAt(offset + 1) << 16
| (long) str.charAt(offset + 2) << 32
| (long) str.charAt(offset + 3) << 48;
return FastDoubleSwar.tryToParseFourDigitsUtf16(first);
}
public static int tryToParseFourDigits(byte[] a, int offset) {
return tryToParseFourDigitsUtf8((int) readIntLE.get(a, offset));
}
public static int tryToParseFourDigitsUtf16(long first) {
long fval = first - 0x0030_0030_0030_0030L;
// Create a predicate for all bytes which are smaller than '0' (0x0030)
// or greater than '9' (0x0039).
// We have 0x007f - 0x0039 = 0x0046.
// The predicate is true if the hsb of a byte is set: (predicate & 0xff80) != 0.
long fpre = first + 0x0046_0046_0046_0046L | fval;
if ((fpre & 0xff80_ff80_ff80_ff80L) != 0L) {
return -1;
}
return (int) (fval * 0x03e8_0064_000a_0001L >>> 48);
}
public static int tryToParseFourDigitsUtf8(int chunk) {
// Create a predicate for all bytes which are greater than '0' (0x30).
// The predicate is true if the hsb of a byte is set: (predicate & 0x80) != 0.
int val = chunk - 0x30303030;
int predicate = ((chunk + 0x46464646) | val) & 0x80808080;
if (predicate != 0L) {
return -1;//~(Integer.numberOfTrailingZeros(predicate)>>3);
}
// The last 2 multiplications are independent of each other.
val = val * (1 + (10 << 8)) >>> 8;
val = (val & 0xff) * 100 + ((val & 0xff0000) >> 16);
return val;
}
public static int tryToParseUpTo7Digits(byte[] str, int from, int to) {
int result = 0;
boolean success = true;
for (; from < to; from++) {
byte ch = str[from];
success &= isDigit(ch);
result = 10 * (result) + ch - '0';
}
return success ? result : -1;
}
public static int tryToParseUpTo7Digits(char[] str, int from, int to) {
int result = 0;
boolean success = true;
for (; from < to; from++) {
char ch = str[from];
success &= isDigit(ch);
result = 10 * (result) + ch - '0';
}
return success ? result : -1;
}
public static int tryToParseUpTo7Digits(CharSequence str, int from, int to) {
int result = 0;
boolean success = true;
for (; from < to; from++) {
char ch = str.charAt(from);
success &= isDigit(ch);
result = 10 * (result) + ch - '0';
}
return success ? result : -1;
}
public static void writeIntBE(byte[] a, int offset, int value) {
readIntBE.set(a, offset, value);
}
public static void writeLongBE(byte[] a, int offset, long value) {
readLongBE.set(a, offset, value);
}
public static double fma(double a, double b, double c) {
return Math.fma(a, b, c);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy