String Delphi(10.2):使用分隔符将整数快速转换为字符串

String Delphi(10.2):使用分隔符将整数快速转换为字符串,string,performance,delphi,format,String,Performance,Delphi,Format,假设我们有一个整数1234567890,我们想把它转换成一个带分隔符=1.234.567.890的字符串,我们可以进行格式化('%n',[1234567890.0]);但是速度很慢。我编写了一个函数来大大提高速度(比原来快2倍多)。我怎样才能进一步改进它,或者你能想出一个更快的程序 function MyConvertDecToStrWithDot(Const n: UInt64): string; Var a,b,x: Integer; z,step: Integer; l:

假设我们有一个整数1234567890,我们想把它转换成一个带分隔符=1.234.567.890的字符串,我们可以进行格式化('%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;
函数MyConvertDecToStrWithDot(Const n:UInt64):字符串;
变量a,b,x:整数;
z、 步骤:整数;
l:SmallInt;
开始
结果:=IntToStr(n);
如果n<1000,则退出;
l:=长度(结果);
a:=l第3部分;
b:=l模3;
步骤:=b+1;
z:=4;
如果b0,那么开始
插入('.',结果,步骤);
Inc(z,step);
结束;
对于x:=1到(a-1)do begin
插入('.',结果,z);
公司(z,4),;
结束;
结束;
程序TForm1.按钮1单击(发送方:TObject);
Var a:整数;
s:字符串;
开始
PerfTimerInit;
对于a:=1到1000000 do
s:=MyConvertDectToStrWithDot(1234567890);
备注1.lines.Add(PerfTimerStopMS.ToString);
标题:=s;
结束;
32位

格式:~230ms
我的功能:~79ms

64位

格式:~440ms
我的功能:~103ms


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

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;
使用strutil;
函数FormatNumber(n:整数):字符串;
变量位:整数;
计数:整数;
isNegative:布尔值;
开始
Is阴性:=(n<0);
如果为负,则n:=-n;
结果:='';
计数:=3;
当n0开始时
数字:=n模10;
n:=n第10部分;
如果计数=0,则开始
结果:=结果+';
计数:=3;
结束;
结果:=结果+chr(ord('0')+位数);
十二月(计数);
结束;
如果为负,则结果:=结果+'-';
结果:=反向限制(结果);
结束;
在行动中看到它:

直接分配字符比使用维多利亚建议的串联运算符/函数更快。这是仅包含未签名类型的改进版本

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;
类型string28=字符串[28];
函数FormatNumber(n:UInt64):string28;
变量位:整数;
长度:整数;
计数:整数;
c:半焦;
开始
计数:=3;
长度:=0;
当n0开始时
数字:=n模10;
n:=n第10部分;
如果计数=0,则开始
公司(长度);
结果[长度]:=';
计数:=3;
结束;
公司(长度);
结果[长度]:=chr(ord('0')+位数);
十二月(计数);
结束;
对于计数:=1到(长度+1)div 2 do begin
c:=结果[计数];
结果[计数]:=结果[长度-计数+1];
结果[长度-计数+1]:=c;
结束;
设置长度(结果、长度);
FormatNumber:=结果;
结束;

如果该操作执行了数百万次,并且在评测后确实是一个瓶颈,那么最好在多线程中执行,同时在我的测试中执行以下操作,速度会稍微快一些:

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;
以及:


%n
格式化浮点值。这就解释了为什么它比你的代码慢。@alcalde将整数转换成字符串需要额外300ms的时间。但是,OP正在转换1000000个整数。所以问题是:“为什么额外的0.0003ms a被认为是需要优化的瓶颈?”我仍然怀疑这是否是瓶颈。这似乎有道理,但你真的检查过了吗?你知道瓶颈这个词是什么意思吗?如果我必须对此进行优化,我会确保只有一个堆分配。@hikari您的理由更加混淆了这个问题:“处理大量文本”?所以你从文本开始,转换成int,然后再转换回文本,然后执行文本操作以获得特定的格式?这听起来已经很低效了。还有:“甚至每秒数百万次”。这听起来似乎仍然不能确定您的实际性能要求。更重要的是,;无论目标系统希望这些整数以相对复杂的格式而不是二进制格式出现,它们本身都不可能有这样的性能。我仍然不相信你已经正确地描述了你的问题。@hikari,因为我们喜欢帮助人们学习。看来你不想学。有趣的是,你最近的评论表明你的整个方法考虑得很差。不要将文本转换为整数,然后再转换。完全重新格式化为文本,并可能避免堆分配和
string
完全。如果这整件事不是浪费时间的话。我有超过20年的绩效关键型开发经验。我曾多次看到人们犯下过早和毫无意义的优化错误。它仍然有点依赖于区域设置。因为不是每个区域设置都会每隔3位放置分隔符。例如,将12345678900123写为
12,34,56,78,90123
,而CJK语言可以将其写为
12345678900123
。这种方法比我的方法慢62%左右(添加IntToStr,因为我们使用整数调用)。FormatSettings.000AndSeparator还添加了大量处理:使用它时:200ms,“.”=130ms。(与我的79毫秒相比)编辑:将源更改为整数并从函数中调用IntToStr,实际上只慢了8-9毫秒。@hikari我的初始测试没有显示出接近于该速度的任何地方。小于10毫秒的东西。我将在10000000次迭代后重新讨论它:~810ms我的进程,~870ms你的持续时间也会导致性能下降。只是一个
  TestHisCode;
  TestMyCode;
  TestMyCode;
  TestHisCode;
  TestMyCode;
  TestHisCode;
  TestHisCode;
  TestMyCode;