德尔福 (10.2):快速整数转换为带分隔符的字符串



假设我们有这个整数1234567890,我们希望它转换为带有分隔符= 1.234.567.890的字符串,我们可以做Format('%n',[1234567890.0](; 但它非常慢。我写了一个函数来大大加快速度(快 2 倍以上(。我怎样才能进一步改进它,或者你能想出一个更快的例程吗?

function MyConvertDecToStrWithDot(Const n: UInt64): string;
Var a,b,x: Integer;
    z,step: Integer;
    l: SmallInt;
begin
  Result := IntToStr(n);
  if n < 1000 then Exit;
  l := Length(Result);
  a := l div 3;
  b := l mod 3;
  step := b+1;
  z := 4;
  if b <> 0 then begin
    Insert('.',Result,step);
    Inc(z,step);
  end;
  for x := 1 to (a-1) do begin
    Insert('.',Result,z);
    Inc(z,4);
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
Var a: Integer;
    s: string;
begin
  PerfTimerInit;
  for a := 1 to 1000000 do
   s := MyConvertDecToStrWithDot(1234567890);
  Memo1.lines.Add(PerfTimerStopMS.ToString);
  caption := s;
end;

32 位

格式:~230毫秒
我的函数:~79ms

64 位

格式: ~440ms
我的函数:~103ms

在我的测试中,以下内容的速度略快:

function ThousandsSepStringOf(Num: UInt64): string;
const
  MaxChar = 30; // Probably, 26 is enough: 19 digits + 7 separators
var
  Count: Integer;
  Rem: UInt64;
  Res: array[0..MaxChar] of Char;
  WritePtr: PChar;
begin
  WritePtr := @Res[MaxChar];
  WritePtr^ := #0;
  Count := 0;
  while Num > 0 do
  begin
    DivMod(Num, 10, Num, Rem);
    Dec(WritePtr);
    WritePtr^ := Char(Byte(Rem) + Ord('0'));
    Inc(Count);
    if Count = 3 then
    begin
      Dec(WritePtr);
      WritePtr^ := '.';
      Count := 0;
    end;
  end;
  if WritePtr^ = '.' then
    Inc(WritePtr);
  Count := MaxChar - ((NativeInt(WritePtr) - NativeInt(@Res)) shr 1);
  SetLength(Result, Count);
  Move(WritePtr^, PByte(Result)^, Count * SizeOf(Char));
end;

测试:

procedure TestHisCode;
Var
  a: Integer;
  s: string;
  SW: TStopwatch;
begin
  Writeln('His code');
  SW := TStopwatch.StartNew;
  for a := 1 to KLoops do
    s := MyConvertDecToStrWithDot(1234567890);
  Writeln(SW.ElapsedMilliseconds);
  Writeln(s);
  Writeln;
end;
procedure TestMyCode;
Var
  a: Integer;
  s: string;
  SW: TStopwatch;
begin
  Writeln('My code');
  SW := TStopwatch.StartNew;
  for a := 1 to KLoops do
    s := ThousandsSepStringOf(1234567890);
  Writeln(SW.ElapsedMilliseconds);
  Writeln(s);
  Writeln;
end;

和:

  TestHisCode;
  TestMyCode;
  TestMyCode;
  TestHisCode;
  TestMyCode;
  TestHisCode;
  TestHisCode;
  TestMyCode;

尚未正确测试其性能,但是它应该是跨平台和区域设置独立的:

function Thousands(const ASource: string): string;
var
  I, LLast: Integer;
begin
  Result := ASource;
  LLast := Length(Result);
  I := LLast;
  while I > 0 do
  begin
    if (LLast - I + 1) mod 3 = 0 then
    begin
      Insert(FormatSettings.ThousandSeparator, Result, I);
      Dec(I, 2);
    end
    else
      Dec(I);
  end;
end;

注意:它显然仅适用于整数

最好在

构造字符串时直接插入分隔符,而不是稍后将分隔符插入转换后的字符串中,因为每次插入都涉及大量数据移动和性能下降。除了避免除以 3 可能会提高一点性能

这是我在几十年不使用它后从生锈的帕斯卡那里得到的

uses strutils;
function FormatNumber(n: integer): string;
var digit: integer;
    count: integer;
    isNegative: boolean;
begin
    isNegative := (n < 0);
    if isNegative then n := -n;
    Result := '';
    count  := 3;
    while n <> 0 do begin
        digit := n mod 10;
        n     := n div 10;
        if count = 0 then begin
            Result := Result + '.';
            count  := 3;
        end;
        Result := Result + chr(ord('0') + digit);
        dec(count);
    end;
    if isNegative then Result := Result + '-';
    Result := reversestring(Result);
end;

查看实际操作:http://ideone.com/6O3e8w

直接分配字符也更快,而不是像维多利亚建议的那样使用串联运算符/函数。这是仅包含无符号类型的改进版本

type string28 = string[28];
function FormatNumber(n: UInt64): string28;
var digit: integer;
    length: integer;
    count: integer;
    c: char;
begin
    count  := 3;
    length := 0;
    while n <> 0 do begin
        digit := n mod 10;
        n     := n div 10;
        if count = 0 then begin
            inc(length);
            Result[length] := '.';
            count  := 3;
        end;
        inc(length);
        Result[length] := chr(ord('0') + digit);
        dec(count);
    end;
    for count := 1 to (length + 1) div 2 do begin
        c := Result[count];
        Result[count] := Result[length - count + 1];
        Result[length - count + 1] := c;
    end;
    setlength(Result, length);
    FormatNumber := Result;
end;

如果操作执行了数百万次,并且在分析后确实是一个瓶颈,则最好与 SIMD 一起在多个线程中执行

最新更新