{
  Clever Internet Suite
  Copyright (C) 2014 Clever Components
  All Rights Reserved
  www.CleverComponents.com
}

			{
/**********************************************************\
|                                                          |
| The implementation of PHPRPC Protocol 3.0                |
|                                                          |
| BigInt.pas                                               |
|                                                          |
| Release 3.0.1                                            |
| Copyright by Team-PHPRPC                                 |
|                                                          |
| WebSite:  http://www.phprpc.org/                         |
|           http://www.phprpc.net/                         |
|           http://www.phprpc.com/                         |
|           http://sourceforge.net/projects/php-rpc/       |
|                                                          |
| Authors:  Ma Bingyao <andot@ujn.edu.cn>                  |
|                                                          |
| This file may be distributed and/or modified under the   |
| terms of the GNU Lesser General Public License (LGPL)    |
| version 3.0 as published by the Free Software Foundation |
| and appearing in the included file LICENSE.              |
|                                                          |
\**********************************************************/

/* BigInteger Variant Type
 *
 * Copyright: Ma Bingyao <andot@ujn.edu.cn>
 * Version: 3.1
 * LastModified: Dec 28, 2008
 * This library is free.  You can redistribute it and/or modify it.
 */
}

unit clBigInt;

{$I clVer.inc}

interface

uses
{$IFNDEF DELPHIXE2}
  Types, Variants, SysUtils, StrUtils, Math,
{$ELSE}
  System.Types, System.Variants, System.SysUtils, System.StrUtils, System.Math,
{$ENDIF}
  clUtils;

type
{$IFNDEF DELPHI2009}
  RawByteString = type AnsiString;
{$ENDIF}
  TRadix = 2..36;
{ BigInteger variant creation utils }

function VarBi: TVarType;
function Zero: Variant;
function One: Variant;
procedure BigInteger(var V: Variant; const I: Int64); overload;
function BigInteger(const I: Int64): Variant; overload;
procedure BigInteger(var V: Variant; const S: string); overload;
function BigInteger(const S: string): Variant; overload;
function BigInteger(const ABytes: TclByteArray): Variant; overload;
function PowMod(var X, Y, Z: Variant): Variant; overload;
function Rand(BitNumber: Integer; SetHighBit: Boolean): Variant;
function BigIntToBinStr(const V: Variant): RawByteString;
function BigIntToBytes(const V: Variant): TclByteArray;
function BinStrToBigInt(const S: RawByteString): Variant;
function BigIntToString(const V: Variant; Radix: TRadix = 10): RawByteString;

implementation

type
  PLongWordArray = ^TLongWordArray;
  TLongWordArray = array [0..$7FFFFFF] of LongWord;
  TBiVarData = packed record
    VType: TVarType;
    Reserved1, Reserved2, Reserved3: Word;
    VData: PLongWordArray;
    VLength: LongInt;
    {$IFDEF CPUX64}Extra: LongInt;{$ENDIF}
  end;

  TBiVariantType = class(TCustomVariantType)
  public
    procedure Clear(var V: TVarData); override;
    function IsClear(const V: TVarData): Boolean; override;
    procedure Copy(var Dest: TVarData; const Source: TVarData;
      const Indirect: Boolean); override;
    procedure Cast(var Dest: TVarData; const Source: TVarData); override;
    procedure CastTo(var Dest: TVarData; const Source: TVarData;
      const AVarType: TVarType); override;
    procedure BinaryOp(var Left: TVarData; const Right: TVarData;
      const Op: TVarOp); override;
    procedure Compare(const Left, Right: TVarData;
      var Relationship: TVarCompareResult); override;
  end;

var
  BiVariantType: TBiVariantType = nil;

const
  CharacterSet: RawByteString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

function ZeroFill(const S: string; N: Integer): string;
var
  L: Integer;
begin
  L := N - Length(S);
  if L > 0 then Result := StringOfChar('0', L) + S else Result := S;
end;

procedure SetLength(var V: TBiVarData; Count: Integer);
begin
  ReallocMem(V.VData, SizeOf(LongWord) * Count);
  if Count > V.VLength then
    FillChar(V.VData[V.VLength], SizeOf(LongWord) * (Count - V.VLength), 0);
  V.VLength := Count;
end;

procedure FixLength(var V: TBiVarData);
begin
  while (V.VLength > 1) and (V.VData[V.VLength - 1] = 0) do Dec(V.VLength);
end;

procedure Add(var A: TBiVarData; B: Word); overload;
var
  I: Integer;
begin
  FixLength(A);
  Inc(A.VData[0], B);
  if A.VData[0] > $FFFF then begin
    I := 0;
    if (A.VLength = 1) or (A.VData[A.VLength - 1] = $FFFF) then
      SetLength(A, A.VLength + 1);
    repeat
      A.VData[I] := A.VData[I] and $FFFF;
      Inc(I);
      Inc(A.VData[I]);
    until A.VData[I] <= $FFFF;
    FixLength(A);
  end;
end;

procedure Add(var A, B: TBiVarData); overload;
var
  AL, BL, I, L, N: Integer;
begin
  FixLength(A);
  FixLength(B);
  AL := A.VLength;
  BL := B.VLength;
  L := Max(AL, BL) + 1;
  SetLength(A, L);
  for I := 0 to Min(AL, BL) - 1 do Inc(A.VData[I], B.VData[I]);
  if AL < BL then
    for I := AL to BL - 1 do A.VData[I] := B.VData[I];
  I := 0;
  N := 0;
  while (I < L - 1) do begin
    if (A.VData[I] > $FFFF) then begin
      A.VData[I] := A.VData[I] and $FFFF;
      N := I + 1;
      Inc(A.VData[N]);
    end;
    Inc(I);
  end;
  if N = L - 1 then A.VLength := L else A.VLength := L - 1;
end;

procedure Mul(var A: TBiVarData; B: Word); overload;
var
  Temp: LongWord;
  I: Integer;
begin
  FixLength(A);
  if B = 0 then begin
    SetLength(A, 1);
    A.VData[0] := 0;
    Exit;
  end;
  if B = 1 then Exit;
  SetLength(A, A.VLength + 1);
  Temp := A.VData[0];
  A.VData[0] := 0;
  for I := 0 to A.VLength - 2 do begin
    Inc(A.VData[I], Temp * B);
    Temp := A.VData[I + 1];
    A.VData[I + 1] := A.VData[I] shr 16;
    A.VData[I] := A.VData[I] and $FFFF;
  end;
  FixLength(A);
end;

procedure Mul(var A, B: TBiVarData); overload;
var
  R: PLongWordArray;
  L, I, J, K: Integer;
begin
  FixLength(A);
  FixLength(B);
  if B.VLength = 1 then begin
    Mul(A, B.VData[0]);
    Exit;
  end;
  L := A.VLength + B.VLength;
  GetMem(R, SizeOf(LongWord) * L);
  FillChar(R^, SizeOf(LongWord) * L, 0);
  for I := 0 to A.VLength - 1 do for J := 0 to B.VLength - 1 do begin
    K := I + J;
    Inc(R[K], A.VData[I] * B.VData[J]);
    Inc(R[K + 1], R[K] shr 16);
    R[K] := R[K] and $FFFF;
  end;
  FreeMem(A.VData);
  A.VData := R;
  A.VLength := L;
  FixLength(A);
end;

function IntDivMod(var A: TBiVarData; B: Word): Word; overload;
var
  R: LongWord;
  I: Integer;
begin
  FixLength(A);
  if B = 0 then Error(reDivByZero);
  if B = 1 then begin
    Result := 0;
    Exit;
  end;
  R := 0;
  I := A.VLength;
  Dec(I);
  while I >= 0 do begin
    R := R shl 16;
    R := R or A.VData[I];
    A.VData[I] := R div B;
    R := R mod B;
    Dec(I);
  end;
  FixLength(A);
  Result := R;
end;

procedure LeftShift(var A: TBiVarData; B: Word);
var
  W, X, C: Word;
  I, L: Integer;
  R: PLongWordArray;
begin
  FixLength(A);
  // add one empty element for IntDivMod !
  if B = 0 then begin
    SetLength(A, A.VLength + 1);
    FixLength(A);
    Exit;
  end;
  W := B shr 4;
  B := B and 15;
  GetMem(R, SizeOf(LongWord) * (A.VLength + W + 1));
  FillChar(R^, SizeOf(LongWord) * (A.VLength + W + 1), 0);
  L := A.VLength;
  if B = 0 then
    Move(A.VData[0], R[W], SizeOf(LongWord) * L)
  else begin
    I := 0;
    C := 0;
    while I < L do begin
      X := A.VData[I];
      R[I + W] := Word(X shl B) or C;
      C := X shr (16 - B);
      Inc(I);
    end;
    R[I + W] := C;
  end;
  FreeMem(A.VData);
  A.VData := R;
  Inc(A.VLength, W + 1);
  FixLength(A);
end;

procedure RightShift(var A: TBiVarData; B: Word);
var
  W, X, C: Word;
  L: Integer;
  R: PLongWordArray;
begin
  FixLength(A);
  if B = 0 then Exit;
  W := B shr 4;
  B := B and 15;
  if W >= A.VLength then begin
    W := A.VLength - 1;
    B := 0;
  end;
  L := A.VLength - W;
  GetMem(R, SizeOf(LongWord) * L);
  FillChar(R^, SizeOf(LongWord) * L, 0);
  if B = 0 then
    Move(A.VData[W], R[0], SizeOf(LongWord) * L)
  else begin
    C := 0;
    Dec(L);
    while (L >= 0) do begin
      X := A.VData[L + W];
      R[L] := Word(X shr B) or C;
      C := X shl (16 - B);
      Dec(L);
    end;
  end;
  FreeMem(A.VData);
  A.VData := R;
  Dec(A.VLength, W);
  FixLength(A);
end;

function Compare(var A, B: TBiVarData): TVarCompareResult;
var
  I: Integer;
begin
  FixLength(A);
  FixLength(B);
  if A.VLength < B.VLength then Result := crLessThan
  else if A.VLength > B.VLength then Result := crGreaterThan
  else begin
    Result := crEqual;
    for I := A.VLength - 1 to 0 do begin
      if A.VData[I] < B.VData[I] then Result := crLessThan
      else if A.VData[I] > B.VData[I] then Result := crGreaterThan;
    end;
  end;
end;

procedure IntDivMod(var A, B: TBiVarData; out Q: TBiVarData); overload;
var
  DP, NP, RP, RL, DL, I, P: Integer;
  T, S, Mask, Val: Word;
  Sum, B1, B2, D, QH, RH, MC: LongWord;
begin
  if Compare(A, B) = crLessThan then begin
    Q.VType := A.VType;
    SetLength(Q, 1);
    Q.VData[0] := 0;
    Exit;
  end;

  if B.VLength = 1 then begin
    if Q.VType = VarBi then
      FreeMem(Q.VData)
    else
      Q.VType := VarBi;
    Q.VData := A.VData;
    Q.VLength := A.VLength;
    A.VData := nil;
    SetLength(A, 1);
    A.VData[0] := IntDivMod(Q, B.VData[0]);
    Exit;
  end;

  RL := A.VLength + 1;
  DL := B.VLength + 1;
  Mask := $8000;
  Val := B.VData[B.VLength - 1];
  S := 0;
  RP := A.VLength - B.VLength;
  while (Mask <> 0) and ((Val and Mask) = 0) do begin
    Inc(S);
    Mask := Mask shr 1;
  end;

  if Q.VType <> VarBi then begin
    Q.VType := VarBi;
    Q.VData := nil;
    Q.VLength := 0;
  end;
  SetLength(Q, A.VLength - B.VLength + 1);
  LeftShift(A, S);
  LeftShift(B, S);

  I := RL - B.VLength;
  P := RL - 1;

  B1 := B.VData[B.VLength - 1];
  B2 := B.VData[B.VLength - 2];

  while I > 0 do begin
    // maybe you will find P is out of range (P >= A.VLength),
    // but A has more elements than A.VLength (because of LeftShift) ,
    // so here is no mistake. it also appears in the following code.

    D := (A.VData[P] shl 16) + A.VData[P - 1];
    QH := D div B1;
    RH := D mod B1;
    repeat
      if (QH = $10000) or ((QH * B2) > ((RH shl 16) + A.VData[P - 2])) then begin
        Dec(QH);
        Inc(RH, B1);
        if (RH < $10000) then Continue;
      end;
      Break;
    until False;

    //
    // At this point, QH is either exact, or one too large
    // (more likely to be exact) so, we attempt to multiply the
    // divisor by QH, if we get a borrow, we just subtract
    // one from QH and add the divisor back.
    //

    DP := 0;
    NP := P - DL + 1;
    MC := 0;
    QH := QH and $FFFF;
    repeat
      Inc(MC, B.VData[DP] * QH);
      T := A.VData[NP];
      Dec(A.VData[NP], MC and $FFFF);
      A.VData[NP] := A.VData[NP] and $FFFF;
      MC := MC shr 16;
      if A.VData[NP] > T then Inc(MC);
      Inc(DP);
      Inc(NP);
    until DP >= DL;

    NP := P - DL + 1;
    DP := 0;

    // Overestimate
    if MC <> 0 then begin
      Dec(QH);
      Sum := 0;
      repeat
        Inc(Sum, A.VData[NP] + B.VData[DP]);
        A.VData[NP] := Sum and $FFFF;
        Sum := Sum shr 16;
        Inc(DP);
        Inc(NP);
      until DP >= DL;
    end;

    Q.VData[RP] := QH and $FFFF;
    Dec(RP);
    Dec(P);
    Dec(I);
  end;

  FixLength(Q);
  FixLength(A);

  if S <> 0 then RightShift(A, S);
end;

function BigIntToString(const V: Variant; Radix: TRadix): RawByteString;
var
  T: Variant;
  R: Word;
begin
  if V = Zero then
    Result := '0'
  else if V = One then
    Result := '1'
  else begin
    Result := '';
    T := V;
    while T <> Zero do begin
      R := IntDivMod(TBiVarData(T), Radix);
      Result := CharacterSet[R + 1] + Result;
    end;
  end;
end;

function VarBi: TVarType;
begin
  Result := BiVariantType.VarType;
end;

function Zero: Variant;
begin
  VarClear(Result);
  with TBiVarData(Result) do begin
    VType := VarBi;
    VData := nil;
    VLength := 0;
    SetLength(TBiVarData(Result), 1);
  end;
end;

function One: Variant;
begin
  VarClear(Result);
  with TBiVarData(Result) do begin
    VType := VarBi;
    VData := nil;
    VLength := 0;
    SetLength(TBiVarData(Result), 1);
    VData[0] := 1;
  end;
end;

procedure BigInteger(var V: Variant; const I: Int64); overload;
begin
  VarClear(V);
  with TBiVarData(V) do begin
    VType := VarBi;
    VData := nil;
    VLength := 0;
    if I > $FFFFFFFF then begin
      SetLength(TBiVarData(V), 4);
      VData[0] := I and $FFFF;
      VData[1] := (I shr 16) and $FFFF;
      VData[2] := (I shr 32) and $FFFF;
      VData[3] := (I shr 48) and $FFFF;
      if VData[3] = 0 then VLength := 3 else VLength := 4;
    end
    else if I > $FFFF then begin
      SetLength(TBiVarData(V), 2);
      VData[0] := I and $FFFF;
      VData[1] := (I shr 16) and $FFFF;
      VLength := 2;
    end
    else begin
      SetLength(TBiVarData(V), 1);
      VData[0] := I;
      VLength := 1;
    end;
  end;
end;

function BigInteger(const I: Int64): Variant; overload;
begin
  BigInteger(Result, I);
end;

procedure BigInteger(var V: Variant; const S: string); overload;
var
  I, SLen, ALen: Integer;
  Temp: string;
begin
  BigInteger(V, 0);
  SLen := Length(S);
  Inc(SLen, 4 - (SLen mod 4));
  Temp := ZeroFill(S, SLen);
  ALen := SLen shr 2;
  for I := 0 to ALen - 1 do begin
    Mul(TBiVarData(V), 10000);
    Add(TBiVarData(V), StrToInt(MidStr(Temp, I shl 2 + 1, 4)));
  end;
end;

function BigInteger(const S: string): Variant; overload;
begin
  BigInteger(Result, S);
end;

function BigInteger(const ABytes: TclByteArray): Variant; overload;
var
  raw: RawByteString;
begin
  System.SetLength(raw, Length(ABytes));
  Move(ABytes[0], raw[1], Length(ABytes));
  Result := BinStrToBigInt(raw);
end;

function PowMod(var X, Y, Z: Variant): Variant; overload;
var
  A, B, C: Variant;
  N, I, J: Integer;
  Temp: LongWord;
begin
  if VarType(X) = VarBi then A := X else VarCast(A, X, VarBi);
  if VarType(Y) = VarBi then B := Y else VarCast(B, Y, VarBi);
  if VarType(Z) = VarBi then C := Z else VarCast(C, Z, VarBi);
  with TBiVarData(B) do begin
    N := VLength;
    Result := One;
    for I := 0 to N - 2 do begin
      Temp := VData[I];
      for J := 0 to 15 do begin
        if (Temp and 1) <> 0 then Result := (Result * A) mod C;
        Temp := Temp shr 1;
        A := (A * A) mod C;
      end;
    end;
    Temp := VData[N - 1];
    while (Temp <> 0) do begin
      if (Temp and 1) <> 0 then Result := (Result * A) mod C;
      Temp := Temp shr 1;
      A := (A * A) mod C;
    end;
  end;
end;

function Rand(BitNumber: Integer; SetHighBit: Boolean): Variant;
const
  LowBitMasks: array [0..15] of Word = ($0001, $0002, $0004, $0008,
                                        $0010, $0020, $0040, $0080,
                                        $0100, $0200, $0400, $0800,
                                        $1000, $2000, $4000, $8000);
var
  I, R, Q: Integer;
begin
  VarClear(Result);
  R := BitNumber mod 16;
  Q := BitNumber shr 4;
  with TBiVarData(Result) do begin
    VType := VarBi;
    VData := nil;
    VLength := 0;
    SetLength(TBiVarData(Result), Q + 1);
    for I := 0 to Q - 1 do VData[I] := Random($10000);
    if R <> 0 then begin
      VData[Q] := Random(LowBitMasks[R]);
      if SetHighBit then VData[Q] := VData[Q] or LowBitMasks[R - 1];
    end
    else begin
      VData[Q] := 0;
      if SetHighBit then VData[Q - 1] := VData[Q - 1] or $8000;
    end;
  end;
  FixLength(TBiVarData(Result));
end;

function BigIntToBinStr(const V: Variant): RawByteString;
var
  N, I: Integer;
begin
  with TBiVarData(V) do begin
    N := VLength;
    System.SetLength(Result, N * 2);
    for I := 0 to N - 1 do begin
      Result[(N - I) * 2] := AnsiChar(VData[I] and $FF);
      Result[(N - I) * 2 - 1] := AnsiChar((VData[I] shr 8) and $FF);
    end;
  end;
end;

function BigIntToBytes(const V: Variant): TclByteArray;
var
  raw: RawByteString;
begin
  raw := BigIntToBinStr(V);
  System.SetLength(Result, Length(raw));
  Move(raw[1], Result[0], Length(raw));
end;

function BinStrToBigInt(const S: RawByteString): Variant;
var
  I, N: Integer;
begin
  N := Length(S);
  if N = 0 then begin
    Result := Zero;
    Exit;
  end;
  VarClear(Result);
  with TBiVarData(Result) do begin
    VType := VarBi;
    VData := nil;
    VLength := 0;
    SetLength(TBiVarData(Result), (N + 1) shr 1);
    I := N;
    while I > 1 do begin
      VData[VLength - ((I + 1) shr 1)] := (Ord(S[I - 1]) shl 8) or Ord(S[I]);
      Dec(I, 2);
    end;
    if Odd(N) then VData[VLength - 1] := Ord(S[1]);
  end;
end;

{ TBiVariantType }

procedure TBiVariantType.BinaryOp(var Left: TVarData;
  const Right: TVarData; const Op: TVarOp);
var
  TL, TR: TVarData;
begin
  VarDataInit(TL);
  VarDataInit(TR);
  try
    VarDataCopy(TL, Left);
    VarDataCopy(TR, Right);
    case Op of
      opAdd:
        Add(TBiVarData(Left), TBiVarData(TR));
      opMultiply:
        Mul(TBiVarData(Left), TBiVarData(TR));
      opDivide, opIntDivide:
        IntDivMod(TBiVarData(TVarData(TL)), TBiVarData(TVarData(TR)), TBiVarData(Left));
      opModulus:
        IntDivMod(TBiVarData(Left), TBiVarData(TVarData(TR)), TBiVarData(TVarData(TL)));
      opShiftLeft:
        LeftShift(TBiVarData(Left), TBiVarData(TR).VData[0]);
      opShiftRight:
        RightShift(TBiVarData(Left), TBiVarData(TR).VData[0]);
    else
      RaiseInvalidOp;
    end;
  finally
    VarDataClear(TL);
    VarDataClear(TR);
  end;
end;

procedure TBiVariantType.Cast(var Dest: TVarData;
  const Source: TVarData);
var
  LTemp: TVarData;
begin
  if VarDataIsStr(Source) then begin
    BigInteger(Variant(Dest), VarToStr(Variant(Source)));
  end
  else begin
    VarDataInit(LTemp);
    try
      VarDataCastTo(LTemp, Source, varInt64);
      BigInteger(Variant(Dest), LTemp.VInt64);
    finally
      VarDataClear(LTemp);
    end;
  end;
end;

procedure TBiVariantType.CastTo(var Dest: TVarData;
  const Source: TVarData; const AVarType: TVarType);
var
  S: RawByteString;
begin
  if Source.VType = VarType then begin
    S := BigIntToString(Variant(Source));
    case AVarType of
      varOleStr:
        VarDataFromOleStr(Dest, WideString(StringToOleStr(S)));
      varString{$IFDEF DELPHI2009}, varUString{$ENDIF}:
        VarDataFromStr(Dest, string(S));
    else
      RaiseCastError;
    end
  end
  else
    RaiseCastError;
end;

procedure TBiVariantType.Clear(var V: TVarData);
begin
  V.VType := varEmpty;
  FreeMem(TBiVarData(V).VData);
  TBiVarData(V).VData := nil;
  TBiVarData(V).VLength := 0;
end;

procedure TBiVariantType.Compare(const Left, Right: TVarData;
  var Relationship: TVarCompareResult);
var
  L, R: TBiVarData;
begin
  if (Left.VType = VarType) and (Right.VType = VarType) then begin
    L := TBiVarData(Left);
    R := TBiVarData(Right);
    Relationship := clBigInt.Compare(L, R);
  end
  else RaiseInvalidOp;
end;

procedure TBiVariantType.Copy(var Dest: TVarData;
  const Source: TVarData; const Indirect: Boolean);
begin
  if Indirect and VarDataIsByRef(Source) then
    VarDataCopyNoInd(Dest, Source)
  else
    VarDataClear(Dest);
    Dest.VType := VarType;
    TBiVarData(Dest).VLength := TBiVarData(Source).VLength;
    GetMem(TBiVarData(Dest).VData, SizeOf(LongWord) * TBiVarData(Dest).VLength);
    Move(TBiVarData(Source).VData^, TBiVarData(Dest).VData^, SizeOf(LongWord) * TBiVarData(Dest).VLength);
end;

function TBiVariantType.IsClear(const V: TVarData): Boolean;
begin
  Result := (TBiVarData(V).VData = nil) and (TBiVarData(V).VLength = 0);
end;

initialization
  Randomize;
  BiVariantType := TBiVariantType.Create;

finalization
  FreeAndNil(BiVariantType);
end.

