Title: Unsigned 64 bit Integer support
Question: This article presents a new unsigned 64 bit integer type: UInt64.
And two routines to convert between UInt64 and strings.
Answer:
unit UInt64Lib;
{
Unsigned 64 bit Integer support for Delphi 6 and Kylix.
Copyright (c) 2002 by DelphiFactory Netherlands BV
This unit provides to routines to convert between strings and unsigned
64 bit integers.
UInt64 range: 0..18446744073709551615
Thanks to J.H.Plantenberg for the assembler part.
For comments mail to:
erwin@delphi-factory.com
}
interface
resourcestring
SInvalidUInt64 = '''%s'' is not a valid UInt64 value';
{ The UInt64 type is declared as a strong typed Int64, since both compilers
don't support unsigned 64 bit integers. This way you can at least do some
calculations. }
type
UInt64 = type Int64;
{ StrToUInt64 converts the given string to an unsigned 64 bit integer value.
If the string doesn't contain a valid value, an EConvertError exception is
raised. }
function StrToUInt64(const S: AnsiString): UInt64;
{ UInt64ToStr converts the given value to its decimal string representation. }
function UInt64ToStr(Value: UInt64): string;
implementation
// For speed we are going to use the fact that long strings are
// guaranteed to be null-terminated:
{$R-} // Range checking must be disabled
{$B-} // Do not perform complete boolean expression evaluations
uses
SysUtils;
type
_UInt64 = packed record // Split the 64 bit into 2x32 bit
Lo, Hi : LongWord;
end;
procedure RaiseEConvertError(const S: AnsiString);
begin
// raise an exception explaining the input string was invalid
raise EConvertError.CreateResFmt(@SInvalidUInt64, [S]);
end;
function StrToUInt64(const S: AnsiString): UInt64;
var
I: LongWord;
Dig: Integer;
Digit : LongWord;
Hi,Lo : LongWord;
begin
// check if S is empty (is nil pointer)
if S = '' then
RaiseEConvertError(S);
// start at the first character
I := 1;
// trim leading spaces
while S[I] = ' ' do
Inc(I);
if S[I] = '-' then // check for minus sign
RaiseEConvertError(S);
if S[I] = '+' then // check for plus sign
Inc(I);
// Check for hexidecimal string id: '$' or '0x'
if (S[I] = '$') or ((S[I] = '0') and (Upcase(S[I+1]) = 'X')) then
begin
// trim leading zero (if '0x' hex marker)
if S[I] = '0' then
Inc(I);
// trim hex marker
Inc(I);
// Check if empty
if S[I] = #0 then
RaiseEConvertError(S);
// init loop
Dig := 0;
Result := 0;
// while not end of string
while S[I] #0 do
begin
// try character convert
case S[I] of
'0'..'9': Dig := Ord(S[I]) - Ord('0');
'A'..'F': Dig := Ord(S[I]) - (Ord('A') - 10);
'a'..'f': Dig := Ord(S[I]) - (Ord('a') - 10);
else
RaiseEConvertError(S)
end;
// still enough room in result?
if Result shr 60 0 then
RaiseEConvertError(S);
// shift converted digit into result
Result := Result shl 4 + Dig;
// next char
Inc(I);
end;
end
else begin // decimal unsigned 64 bit conversion
// check if not empty
if S[I] = #0 then
RaiseEConvertError(S);
Hi := 0;
Lo := 0;
while S[I] #0 do
begin
// Extract the digit from the string and convert it ASCII-Byte
Digit := Ord(S[I]) xor Ord('0');
// Some assembler to perform an unsigned 64 bit integer calculation.
// This asm code runs in D6 and Kylix (PIC code).
// HiLo := (HiLo*10)+Digit
asm
push esi // save register
// calculate: Hi * 10;
mov eax, Hi // Load Hi
mov ecx, 10 // multiplier is 10
mul ecx // EDX:EAX := EAX*ECX
or edx, edx // Overflow?
jnz @TooBig // yes - bye bye
// calculate: Lo * 10
mov esi, eax // save Hi value
mov eax, Lo // load Lo
mul ecx // EDX:EAX := EAX*ECX
// Combine Hi, Lo, and overflow of Lo to form HiLo result
add edx, esi // EDX:EAX := HiLo*10
// HiLo := HiLo + Digit
add eax, Digit // EDX:EAX := HiLo+Digit
adc edx, 0 // check overflow
jc @TooBig // yes - bye bye
// save HiLo
mov Hi, edx // Hi := EDX
mov Lo, eax // Lo := EAX
jmp @TheEnd // successfull finish
@TooBig:
mov Digit, 666 // something went wrong: invalidate Digit
@TheEnd:
pop esi // restore register
end;
// Check if digit was legal and if the previous calculation was a success
if not (Digit in [0..9]) then
RaiseEConvertError(S);
// proceed to the next digit
Inc(I);
end;
// Return HiLo as an unsigned 64 bit integer
_UInt64(Result).Lo := Lo;
_UInt64(Result).Hi := Hi;
end;
end;
function UInt64ToStr(Value: UInt64): string;
const
BiggestUInt64Str = '18446744073709551615';
MaxBCD = Length(BiggestUInt64Str);
type
TBCD = array[0..MaxBCD-1] of Integer; { Index 0 is highest BCD digit}
procedure AddBCD(var BCD : TBCD; Pos,Value : Integer);
begin
Inc(BCD[Pos], Value);
if BCD[Pos] 9 then
begin
Dec(BCD[Pos],10);
AddBCD(BCD,Pos-1, 1);
end;
end;
procedure IncBCD(var A : TBCD; const B : TBCD);
var
I : Integer;
begin
for I := MaxBCD-1 downto 0 do
AddBCD(A, I, B[I]);
end;
var
ValueBCD : TBCD;
BitValue : TBCD;
Tmp : TBCD;
I : Integer;
Ofs : Integer;
begin
// default to zero
FillChar(ValueBCD, SizeOf(ValueBCD), 0);
// set bit value BCD. Lowest bit has value 1
FillChar(BitValue, SizeOf(BitValue), 0);
BitValue[MaxBCD-1] := 1;
// check if there are bits available
while Value 0 do
begin
// if current lowest bit is set
// Increment the BCD value with the current bit value
if Value and 1 0 then
IncBCD(ValueBCD, BitValue);
// proceed to the next bit
Value := Value shr 1;
// Double the BitValue
Tmp := BitValue;
IncBCD(BitValue, Tmp);
end;
// Find highest non zero decimal
Ofs := 0;
while (Ofs Inc(Ofs);
// check if any non zero decimals present
if Ofs begin
// convert BCD result to ASCII result
SetLength(Result, MaxBCD-Ofs);
I := Ofs;
Repeat
Result[I-Ofs+1] := Char(ValueBCD[I]+Ord('0'));
Inc(I);
until I MaxBCD-1;
end
else
Result := '0'; // nothing set - value is '0'
end;
end.