1

Тема: Функции Str и Val

Функции Str и Val из стандартного System могут быт полезны для конвертации чисел в строку и наоборот.

Например, функция Val(_ValLong и _ValInt64) используется в стандартном SysUtils функцией StrToInt, а также в System функцией Read/ReadLn(_ReadLong и _ReadInt64).

На данный момент переделал стандартную реализацию для _ValLong и _ValInt64.
Я использовал сдвиги (shl 4) и (shl 1) + (shl 3) для предотвращения умножения, в том числе длинного, на 16 и 10.

Функции _ValLong и _ValInt64 идентичные, разница только в типах: LongInt и Int64.
Поэтому _ValLong закономерно будет работать быстрее, чем _ValInt64.

Также сделал реализацию _StrLong, _Str0Long, _Str0Int64, _StrInt64 — эти функции вызываются,
когда мы хотим сконвертировать число в строку с учётом ширины, пример:

Str(Number:7, MyString); // число Number конвертируется в строку MyString, ширина не менее 7

Ещё эти функции вызываются внутри Write/WriteLn, у нас как раз не хватает реализации вывода 64-битного целого числа(_WriteInt64 и _Write0Int64).
Для конвертации я адаптировал свою ассемблерную функцию (изначально NASM) ConvertToBase64.
Функция ConvertToBase64 пригодится также для конвертации шестнадцатеричных чисел(функция IntToHex из SysUtils), а вообще она поддерживает системы счисления от 2 до 36.
И я думаю будет полезно её оставить в интерфейсной части System.

Вот эти функции вместе с _WriteInt64 и _Write0Int64
interface
...................................
function _ValLong(const S: string; var Code: Integer): Integer;
function _ValInt64(const S: string; var Code: Integer): Int64;
function ConvertToBase64(Value: Int64; Base: LongInt; var Buffer): LongInt;
function _StrInt64(Val: Int64; Width: Integer): ShortString;
procedure _StrLong(Val, Width: Integer; Result: PShortString);
function _Str0Int64(Val: Int64): ShortString;
procedure _Str0Long(Val: Integer; Result: PShortString);

procedure _WriteInt64(var t: TTextRec; val: Int64; Width: Integer);
procedure _Write0Int64(var t: TTextRec; val: Int64);
...................................
implementation
...................................
const
  VAL_HEX_DIGITS: array[0..54] of Byte = (
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    0, 0, 0, 0, 0, 0, 0,
    $A, $B, $C, $D, $E, $F,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    $a, $b, $c, $d, $e, $f);

// in code below (shl 4) & (shl 1) + (shl 3) used to avoid long multiply by 16 and 10
function _ValInt64(const S: string; var Code: Integer): Int64;
var
  i: LongInt;
  Neg, Hex: Boolean;
begin
  Result := 0;
  Code := 1;
  if S = '' then Exit;
  Neg := False;
  Hex := False;
  i := 1;
  while (s[i] = ' ') do Inc(i);
  case s[i] of
    '-':
      begin
        Neg := True;
        Inc(i);
      end;
    '+':
      Inc(i);
    '0':
      begin
        Hex := (S[i + 1] in ['X', 'x']);
        if Hex then Inc(i, 2);
      end;
    '$', 'X', 'x':
      begin
        Hex := True;
        Inc(i);
      end;
  end;
  if Hex then
    while (s[i] in ['0'..'9', 'A'..'F', 'a'..'f']) and (Result >= 0) do
    begin
      Result := Result shl 4 + VAL_HEX_DIGITS[Ord(S[i]) - Ord('0')];
      Inc(I);
    end
  else
    while (s[i] in ['0'..'9']) and (Result >= 0) do
    begin
      Result := Result shl 1 + Result shl 3 + Ord(S[i]) - Ord('0');
      Inc(I);
    end;
  if Neg then
    Result := -Result;
  if s[i] <> #0 then
    code := i
  else
    code := 0;
end;

// in code below (shl 4) & (shl 1) + (shl 3) used to avoid multiply by 16 and 10
function _ValLong(const S: string; var Code: Integer): Integer;
var
  i: LongInt;
  Neg, Hex: Boolean;
begin
  Result := 0;
  Code := 1;
  if S = '' then Exit;
  Neg := False;
  Hex := False;
  i := 1;
  while (s[i] = ' ') do Inc(i);
  case s[i] of
    '-':
      begin
        Neg := True;
        Inc(i);
      end;
    '+':
      Inc(i);
    '0':
      begin
        Hex := (S[i + 1] in ['X', 'x']);
        if Hex then Inc(i, 2);
      end;
    '$', 'X', 'x':
      begin
        Hex := True;
        Inc(i);
      end;
  end;
  if Hex then
    while (s[i] in ['0'..'9', 'A'..'F', 'a'..'f']) and (Result >= 0) do
    begin
      Result := Result shl 4 + VAL_HEX_DIGITS[Ord(S[i]) - Ord('0')];
      Inc(I);
    end
  else
    while (s[i] in ['0'..'9']) and (Result >= 0) do
    begin
      Result := Result shl 1 + Result shl 3 + Ord(S[i]) - Ord('0');
      Inc(I);
    end;
  if Neg then
    Result := -Result;
  if s[i] <> #0 then
    code := i
  else
    code := 0;
end;

// Convert Value into the Buffer with a given Base
// function returns count of digits of a Value
// Example(convert to hexadecimal):
// ConvertToBase64(1357902481234567890, 16, mystring)
// Result: mystring = "12D83D1CB99BA2D2"
// Value treated as unsigned
// 2 <= Base <= 36
// SizeOf(Buffer) => (64 + 1)
function ConvertToBase64(Value: Int64; Base: LongInt; var Buffer): LongInt;
const
  DIGITS: array [0..35] of AnsiChar = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
asm
        push   ebx
        push   esi
        push   edi
        push   ebp
        mov    esi, edx                   // Buffer
        mov    ecx, eax                   // Base
        mov    ebx, [dword ptr Value + 4] // Value.Hi
        mov    ebp, [dword ptr Value]     // Value.Lo
        mov    edi, esi            //                            +0                              +63
        add    esi, 64             // base2(0xFFFFFFFFFFFFFFFF) = 11111111111111...1111111111111111b
        mov    [esi], byte ptr 0   // end of string
@next:
        xor    edx, edx
        mov    eax, ebx            // hi
        div    ecx
        mov    ebx, eax            // hi
        mov    eax, ebp            // lo
        div    ecx
        mov    ebp, eax            // lo
        dec    esi
        mov    dl, [edx + DIGITS]  // (put digit
        mov    [esi], dl           // to buffer)
        or     eax, ebx            // IF (hi <> 0) & (lo <> 0) THEN jump @next
        jnz    @next               //
// shift result string to buffer beginning
        mov    eax, esi
        sub    eax, edi
        mov    ecx, 64 + 1
        sub    ecx, eax
        lea    eax, [ecx - 1] // return count
        rep    movsb
        pop    ebp
        pop    edi
        pop    esi
        pop    ebx
end;

procedure StrInteger(Val: Int64; Width: LongInt; Result: PShortString);
var
  Buf: array[0..65] of AnsiChar;
  i, n, m: LongInt;
begin
  i := 0;
  if Val < 0 then
  begin
    Buf[i] := '-';
    Val := -Val;
    Inc(i);
  end;
  Inc(i, ConvertToBase64(Val, 10, Buf[i]));
  if Width > 255 then
    Width := 255;
  // Fill the Result with the appropriate number of blanks
  n := 1;
  while n <= width - i do
  begin
    Result^[n] := ' ';
    Inc(n);
  end;
  // Fill the Result with the number
  m := 0;
  while m < i do
  begin
    Result^[n] := Buf[m];
    Inc(n);
    Inc(m);
  end;
  // Result is n - 1 characters long
  SetLength(Result^, n - 1);
end;

function _StrInt64(Val: Int64; Width: Integer): ShortString;
begin
  StrInteger(Val, Width, @Result);
end;

procedure _StrLong(Val, Width: Integer; Result: PShortString);
begin
  StrInteger(Val, Width, Result);
end;

function _Str0Int64(Val: Int64): ShortString;
begin
  Result := _StrInt64(Val, 0);
end;

procedure  _Str0Long(Val: Integer; Result: PShortString);
begin
  _StrLong(Val, 0, Result);
end;

procedure _WriteInt64(var t: TTextRec; val: Int64; Width: Integer);
var
  S: ShortString;
begin
  S := _StrInt64(val, Width);
  ConsoleInterface.WritePCharLen(@S[1], LongWord(S[0]));
end;

procedure _Write0Int64(var t: TTextRec; val: Int64);
begin
  _WriteInt64(t, val, 0);
end;

Также стоит упомянуть, что функция Val(_ValLong и _ValInt64) вызывается ещё при вводе чисел, например

  Write('Input number:');
  ReadLn(MyLongInt);
  WriteLn('MyLongInt = ', MyLongInt);

Я реализовал ещё функции _ReadLong и _ReadInt64, но пока ещё их не тестировал, так как нужно сперва убрать Read/ReadLn из модуля CRT, потому что они перекрывают стандартные функции.

Вот функции _ReadLong и _ReadInt64
function _ReadLong(var t: TTextRec): Integer;
var
  Buf: array [0..High(Byte)] of AnsiChar;
  Code: LongInt;
begin
  ConsoleInterface.GetS(Buf, High(Byte));
  Val(Buf, Result, Code);
end;

function _ReadInt64(var t: TTextRec): Int64;
var
  Buf: array [0..High(Byte)] of AnsiChar;
  Code: LongInt;
begin
  ConsoleInterface.GetS(Buf, High(Byte));
  Val(Buf, Result, Code);
end;
А функцию IntToHex можно сделать вот такой
function IntToHex(Value: Int64; Digits: Integer): ShortString;
var
  Buf: array[0..65] of AnsiChar;
  i, n, m: Integer;
begin
  i := ConvertToBase64(Value, 16, Buf);
  if Digits > 32 then
    Digits := 0;
  // Fill the Result with the appropriate number of leading zeroes
  n := 1;
  while n <= Digits - i do
  begin
    Result[n] := '0';
    Inc(n);
  end;
  // Fill the Result with the number
  m := 0;
  while m < i do
  begin
    Result[n] := Buf[m];
    Inc(n);
    Inc(m);
  end;
  // Result is n - 1 characters long
  SetLength(Result, n - 1);
end;
В качестве теста для реализованных функций я использую этот пример
program StrValTest;

{$APPTYPE CONSOLE}

uses
  CRT;

var
  MyString: ShortString;
  MyInt64: Int64;
  MyLongInt: LongInt;
  Code: LongInt;

begin
  WriteLn('Hello, this is a Str/Val Test!');

  WriteLn;
  TextColor(White);
  WriteLn('----  WriteLn  ----');
  TextColor(LightGray);
  WriteLn;

  WriteLn('High(LongInt) = ', High(LongInt));
  WriteLn('High(Int64) = ', High(Int64));
  WriteLn('0 = ', 0);
  WriteLn('Low(LongInt) = ', Low(LongInt));
  WriteLn('Low(Int64) = ', Low(Int64));
  WriteLn;
  WriteLn(High(LongInt):20);
  WriteLn(High(Int64):20);
  WriteLn(0:20);
  WriteLn(Low(LongInt):20);
  WriteLn(Low(Int64):20);

  WriteLn;
  TextColor(White);
  WriteLn('------  Val  ------');
  TextColor(LightGray);
  WriteLn;

  Val('9223372036854775807', MyInt64, Code);
  WriteLn('MyInt64 = ', MyInt64, '; Code = ', Code);
  Val('2147483647', MyLongInt, Code);
  WriteLn('MyLongInt = ', MyLongInt, '; Code = ', Code);
  Val('-9223372036854775808', MyInt64, Code);
  WriteLn('MyInt64 = ', MyInt64, '; Code = ', Code);
  Val('-2147483648', MyLongInt, Code);
  WriteLn('MyLongInt = ', MyLongInt, '; Code = ', Code);

  WriteLn;
  Val('', MyInt64, Code);
  WriteLn('MyInt64 = ', MyInt64, '; Code = ', Code);
  Val('', MyLongInt, Code);
  WriteLn('MyLongInt = ', MyLongInt, '; Code = ', Code);

  WriteLn;
  Val('$7FFFFFFFFFFFFFFF', MyInt64, Code);
  WriteLn('MyInt64 = ', MyInt64, '; Code = ', Code);
  Val('$7FFFFFFF', MyLongInt, Code);
  WriteLn('MyLongInt = ', MyLongInt, '; Code = ', Code);

  WriteLn;
  Val('0x7fffffffffffffff', MyInt64, Code);
  WriteLn('MyInt64 = ', MyInt64, '; Code = ', Code);
  Val('0x7fffffff', MyLongInt, Code);
  WriteLn('MyLongInt = ', MyLongInt, '; Code = ', Code);

  WriteLn;
  Val('x7FFFFFFFFFFFFFFF', MyInt64, Code);
  WriteLn('MyInt64 = ', MyInt64, '; Code = ', Code);
  Val('x7FFFFFFF', MyLongInt, Code);
  WriteLn('MyLongInt = ', MyLongInt, '; Code = ', Code);

  WriteLn;
  TextColor(White);
  WriteLn('------  Str  ------');
  TextColor(LightGray);
  WriteLn;

  Str(High(LongInt), MyString);
  WriteLn(MyString);
  Str(High(Int64), MyString);
  WriteLn(MyString);
  Str(0, MyString);
  WriteLn(MyString);
  Str(Low(LongInt), MyString);
  WriteLn(MyString);
  Str(Low(Int64), MyString);
  WriteLn(MyString);
end.