I'm looking for the fastest way to parse a hex string representing a ulong into a uint keeping as many leading digits as a uint can handle and discarding the rest. For example,
string hex = "0xab54a9a1df8a0edb"; // 12345678991234567899 Should output: uint result = 1234567899;
I can do this by simply parsing the hex into a ulong, getting the digits using ToString and then just taking as many of them as would fit into uint without overflowing but I need something much faster. Thanks. C# code preferred but any would do.
For decimal truncation, all the high bits of the hex digit affect the low 9 or 10 decimal digits, so you need to convert the whole thing. Is there an algorithm to convert massive hex string to bytes stream QUICKLY? asm/C/C++ has C++ with SSE intrinsics. I commented there with some possible improvements to that, and to https://github.com/zbjornson/fast-hex . This could be especially good if you're using SIMD to find numeric literals in larger buffers, so you might have the hex string in a SIMD register already. (Not sure if SIMDJSON does that.)
Hex-string to 64-bit integer is something SIMD certainly can speed up, e.g. do something to map each digit to a 0-15 integer, combine pairs of bytes to pack nibbles (e.g. with x86 pmaddubsw
), then shuffle those 8-bit chunks to the bottom of a register. (e.g. packuswb
or pshufb
). x86 at least has efficient SIMD to GP-integer movq rax, xmm0
, although the ARM equivalent is slow on some ARM CPUs.
(Getting a speedup from SIMD for ASCII hex -> uint is much easier if your strings are fixed-length, and probably if you don't need to check for invalid characters that aren't hex digits.)
u64
(C# ulong
) to fit in u32
(C# uint
)Modulo by a power of 10 truncates to some number of decimal digits.
(uint)(x % 10000000000)
works for some numbers, but 10000000000 (1e10 = one followed by 10 zeros) is larger than 2^32-1. Consider an input like 0x2540be3ff
(9999999999
). We'd get (uint)9999999999
producing 1410065407 = 0x540be3ff
(keeping the low 32 bits of that 34-bit number.)
So perhaps try modulo 1e10, but if it's too big for u32 then modulo 1e9.
ulong tendigit = x % 10000000000; // 1e10
uint truncated = tendigit <= (ulong)0xffffffff ? tendigit : (x % 1000000000); // % 1e9 keeps 9 decimal digits
If this isn't correct C# syntax or the literals need some decoration to make them ulong
(like C 10000000000uLL
for good measure), please let me know.
It's probably at least as efficient to just modulo the original number two different ways than to try to get the leading decimal digit of x % 1e10
and subtract it or whatever. The asm is going to need two 64-bit multiplicative inverse constants, and starting from the original number again keeps critical-path latency shorter for out-of-order exec if branch prediction predicts that it needs to calculate the nine-digit truncation.
@Matthew Whited deleted his answer (due to a bug in the decimal truncation part), but his binary truncation part based on substrings of the original hex input could perhaps be more efficient in some cases than doing the full conversion and then casting to a narrower type or masking with AND.
If you want the last 8 bytes of the hex string
uint.Parse(hex[^8..],NumberStyles.HexNumber)
If you want the first 8 bytes
uint.Parse(hex[2..10], NumberStyles.HexNumber);