如何对任意大小的整数执行算术运算?

时间:2013-10-23 19:15:32

标签: delphi delphi-7

我的原始代码在这里..

function AddNumStrings(Str1, Str2: string): string;
var
  i: integer;
  carryStr: string;
  worker: integer;
  workerStr: string;
begin
  Result:= inttostr(length(Str1));
  Result:= '';
  carryStr:= '0';

  // make numbers the same length
  while length(Str1) < length(Str2) do Str1:= '0' + Str1;
  while length(Str1) > length(Str2) do Str2:= '0' + Str2;

  i:= 0;
  while i < length(Str1) do begin
    worker:= strtoint(copy(Str1, length(Str1) - i, 1)) + strtoint(copy(Str2, length(Str2) - i, 1)) + strtoint(carryStr);
    if worker > 9 then begin
      workerStr:= inttostr(worker);
      carryStr:= copy(workerStr, 1, 1);
      Result:= copy(workerStr, 2, 1) + Result;
    end else begin
      Result:= inttostr(worker) + Result;
      carryStr:= '0';
    end;

    inc(i);
  end; { while }
  if carryStr <> '0' then Result:= carryStr + Result;
  Application.ProcessMessages;
end;

function yeni(s1, s2: string): string;
var
  j, i, k: integer;
  c: char;
  s: string;
begin
  k:= length(s2);
  j:= length(s1) - length(s2);
  for i:= j downto 1 do begin
    c:= s1[i];
    k:= k + 1;
    if (c <> '9') and (c <> '0') then begin
      s:= copy(s1, i, k);
      s:= AddNumStrings(s, s2);
      Setlength(s1, i - 1);
      Result:= s1 + s;
      break;
    end;

    Application.ProcessMessages;
  end;

  procedure TForm1.Button13Click(Sender: TObject);
  var
    i, k: integer;
    s: Ansistring;
  begin
    s:= '1111';
    for i:= 1 to 1000000 do begin
      for k:= 1 to 120 do begin
        s:= yeni(s, '4');
      end;
    end;
  end;

end.

最后我的字符串s必须是1,000,000,000长度。但我想我需要68天。 我怎样才能加快这段代码的速度?

3 个答案:

答案 0 :(得分:5)

您的代码对Shlemiel The Painter's Algorithm的变体感到窒息。每次你像这样添加两个字符串时,Delphi RTL必须增加字符串的大小,这可能涉及分配一个新的,更大的字符串并复制旧字符串,然后释放旧字符串。当你这样做1000万次时,所有的内存复制和重新分配都会加起来。

要让它快速运行,您需要做的是预先确定完成的字符串的长度,然后在SetLength上调用s并从头开始分配大字符串。之后,跟踪尚未“填写”的最高char的索引。您的每个IntToStr调用都会生成一个新字符串;插入|字符,然后复制IntToStr结果,随时更新索引。这应该会大大减少运行此代码所需的时间。

答案 1 :(得分:3)

您正在尝试使用字符串进行任意精度算术 这是一个坏主意。

请改用bigint库。 它使用整数数组来完成这项工作 一个不错的可下载:http://www.delphiforfun.org/Programs/Library/big_integers.htm

或根据LURD的建议:https://code.google.com/p/gmp-wrapper-for-delphi/

答案 2 :(得分:1)

显然这是一个愚蠢的例子,但你可以做一些事情:

  1. 了解调用Delphi函数时会发生什么
  2. 了解您的数据
  3. 在圈外移动东西
  4. 除非其可预测
  5. ,否则不要跳

    让我详细说明

    了解调用Delphi函数时会发生什么
    当您调用RTL函数时,例如string:= string + someotherstring
    Delphi将执行concat操作。这包括调整字符串分配的大小(如果需要)并将字符串(如果需要)移动到内存中的新位置 如果您多次执行此操作,则所有存储分配将开始累计。事实上,你将功能加倍到20,000,000次运行,你会发现运行时间超过了两倍 所以你要避免这种废话并将那些昂贵的功能移出循环。

    了解您的数据
    只要s[index] = 0,任何乘法都将为0.您可以使用此知识来加速很多。有人称这种作弊行为;在这种情况下可能是真的,但在更复杂的情况下,知道你的数据将会做什么可以让你获得非常好的速度 此外,你知道将单个数字乘以9会产生结果&gt; 81所以你不需要检查超过2个字符。

    将内容移出循环
    一旦你知道你的数据,就可以把东西移出循环,所以不要多次这样做,你只需要做一次 即使循环外的东西比循环内的东西长了10,000倍。这仍然是一个很好的协议。

    除非可预测,否则不要跳 在这种情况下,这不适用,因为该函数在输出稳定的零流时会很快稳定下来,但无论如何我都将其用于演示目的。
    事实上,此处if将比没有if更快,因为跳转会破坏依赖关系链。

    代码示例

    procedure TForm1.Button4Click(Sender: TObject); 
    const
      NumberOfCharsForKToSettleTo0 = 10;
      ManyTimes = 10 * 1000 * 1000 
    var
      i,j,k,l:integer;
      s:string;
    begin
      //preallocate the length of the string
      SetLength(s, 4 + (ManyTimes * length('|1')) + NumberOfCharsForKToSettleTo0);
      s:='1369';
      l:= 4;
      for i:=1 to ManyTimes do begin
        j:=i mod 9;
        //k:=strtoint(s[Length(s)])*j; don't use strtoint
        k:= ord(s[l]) - ord('0');
        k:= k*j;
        //s:=s+'|'+inttostr(k);
        inc(l);
        s[l]:= '|';
        inc(l);
        s[l]:= chr(k div 10+ord('0'));
        inc(l,integer(k>9));  //avoid jumps/ifs that are hard to predict.
        s[l]:= chr(k+ord('0'));
      end;
    end;