var
Higher, Lower: QWORD; // Lower contains 8 ASCII digits on entry
Dec( Lower, $3030303030303030 ); // convert to numbers 0-9
Higher := ( Lower * 10 ) shr 8; // no overflow possible
Lower := ( Lower and $00ff00ff00ff00ff ) + ( Higher and $00ff00ff00ff00ff );
Higher := ( Lower * 100 ) shr 16;
Lower := ( Lower and $0000ffff0000ffff ) + ( Higher and $0000ffff0000ffff );
Higher := ( Lower * 10000 ) shr 32;
Lower := ( Lower and $00000000ffffffff ) + Higher;
I put it in, but it did not help.
old run-time: 606 ms
with 3 mul (wrong): run-time: 624 ms
with 3 mul endian corrected: run-time: 667 ms
program Project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, bbutils, bbutilsbeta,bbdebugtools
{ you can add units after this };
function strTryParseInt(pstart, pend: pchar; out unsigned: UInt64): boolean;
var
length: SizeUInt;
begin
result := false;
if pend <= pstart then exit;
while (pstart < pend) and (pstart^ = '0') do inc(pstart);
length := pend - pstart;
if length > 20 then exit;
unsigned := 0;
while (pstart < pend) do begin
case pstart^ of
'0'..'9': unsigned := unsigned * 10 + UInt64(ord(pstart^) - ord('0'));
else exit;
end;
inc(pstart);
end;
if (length = 20) and (unsigned < 10000000000000000000) then exit;
result := true;
end;
{$AsmMode intel}
function PcharToInt(pstart, pend: pchar; out unsignedResult: UInt64): boolean;
const
selectFirstHalfByte8 = UInt64($F0F0F0F0F0F0F0F0);
decimalZeros8 = UInt64($3030303030303030);
overflowMaxDigit8 = UInt64($0606060606060606);
selectFirstHalfByte4 = UInt32($F0F0F0F0);
decimalZeros4 = UInt32($30303030);
overflowMaxDigit4 = UInt32($06060606);
var
length: SizeUInt;
temp8: UInt64;
temp4: UInt32;
bytes: pbyte;
unsigned: UInt64;
begin
result := false;
if pend <= pstart then exit;
while (pstart < pend) and (pstart^ = '0') do inc(pstart);
length := pend - pstart;
if length > 20 then exit;
if (length = 20) and (pstart^ >= '2') then exit;
unsigned := 0;
if PtrUInt(TObject(pstart)) and 7 = 0 then begin
while pstart + 8 <= pend do begin
temp8 := PUInt64(pstart)^;
if (temp8 and selectFirstHalfByte8) <> decimalZeros8 then exit;
temp8 := temp8 - decimalZeros8;
if ((temp8 + overflowMaxDigit8) and selectFirstHalfByte8) <> 0 then exit;
bytes := @temp8;
unsigned := unsigned * 100000000 + (((((((bytes[0] * 10) + bytes[1])* 10 + bytes[2])* 10 + bytes[3])* 10 + bytes[4])* 10 + bytes[5])* 10 + bytes[6])* 10 + bytes[7];
inc(pstart, 8);
end;
while pstart + 4 <= pend do begin
temp4 := PUInt32(pstart)^;
if (temp4 and selectFirstHalfByte4) <> decimalZeros4 then exit;
temp4 := temp4 - decimalZeros4;
if ((temp4 + overflowMaxDigit4) and selectFirstHalfByte4) <> 0 then exit;
bytes := @temp4;
unsigned := unsigned * 10000 + ((((bytes[0] * 10) + bytes[1])* 10 + bytes[2])* 10 + bytes[3]);
inc(pstart, 4);
end;
end;
while (pstart < pend) do begin
case pstart^ of
'0'..'9': unsigned := unsigned * 10 + UInt64(ord(pstart^) - ord('0'));
else exit;
end;
inc(pstart);
end;
if (length = 20) and (unsigned < 10000000000000000000) then exit;
result := true;
unsignedResult:=unsigned;
end;
function PcharToIntMM(pstart, pend: pchar; out unsignedResult: UInt64): boolean;
const
selectFirstHalfByte8 = UInt64($F0F0F0F0F0F0F0F0);
decimalZeros8 = UInt64($3030303030303030);
overflowMaxDigit8 = UInt64($0606060606060606);
selectFirstHalfByte4 = UInt32($F0F0F0F0);
decimalZeros4 = UInt32($30303030);
overflowMaxDigit4 = UInt32($06060606);
var
length: SizeUInt;
temp8: UInt64;
temp4: UInt32;
bytes: pbyte;
unsigned: UInt64;
var
Higher, Lower: QWORD; // Lower contains 8 ASCII digits on entry
begin
result := false;
if pend <= pstart then exit;
while (pstart < pend) and (pstart^ = '0') do inc(pstart);
length := pend - pstart;
if length > 20 then exit;
if (length = 20) and (pstart^ >= '2') then exit;
unsigned := 0;
if PtrUInt(TObject(pstart)) and 7 = 0 then begin
while pstart + 8 <= pend do begin
temp8 := PUInt64(pstart)^;
if (temp8 and selectFirstHalfByte8) <> decimalZeros8 then exit;
temp8 := temp8 - decimalZeros8;
if ((temp8 + overflowMaxDigit8) and selectFirstHalfByte8) <> 0 then exit;
// bytes := @temp8;
// unsigned := unsigned * 100000000 + (((((((bytes[0] * 10) + bytes[1])* 10 + bytes[2])* 10 + bytes[3])* 10 + bytes[4])* 10 + bytes[5])* 10 + bytes[6])* 10 + bytes[7];
unsigned := unsigned * 100000000;
lower := SwapEndian(temp8);
Higher := ( Lower * 10 ) shr 8; // no overflow possible
Lower := ( Lower and $00ff00ff00ff00ff ) + ( Higher and $00ff00ff00ff00ff );
Higher := ( Lower * 100 ) shr 16;
Lower := ( Lower and $0000ffff0000ffff ) + ( Higher and $0000ffff0000ffff );
Higher := ( Lower * 10000 ) shr 32;
unsigned += ( Lower and $00000000ffffffff ) + Higher;
inc(pstart, 8);
end;
while pstart + 4 <= pend do begin
temp4 := PUInt32(pstart)^;
if (temp4 and selectFirstHalfByte4) <> decimalZeros4 then exit;
temp4 := temp4 - decimalZeros4;
if ((temp4 + overflowMaxDigit4) and selectFirstHalfByte4) <> 0 then exit;
bytes := @temp4;
unsigned := unsigned * 10000 + ((((bytes[0] * 10) + bytes[1])* 10 + bytes[2])* 10 + bytes[3]);
inc(pstart, 4);
end;
end;
while (pstart < pend) do begin
case pstart^ of
'0'..'9': unsigned := unsigned * 10 + UInt64(ord(pstart^) - ord('0'));
else exit;
end;
inc(pstart);
end;
if (length = 20) and (unsigned < 10000000000000000000) then exit;
result := true;
unsignedResult:=unsigned;
end;
function PcharToIntMMWrong(pstart, pend: pchar; out unsignedResult: UInt64): boolean;
const
selectFirstHalfByte8 = UInt64($F0F0F0F0F0F0F0F0);
decimalZeros8 = UInt64($3030303030303030);
overflowMaxDigit8 = UInt64($0606060606060606);
selectFirstHalfByte4 = UInt32($F0F0F0F0);
decimalZeros4 = UInt32($30303030);
overflowMaxDigit4 = UInt32($06060606);
var
length: SizeUInt;
temp8: UInt64;
temp4: UInt32;
bytes: pbyte;
unsigned: UInt64;
var
Higher, Lower: QWORD; // Lower contains 8 ASCII digits on entry
begin
result := false;
if pend <= pstart then exit;
while (pstart < pend) and (pstart^ = '0') do inc(pstart);
length := pend - pstart;
if length > 20 then exit;
if (length = 20) and (pstart^ >= '2') then exit;
unsigned := 0;
if PtrUInt(TObject(pstart)) and 7 = 0 then begin
while pstart + 8 <= pend do begin
temp8 := PUInt64(pstart)^;
if (temp8 and selectFirstHalfByte8) <> decimalZeros8 then exit;
temp8 := temp8 - decimalZeros8;
if ((temp8 + overflowMaxDigit8) and selectFirstHalfByte8) <> 0 then exit;
// bytes := @temp8;
// unsigned := unsigned * 100000000 + (((((((bytes[0] * 10) + bytes[1])* 10 + bytes[2])* 10 + bytes[3])* 10 + bytes[4])* 10 + bytes[5])* 10 + bytes[6])* 10 + bytes[7];
unsigned := unsigned * 100000000;
lower := temp8;
Higher := ( Lower * 10 ) shr 8; // no overflow possible
Lower := ( Lower and $00ff00ff00ff00ff ) + ( Higher and $00ff00ff00ff00ff );
Higher := ( Lower * 100 ) shr 16;
Lower := ( Lower and $0000ffff0000ffff ) + ( Higher and $0000ffff0000ffff );
Higher := ( Lower * 10000 ) shr 32;
unsigned += ( Lower and $00000000ffffffff ) + Higher;
inc(pstart, 8);
end;
while pstart + 4 <= pend do begin
temp4 := PUInt32(pstart)^;
if (temp4 and selectFirstHalfByte4) <> decimalZeros4 then exit;
temp4 := temp4 - decimalZeros4;
if ((temp4 + overflowMaxDigit4) and selectFirstHalfByte4) <> 0 then exit;
bytes := @temp4;
unsigned := unsigned * 10000 + ((((bytes[0] * 10) + bytes[1])* 10 + bytes[2])* 10 + bytes[3]);
inc(pstart, 4);
end;
end;
while (pstart < pend) do begin
case pstart^ of
'0'..'9': unsigned := unsigned * 10 + UInt64(ord(pstart^) - ord('0'));
else exit;
end;
inc(pstart);
end;
if (length = 20) and (unsigned < 10000000000000000000) then exit;
result := true;
unsignedResult:=unsigned;
end;
var s: array of string = ('123', '456', '9223372036854775807', '9223372036854775808', '18446744073709551615');//, '18446744073709551616');
ss: array of shortstring = ('123', '456', '9223372036854775807', '9223372036854775808', '18446744073709551615');
var i, j, code: integer;
v: uint64;
t: String;
begin
i := 2;// high(s);
t := '12345678';
writeln(strTryParseInt(pchar(t), pchar(t) + length(t), v));
writeln(v);
writeln(PcharToIntMM(pchar(t), pchar(t) + length(t), v));
writeln(v);
//exit;
{ startTiming('copy');
for i := 0 to high(s) do begin
for j := 1 to 10000000 do
val(copy(s[i],1,length(s[i])), v, code)
end;
stopTiming('copy');
startTiming('val');
for i := 0 to high(s) do begin
for j := 1 to 10000000 do
val(s[i], v, code)
end;
stopTiming('val');
startTiming('valss');
for i := 0 to high(s) do begin
for j := 1 to 10000000 do
val(ss[i], v, code)
end;
stopTiming('valss'); }
startTiming('new');
for i := 0 to high(s) do begin
for j := 1 to 10000000 do
PcharToInt(pchar(s[i]), pchar(s[i]) + length(s[i]), v)
end;
stopTiming('new');
startTiming('new');
for i := 0 to high(s) do begin
for j := 1 to 10000000 do
PcharToIntMMWrong(pchar(s[i]), pchar(s[i]) + length(s[i]), v)
end;
stopTiming('new');
startTiming();
for i := 0 to high(s) do begin
for j := 1 to 10000000 do
PcharToIntMM(pchar(s[i]), pchar(s[i]) + length(s[i]), v)
end;
stopTiming();
end.
Although I had a bug that it only used the 8-digit fast path when there where at least 9 digits. The condition in the loop should have been while pstart + 8 <= pend do begin rather than while pstart + 8 < pend do begin. Changing that made it somewhat faster. Or sometimes slower. It is very fiddly to benchmark. The branch predicator appears to be very random
Point is - if you read in the ASCII digits from memory, then you have to differentiate between little & big endian CPU in the above!
And the code got exactly the wrong endianess. Everything comes out in reverse.
I had to put in a SwapEndian but that call slow it down too much. FPC not inlining such functions is another big RTL problem.
Inline assembler bswap is even worse because it apparently prevents optimizations.
However - if you really want to dive into implementing a faster 'Val' then maybe you want to take a look at this - https://lemire.me/blog/2021/01/29/number-parsing-at-a-gigabyte-per-second/
That is for float. Integer should always be faster than float.
I have already convinced Bero to implement that. But I am not sure he got everything right.
A bigger issue is how to name this function. Could be PcharToUInt, PcharToUInt64, TryPcharToUInt, TryPcharToUInt64, or PcharToUIntTry, or PcharToUInt64Try. Try at the beginning is more common, but Try at the end is easier to find in the Lazarus completion list. But there is not other PcharTo* function in the RTL.
Could be TextToUInt, TextToUInt64, TryTextToUInt, TryTextToUInt64, TextToUIntTry, or TextToUInt64Try, because there is sysutils.TextToFloat(). But that is an odd one, a Try function without Try in its name and the only one of its kind.
Could be StrToUInt, StrToUInt64, TryStrToUInt, TryStrToUInt64, or StrToUIntTry, TryStrToUInt64Try, because the overload resolving can find it from its type, so the type does not need to be in the name. Nor the 64.
But the classical one also handles other bases. Perhaps DecimalStrToUInt, DecimalStrToUInt64, TryDecimalStrToUInt, TryDecimalStrToUInt64, DecimalStrToUIntTry, DecimalStrToUInt64Try.
StrDecimalToUInt, StrDecimalToUInt64, TryStrDecimalToUInt, TryStrDecimalToUInt64, or StrDecimalToUIntTry, StrDecimalToUInt64Try
Could also drop the string type, TryParse, or TryParseDecimal or TryParseDecimalNumber