[Delphi]快速计算质数序列

       本程序(uPrimes)的算法是从Julia语言的Primes软件包算法转换过来,能实现1秒内找出0到1亿范围内的所有质数,这是编译为64bit的测试结果, 如果编译为32bit的话稍慢些。

       常用的找质数的方法如uPrimesSimple单元所示,方法很简单,但0到1亿范围内找出所有的质数要超过7秒,性能上与前者相比,有7倍差距。

unit uPrimes;

interface

uses SysUtils, Math;

type
  TIntArray  = array of Int64;

function Primes(Lo, Hi:Int64): TIntArray; overload;
function Primes(N: Int64): TIntArray; overload;
function IsPrime(N: Int64): Boolean;

implementation

const
  wheel        : array[1..8 ] of Int64 = (4,  2,  4,  2,  4,  6,  2,  6);
  wheel_primes : array[1..8 ] of Int64 = (7, 11, 13, 17, 19, 23, 29, 31);
  wheel_indices: array[1..31] of Int64 = (0,  0,  0,  0,  0,  0,  0,  1,
                                          1,  1,  1,  2,  2,  3,  3,  3,
                                          3,  4,  4,  5,  5,  5,  5,  6,
                                          6,  6,  6,  6,  6,  7,  7);

  bases: array[1..256] of UInt16 =(
    15591,  2018,   166,  7429,  8064, 16045, 10503,  4399,  1949,  1295,  2776,  3620,
      560,  3128,  5212,  2657,  2300,  2021,  4652,  1471,  9336,  4018,  2398, 20462,
    10277,  8028,  2213,  6219,   620,  3763,  4852,  5012,  3185,  1333,  6227,  5298,
     1074,  2391,  5113,  7061,   803,  1269,  3875,   422,   751,   580,  4729, 10239,
      746,  2951,   556,  2206,  3778,   481,  1522,  3476,   481,  2487,  3266,  5633,
      488,  3373,  6441,  3344,    17, 15105,  1490,  4154,  2036,  1882,  1813,   467,
     3307, 14042,  6371,   658,  1005,   903,   737,  1887,  7447,  1888,  2848,  1784,
     7559,  3400,   951, 13969,  4304,   177,    41, 19875,  3110, 13221,  8726,   571,
     7043,  6943,  1199,   352,  6435,   165,  1169,  3315,   978,   233,  3003,  2562,
     2994, 10587, 10030,  2377,  1902,  5354,  4447,  1555,   263, 27027,  2283,   305,
      669,  1912,   601,  6186,   429,  1930, 14873,  1784,  1661,   524,  3577,   236,
     2360,  6146,  2850, 55637,  1753,  4178,  8466,   222,  2579,  2743,  2031,  2226,
     2276,   374,  2132,   813, 23788,  1610,  4422,  5159,  1725,  3597,  3366, 14336,
      579,   165,  1375, 10018, 12616,  9816,  1371,   536,  1867, 10864,   857,  2206,
     5788,   434,  8085, 17618,   727,  3639,  1595,  4944,  2129,  2029,  8195,  8344,
     6232,  9183,  8126,  1870,  3296,  7455,  8947, 25017,   541, 19115,   368,   566,
     5674,   411,   522,  1027,  8215,  2050,  6544, 10049,   614,   774,  2333,  3007,
    35201,  4706,  1152,  1785,  1028,  1540,  3743,   493,  4474,  2521, 26845,  8354,
      864, 18915,  5465,  2447,    42,  4511,  1660,   166,  1249,  6259,  2553,   304,
      272,  7286,    73,  6554,   899,  2816,  5197, 13330,  7054,  2818,  3199,   811,
      922,   350,  7514,  4452,  3449,  2663,  4708,   418,  1621,  1171,  3471,    88,
    11345,   412,  1559,   194);


type
  TBoolArray = array of Boolean; //为与Julia Primes保持一致,下标范围实际只使用1:n

procedure push(var aArray: TIntArray; N: Int64);
var
  Len: Int64;
begin
  Len := Length(aArray);
  SetLength(aArray, Len + 1);
  aArray[Len] := N;
end;

function max(a, b: Int64): Int64;
begin
  if a > b then
    Result := a
  else
    Result := b;
end;

function isqrt(x: Int64): Int64;
var
  s: Int64;
begin
  if x = 0 then
  begin
    Result := x;
    Exit;
  end;
  s := Int64(trunc(sqrt(x)));
  s := (s + (x div s)) shr 1;
  if s*s > x then
    result := s-1
  else
    Result := s;
end;

function wheel_index(n: Int64): Int64; inline;
var
  d, r: Int64;
begin
  d := (n-1) div 30;
  r := n-1 - d*30;
  Result :=  8*d + wheel_indices[r + 2];
end;

function wheel_prime(n: Int64): Int64; inline;
var
  d, r: Int64;
begin
  d := (n - 1) shr 3;
  r := (n - 1) and 7;
  Result := 30*d + wheel_primes[r + 1];
end;

function _primesmask(limit:Int64): TBoolArray; overload;
var
  m, n, p, q, i, j: Int64;
begin
  if (limit < 7) then
      raise Exception.Create('The condition limit ≥ 7 must be met.');
  n := wheel_index(limit);
  m := wheel_prime(n);
  SetLength(Result, n+1);
  for i := 1 to n do Result[i] := True;
  for i := 1 to wheel_index(isqrt(limit)) do
  begin
    if Result[i] = True then
    begin
      p := wheel_prime(i);
      q := p*p;
      j := ((i - 1) and 7) + 1;
      while q <= m do
      begin
        Result[wheel_index(q)] := False;
        q := q + wheel[j] * p;
        j := (j and 7) + 1;
      end;
    end;
  end;
end;

function _primesmask(lo: Int64; hi: Int64): TBoolArray; overload;
var
  r, q: Int64;
  i, j, wlo, whi, m, p: Int64;
  small_sieve: TBoolArray;
begin
  if not ((7 <= lo) and (lo <= hi)) then
    Raise Exception.Create('The condition 7 ≤ lo ≤ hi must be met.');
  if lo = 7 then
  begin
    Result := _primesmask(hi);
    Exit;
  end;
  wlo := wheel_index(lo - 1);
  whi := wheel_index(hi);
  m := wheel_prime(whi);
  SetLength(Result, whi - wlo + 1);
  for i := 1 to whi - wlo do Result[i] := True;
  if hi < 49 then Exit;
  small_sieve := _primesmask(isqrt(hi));
  for i := 1 to length(small_sieve) - 1 do // don't use eachindex here
  begin
    if small_sieve[i] = True then
    begin
      p := wheel_prime(i);
      j := wheel_index(2 * ((lo - p - 1) div (2*p)) + 1);
      r := p * wheel_prime(j + 1);
      if r > m then Continue; // use widemul to avoid r <= m caused by overflow
      j := (j and 7) + 1;
      q := r;
      // q < 0 indicates overflow when incrementing q inside loop
      while (0 <= q) and (q <= m) do
      begin
        Result[wheel_index(q) - wlo] := False;
        q := q + wheel[j] * p ;
        j := j and 7 + 1;
      end;
    end;
  end;
end;

function primesmask(lo: Int64; hi: Int64): TBoolArray; overload;
var
  i, lsi, lwi: Int64;
  wheel_sieve: TBoolArray;
begin
  if not ((0 < lo) and (lo <= hi)) then
     raise Exception.Create('The condition 0 < lo ≤ hi must be met.');
  SetLength(Result, hi - lo + 1 + 1);
  for i := 1 to hi - lo + 1 do Result[i] := False;

  if (lo <= 2) and (2 <= hi) then Result[3 - lo] := True;
  if (lo <= 3) and (3 <= hi) then Result[4 - lo] := True;
  if (lo <= 5) and (5 <= hi) then Result[6 - lo] := True;
  if (hi < 7) then Exit;
  wheel_sieve := _primesmask(max(7, lo), hi);
  lsi := lo - 1;
  lwi := wheel_index(lsi);
  for i := 1 to length(wheel_sieve)-1 do   // don't use eachindex here
  begin
    Result[wheel_prime(i + lwi) - lsi] := wheel_sieve[i];
  end;
end;

function primesmask(limit: Int64): TBoolArray; overload;
begin
  Result := primesmask(1, limit);
end;

function Primes(Lo, Hi: Int64): TIntArray;
var
  i, lwi: Int64;
  Sieve: TBoolArray;
begin
  SetLength(Result, 0);
  if not (Lo <= Hi) then
      Raise Exception.Create('The condition lo ≤ hi must be met.');
  if (Lo <= 2) and (Hi >= 2) then push(Result, 2);
  if (Lo <= 3) and (Hi >= 3) then push(Result, 3);
  if (Lo <= 5) and (Hi >= 5) then push(Result, 5);
  if Hi < 7 then Exit;
  Lo := max(2, Lo);
  Sieve := _primesmask(max(7, Lo), Hi);
  lwi := wheel_index(Lo - 1);
  for i := 1 to Length(Sieve)-1 do   // don't use eachindex here
  begin
    if Sieve[i] = True then
      push(Result, wheel_prime(i + lwi));
  end;
end;

function Primes(N: Int64): TIntArray;
begin
  Result := Primes(1, N);
end;

function trailing_zeros(n: Int64): Int64;
begin
  if n = 0 then
  begin
    Result := 64;
    Exit;
  end;
  Result := 0;
  while (n and 1) = 0 do
  begin
    Inc(Result);
    n := (n shr 1);
  end;
end;

function _witnesses(n:UInt64): TIntArray;
var
  i: UInt64;
begin
  i := ((n shr 16) xor n) * $45d9f3b;
  i := ((i shr 16) xor i) * $45d9f3b;
  i := ((i shr 16) xor i) and 255 + 1;
  SetLength(Result, 1);
  Result[0] := Int64(bases[i]);
end;

function witnesses(n: Int64): TIntArray;
begin
  if n < 4294967296 then
      Result := _witnesses(UInt64(n))
  else if n < 2152302898747 then
      Result := [2, 3, 5, 7, 11]
  else if n < 3474749660383 then
      Result := [2, 3, 5, 7, 11, 13]
  else
      Result := [2, 325, 9375, 28178, 450775, 9780504, 1795265022];
end;

function QuickPower(Base, N: Int64): Int64;
begin
  if N = 0 then
    Result := 1
  else if N = 1 then
    Result := Base
  else
  begin
    Result := 1;
    while N > 0 do
    begin
      if (N and 1) > 0 then
      begin
        Result := Result * Base;
      end;
      Base := Base * Base;
      N := (N shr 1);
    end;
  end;
end;

function _prevpow2(x: Int64): Int64;
var
  n: Integer;
begin
  if x < 0 then
    raise Exception.Create('`x` must be ≥ 0');
  if x <= 1 then
    Result := x
  else
  begin
    n := 0;
    while x > 0 do
    begin
      Result := x;
      x := x shr 1;
      if x = 0 then
      begin
         Result := Result shl n;
         Break;
      end;
      n := n + 1;
    end;
  end;
end;

function prevpow(a:Int64; x:Int64): Int64;
var
  n, p: Int64;
begin
  if x < 1 then
    raise Exception.Create('`x` must be ≥ 1');
  // See comment in nextpos() for a == special case.
  if a = 2 then
  begin
    Result :=_prevpow2(x);
    Exit
  end;
  if a <= 1 then raise Exception.Create('`a` must be greater than 1.');
  n := Floor(ln(x)/ln(a));
  p := QuickPower(a, n+1);
  if p <= x then
    Result := p
  else
    Result := QuickPower(a,n);
end;

function powermod(x: Int64; p: Int64; m: Int64): Int64;
var
  b, t, r: Int64;
begin
  //if p < 0 then Exit(powermod(invmod(x, m), -p, m));
  if p < 0 then raise Exception.Create('From function "powermod": p < 0 not allowed');
  if p = 0 then
  begin
    Result := (1 mod m);
    Exit;
  end;
  if (m = 1) or (m = -1) then
  begin
    Result := 0;
    Exit;
  end;
  b := (x mod m);   //this also checks for divide by zero
  t := prevpow(2, p);
  r := 1;
  while true do
  begin
    if p >= t then
    begin
      r := (r*b) mod m;
      p := p - t;
    end;
    t  := (t shr 1);
    if (t <= 0) then break;
    r := (r * r) mod m;
  end;
  Result := r;
end;

function IsPrime(N: Int64): Boolean;
var
  m, s, d, a, x, t: Int64;
begin
  // Small precomputed primes + Miller-Rabin for primality testing:
  //  https://en.wikipedia.org/wiki/Miller–Rabin_primality_test
  //  https://github.com/JuliaLang/julia/issues/11594
  for m in [2, 3, 5, 7, 11, 13, 17, 19, 23] do
  begin
    if (N mod m) = 0 then
    begin
      result := (N = m);
      Exit;
    end;
  end;
  if N < 841 then
  begin
    result := (N > 1);
    Exit;
  end;
  s := trailing_zeros(N - 1);
  d := (N - 1) shr s;
  for a in witnesses(N) do
  begin
    x := powermod(a, d, N);
    if x = 1 then continue;
    t := s;
    while x <> (N - 1) do
    begin
      t := t - 1;
      if t <= 0 then
      begin
        Result := False;
        Exit;
      end;
      x := (x * x) mod N;
      if x = 1 then
      begin
        Result := false;
        Exit;
      end;
    end;
  end;
  Result := True;
end;

end.
unit uPrimesSimple;

interface

function PrimesSimple(n: Int64): TArray<Int64>;

implementation

function PrimesSimple(n: Int64): TArray<Int64>;
var
  i, j, len: Int64;
  prime: TArray<Boolean>;
begin
  SetLength(prime, n+1);
  prime[1] := False;
  for i := 2 to n do
    prime[i] := True;

  for i := 2 to n do
  begin
    j := i*i;
    while j <= n do
    begin
      prime[J] := False;
      j := j + i;
    end;
  end;

  SetLength(Result, 0);
  len := 0;
  for i := 1 to n do
  begin
    if prime[i] = True then
    begin
      len := Len + 1;
      SetLength(result, Len);
      Result[Len-1] := i;
    end;
  end;
end;

end.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值