size : 21596 uploaded_on : Mon May 17 00:00:00 1999 modified_on : Wed Dec 8 14:03:40 1999 title : BigNum org_filename : Bignum.pas author : Jes R. Klinke authoremail : jesk@diku.dk description : Class for handling arbitrarily large integers. keywords : tested : D4 submitted_by : The CKB Crew submitted_by_email : ckb@netalive.org uploaded_by : nobody modified_by : nobody owner : nobody lang : pas file-type : text/plain category : pascal-alg-maths __END_OF_HEADER__ unit BigNum; { BigNum v2.1a, 32-bit for Delphi by Jes R. Klinke Implements calculations on integers of arbitrary size. All operations necessary for cryptographic applications are provided, for functions generating large prime numbers look in BigPrime.pas. You may use this unit for whatever you want. But if you make a commercial product at least send me a copy of it, please. New in version 2.1: * Optimizations The assembler routines have been further optimized, and the reallocation algorithm changed to prevent many reallocations with slowly increasing sizes. New in version 2: * Dynamic size Each instance of TBigNum has just enough memory allocated to it to keep its current value. You don't have to specify a fixed BigNumSize. * Negative numbers Most of the calculations now support negative values. * More efficient calculations As each instance of TBigNum keeps track of how many words are actually used in it's value, only the necessary calculations are performed. This particularly speeds up the multiplication, as lots of MUL's with zero words are avoided. New in Delphi 32-bit release: * Special Object Pascal features Properties provide easier syntax. Exceptions add understandable error messages. To do's: * Pentium optimization I have done some minor optimizations, i.e. swapping of instructions to prevent data-depedency between adjacent instructions, replacement of x86 string instructions with two or more simpler instructions. But the code is still far from optimal, unfortunately I do not know much about optimizing assembler, so if anyone has some suggestions please feel free to mail me. Any comment or bug reports are welcome. You can reach me at jesk@diku.dk or jesk@dk-online.dk. My snail-mail address is Jes Rahbek Klinke Haandvaerkerhaven 3, 2. mf 2400 Copenhagen NV } interface uses SysUtils, BigTestU; type PIntArr = ^TIntArr; TIntArr = array [0..999] of LongInt; TBigNum = class private Value: PIntArr; Alloc, Used: Integer; Sign: Boolean; { obsolete functions, use the properties AsLong and AsString } function ToLong: LongInt; procedure FromLong(AValue: LongInt); function ToStr: string; procedure FromStr(S: string); { internal procedures for memory management } procedure Realloc(Ints: Integer; Preserve: Boolean); function Critical: Boolean; procedure CountUsed; public constructor Create; destructor Destroy; override; procedure Assign(AValue: TBigNum); procedure Add(AValue: TBigNum); procedure Subtract(AValue: TBigNum); procedure Multiply(AValue: TBigNum); function Divide(ADivisor: TBigNum): Boolean; function Modulo(ADivisor: TBigNum): Boolean; procedure PowerModulo(AExponent, AModulo: TBigNum); procedure BitwiseOr(AMask: TBigNum); function Compare(AValue: TBigNum): Integer; procedure Mult10; function Div10: Integer; procedure Mult2; procedure Div2; function Negative: Boolean; procedure Swap(AValue: TBigNum); property AsLong: LongInt read ToLong write FromLong; property AsString: string read ToStr write FromStr; { procedures working on absolute values only, primarily for internal use. } procedure AbsIncrement(By: LongInt); procedure AbsDecrement(By: LongInt); function AbsCompare(AValue: TBigNum): Integer; procedure AbsAdd(AValue: TBigNum); procedure AbsSubtract(AValue: TBigNum); function AbsDivide(ADivisor: TBigNum): Boolean; function AbsModulo(ADivisor: TBigNum): Boolean; function AbsModuloLong(ADivisor: Integer): Integer; end; EBigNum = class(Exception); EBigNumInvalidArg = class(EBigNum); EBigNumDivByZero = class(EBigNum); EBigNumTooBig = class(EBigNum); implementation constructor TBigNum.Create; begin inherited Create; Alloc := 0; Used := 0; end; destructor TBigNum.Destroy; begin FreeMem(Value, Alloc * SizeOf(Integer)); inherited Destroy; end; procedure TBigNum.Assign(AValue: TBigNum); begin if Alloc < AValue.Used then Realloc(AValue.Used, False); Used := AValue.Used; Move(AValue.Value^, Value^, Used shl 2); FillChar(Value^[Used], (Alloc - Used) shl 2, 0); Sign := AValue.Sign; end; procedure TBigNum.Add(AValue: TBigNum); var MValue: TBigNum; begin if Sign xor AValue.Sign then if AbsCompare(AValue) >= 0 then AbsSubtract(AValue) else begin MValue := TBigNum.Create; MValue.Assign(AValue); Self.Swap(MValue); AbsSubtract(MValue); MValue.Destroy; end else AbsAdd(AValue); end; procedure TBigNum.Subtract(AValue: TBigNum); var MValue: TBigNum; begin if Sign xor AValue.Sign then AbsAdd(AValue) else if AbsCompare(AValue) >= 0 then AbsSubtract(AValue) else begin MValue := TBigNum.Create; MValue.Assign(AValue); Self.Swap(MValue); AbsSubtract(MValue); MValue.Destroy; Sign := not Sign; end; end; procedure TBigNum.Multiply(AValue: TBigNum); var Needed: Integer; Result: PIntArr; SmallVal, BigVal: PIntArr; SmallSize, BigSize: Integer; begin if Used = 0 then Exit; if AValue.Used = 0 then begin Used := 0; Exit; end; Sign := Sign xor AValue.Sign; Needed := Used + AValue.Used + 1; GetMem(Result, Needed * SizeOf(Integer)); FillChar(Result^, Needed * SizeOf(Integer), 0); if Used > AValue.Used then begin SmallVal := AValue.Value; SmallSize := AValue.Used; BigVal := Value; BigSize := Used; end else begin BigVal := AValue.Value; BigSize := AValue.Used; SmallVal := Value; SmallSize := Used; end; asm PUSH EDI PUSH ESI PUSH EBX XOR EDX,EDX @@0:PUSH EDX MOV EDI,SmallVal LEA EDI,[EDI+4*EDX] MOV EAX,[EDI] MOV EDI,Result LEA EDI,[EDI+4*EDX] MOV ESI,BigVal MOV ECX,BigSize PUSH EBP MOV EBP,EAX XOR EDX,EDX @@1:MOV EAX,[ESI] MOV EBX,EDX MUL EBP ADD EBX,EAX MOV EAX,[EDI] ADC EDX,0 ADD EAX,EBX MOV [EDI],EAX ADC EDX,0 ADD ESI,4 ADD EDI,4 DEC ECX JNE @@1 MOV EAX,[EDI] ADD EAX,EDX MOV [EDI],EAX POP EBP POP EDX INC EDX CMP EDX,SmallSize JNE @@0 POP EBX POP ESI POP EDI end; Realloc(Needed, False); Move(Result^, Value^, Needed * SizeOf(Integer)); if Alloc - Needed > 0 then FillChar(Value^[Needed], (Alloc - Needed) * SizeOf(Integer), 0); FreeMem(Result, Needed * SizeOf(Integer)); CountUsed; end; { Note: At first sight, you might think, that Divide and Modulo gives wrong results for negative values. This depends on the definition of the quoient and remainder. The definition used by these routines is: Given the divident N and divisor D, the quotient Q and remainder R is then given by the equation N = D * Q + R, where the absolute value of R is less than the absolute value of D, and R has the same sign as D. This will prove to be very convinient.} function TBigNum.Divide(ADivisor: TBigNum): Boolean; begin if Sign xor ADivisor.Sign then begin Subtract(ADivisor); AbsDecrement(1); Result := AbsDivide(ADivisor); Sign := not Sign; end else begin Result := AbsDivide(ADivisor); end; end; function TBigNum.Modulo(ADivisor: TBigNum): Boolean; begin if Sign xor ADivisor.Sign then begin Subtract(ADivisor); AbsDecrement(1); Result := AbsModulo(ADivisor); Add(ADivisor); AbsDecrement(1); end else begin Result := AbsModulo(ADivisor); end; end; procedure TBigNum.PowerModulo(AExponent, AModulo: TBigNum); var Result, A: TBigNum; I: Integer; begin if AExponent.Sign then raise EBigNumInvalidArg.Create('Negative exponent in PowerModulo'); Result := TBigNum.Create; A := TBigNum.Create; Result.AsLong := 1; A.Assign(Self); for I := 0 to AExponent.Used * SizeOf(Integer) * 8 - 1 do begin if AExponent.Value^[I shr 5] and (1 shl (I and 31)) <> 0 then begin Result.Multiply(A); Result.Modulo(AModulo); end; A.Multiply(A); A.Modulo(AModulo); end; Assign(Result); A.Destroy; Result.Destroy; end; procedure TBigNum.BitwiseOr(AMask: TBigNum); begin if AMask.Used > Used then Realloc(AMask.Used, True); asm PUSH EDI PUSH ESI MOV EDI,Self MOV EDI,[EDI.TBigNum.Value] MOV ESI,AMask MOV ECX,[ESI.TBigNum.Used] OR ECX,ECX JE @@1 MOV ESI,[ESI.TBigNum.Value] @@0:MOV EAX,[ESI] OR EAX,[EDI] ADD ESI,4 MOV [EDI],EAX ADD EDI,4 DEC ECX JNE @@0 @@1:POP ESI POP EDI end; CountUsed; end; function TBigNum.Compare(AValue: TBigNum): Integer; begin if Sign xor AValue.Sign then if Sign then Compare := -1 else Compare := 1 else if Sign then Compare := -AbsCompare(AValue) else Compare := AbsCompare(AValue); end; procedure TBigNum.Mult10; begin Realloc(Used + 1, True); asm PUSH EDI PUSH ESI PUSH EBX MOV EDI,Self MOV ECX,[EDI.TBigNum.Used] OR ECX,ECX JE @@1 MOV EDI,[EDI.TBigNum.Value] MOV ESI,10 XOR EBX,EBX @@0:MOV EAX,[EDI] MUL ESI ADD EAX,EBX MOV [EDI],EAX ADC EDX,0 ADD EDI,4 MOV EBX,EDX DEC ECX JNE @@0 MOV [EDI],EBX @@1:POP EBX POP ESI POP EDI end; CountUsed; end; function TBigNum.Div10: Integer; begin asm PUSH EDI PUSH EBX MOV EDI,Self MOV ECX,[EDI.TBigNum.Used] XOR EDX,EDX OR ECX,ECX JE @@1 MOV EDI,[EDI.TBigNum.Value] MOV EDX,ECX LEA EDI,[EDI+4*EDX] XOR EDX,EDX MOV EBX,10 @@0:SUB EDI,4 MOV EAX,[EDI] DIV EBX MOV [EDI],EAX DEC ECX JNE @@0 @@1:MOV @Result,EDX POP EBX POP EDI end; CountUsed; end; procedure TBigNum.Mult2; begin if Critical then begin Realloc(Used + 1, True); Used := Used + 1; end; asm PUSH EDI MOV EDI,Self MOV ECX,[EDI.TBigNum.Used] OR ECX,ECX JE @@1 MOV EDI,[EDI.TBigNum.Value] CLC @@0:MOV EAX,[EDI] ADC EAX,EAX MOV [EDI],EAX ADC EAX,EAX ADD EDI,4 SHR EAX,1 DEC ECX JNE @@0 @@1:POP EDI end; CountUsed; end; procedure TBigNum.Div2; begin asm PUSH EDI MOV EDI,Self MOV ECX,[EDI.TBigNum.Used] OR ECX,ECX JE @@1 MOV EDI,[EDI.TBigNum.Value] MOV EDX,[EDI+4*ECX-4] SHR EDX,1 MOV [EDI+4*ECX-4],EDX DEC ECX JZ @@1 MOV EAX,[EDI+4*ECX-4] DEC ECX JZ @@3 @@0:RCR EAX,1 MOV [EDI+4*ECX],EAX MOV EAX,[EDI+4*ECX-4] DEC ECX JNZ @@0 @@3:RCR EAX,1 MOV [EDI],EAX @@1:OR EDX,EDX JNZ @@2 MOV EDI,Self DEC DWORD PTR [EDI.TBigNum.Used] @@2:POP EDI end; end; function TBigNum.Negative: Boolean; begin Result := Sign; end; procedure TBigNum.Swap(AValue: TBigNum); var MI: Integer; MP: PIntArr; MB: Boolean; begin MI := Alloc; Alloc := AValue.Alloc; AValue.Alloc := MI; MI := Used; Used := AValue.Used; AValue.Used := MI; MP := Value; Value := AValue.Value; AValue.Value := MP; MB := Sign; Sign := AValue.Sign; AValue.Sign := MB; end; function TBigNum.AbsCompare(AValue: TBigNum): Integer; begin if Used > AValue.Used then AbsCompare := 1 else if Used < AValue.Used then AbsCompare := -1 else if Used = 0 then AbsCompare := 0 else asm PUSH EDI PUSH ESI MOV EDI,Self MOV EDI,[EDI.TBigNum.Value] MOV ESI,AValue MOV ECX,[ESI.TBigNum.Used] MOV ESI,[ESI.TBigNum.Value] MOV EDX,ECX DEC EDX SHL EDX,2 ADD EDI,EDX ADD ESI,EDX STD REPZ CMPSD { MOV EAX,[ESI] MOV EDX,[EDI] SUB ESI,4 SUB EDI,4} MOV @Result,0FFFFFFFFh JA @@1 MOV @Result,000000000h JE @@1 MOV @Result,000000001h @@1: CLD POP ESI POP EDI end; end; procedure TBigNum.AbsIncrement(By: LongInt); begin if (Used = 0) or Critical then begin Inc(Used); Realloc(Used, True); end; asm PUSH EDI MOV EDI,Self MOV ECX,[EDI.TBigNum.Used] DEC ECX MOV EDI,[EDI.TBigNum.Value] ADD EDI,4 MOV EAX,[EDI-4] ADD EAX,By MOV [EDI-4],EAX JNC @@1 ADD EDX,EDX OR ECX,ECX JZ @@1 SHR EDX,1 @@0:MOV EAX,[EDI] ADC EAX,0 MOV [EDI],EAX ADC EAX,EAX ADD EDI,4 SHR EAX,1 JC @@0 @@1:POP EDI end; CountUsed; end; procedure TBigNum.AbsDecrement(By: LongInt); begin asm PUSH EDI MOV EDI,Self MOV ECX,[EDI.TBigNum.Used] DEC ECX MOV EDI,[EDI.TBigNum.Value] CLD ADD EDI,4 MOV EAX,[EDI-4] SUB EAX,By MOV [EDI-4],EAX ADD EDX,EDX OR ECX,ECX JZ @@1 SHR EDX,1 @@0:MOV EAX,[EDI] SBB EAX,0 MOV [EDI],EAX ADC EAX,EAX ADD EDI,4 SHR EAX,1 DEC ECX JNE @@0 @@1:POP EDI end; CountUsed; end; procedure TBigNum.AbsAdd(AValue: TBigNum); var RealAdds, ExtraAdds: Integer; begin if AValue.Used >= Alloc then if AValue.Critical or (AValue.Used = Alloc) and (Alloc = Used) and Critical then Realloc(AValue.Used + 1, True) else if AValue.Used > Alloc then Realloc(AValue.Used, True) else if AValue.Used < Alloc then if (Used = Alloc) and Critical then Realloc(Used + 1, True); RealAdds := AValue.Used; ExtraAdds := Alloc - RealAdds; asm PUSH EDI PUSH ESI MOV EDI,Self MOV EDI,[EDI.TBigNum.Value] MOV ESI,AValue MOV ESI,[ESI.TBigNum.Value] MOV ECX,RealAdds OR ECX,ECX JZ @@2 LEA EDI,[EDI+4*ECX] LEA ESI,[ESI+4*ECX] NEG ECX MOV EAX,[ESI+4*ECX] CLC @@0:ADC [EDI+4*ECX],EAX MOV EAX,[ESI+4+4*ECX] INC ECX JNZ @@0 MOV ECX,ExtraAdds JECXZ @@2 LEA EDI,[EDI+4*ECX] NEG ECX @@1:ADC DWORD PTR [EDI+4*ECX],0 INC ECX JNZ @@1 @@2:POP ESI POP EDI end; CountUsed; end; procedure TBigNum.AbsSubtract(AValue: TBigNum); begin asm PUSH EDI PUSH ESI MOV EDI,Self MOV EDX,[EDI.TBigNum.Used] MOV EDI,[EDI.TBigNum.Value] MOV ESI,AValue MOV ECX,[ESI.TBigNum.Used] MOV ESI,[ESI.TBigNum.Value] SUB EDX,ECX OR ECX,ECX JZ @@2 LEA EDI,[EDI+4*ECX] LEA ESI,[ESI+4*ECX] NEG ECX MOV EAX,[ESI+4*ECX] CLC @@0:SBB [EDI+4*ECX],EAX MOV EAX,[ESI+4+4*ECX] INC ECX JNZ @@0 ADC ECX,ECX OR EDX,EDX JZ @@2 SHR ECX,1 LEA EDI,[EDI+4*EDX] NEG EDX @@1:SBB DWORD PTR [EDI+4*EDX],0 INC EDX JNZ @@1 @@2:POP ESI POP EDI end; CountUsed; end; function TBigNum.AbsDivide(ADivisor: TBigNum): Boolean; var Bit, Res, Divisor: TBigNum; NoRemainder: Boolean; begin if ADivisor.Used = 0 then raise EBigNumDivByZero.Create('AbsDivide by zero'); Bit := TBigNum.Create; Res := TBigNum.Create; Divisor := TBigNum.Create; Divisor.Assign(ADivisor); NoRemainder := False; Bit.AsLong := 1; Res.AsLong := 0; while AbsCompare(Divisor) >= 0 do begin Bit.Mult2; Divisor.Mult2; end; while (Bit.Value^[0] and 1 = 0) and not NoRemainder do begin Bit.Div2; Divisor.Div2; case AbsCompare(Divisor) of 1: begin Res.BitwiseOr(Bit); AbsSubtract(Divisor); end; 0: begin NoRemainder := True; Res.BitwiseOr(Bit); AbsSubtract(Divisor); end; end; end; AbsDivide := NoRemainder; Assign(Res); Divisor.Destroy; Res.Destroy; Bit.Destroy; end; function TBigNum.AbsModulo(ADivisor: TBigNum): Boolean; var Divisor: TBigNum; NoRemainder: Boolean; Dif, Count: Integer; function MulSub(Src, Sub, Res: PIntArr; Mul, Size: Integer): Integer; asm MOV ECX,Size MOV ESI,Src MOV EDI,Res @@0:MOV EAX,[ESI] ADD ESI,4 MOV [EDI],EAX ADD EDI,4 DEC ECX JNZ @@0 MOV EDI,Res MOV ESI,Sub MOV ECX,Size PUSH EBP MOV EBP,Mul MOV EBX,0 CLC PUSHF @@1: MOV EAX,[ESI] ADD ESI,4 MUL EBP ADD EAX,EBX ADC EDX,0 JNC @@2 INT 3 @@2: MOV EBX,EDX POPF SBB [EDI],EAX PUSHF ADD EDI,4 DEC ECX JNZ @@1 POPF SBB EAX,EAX end; begin if ADivisor.Used = 0 then raise EBigNumDivByZero.Create('AbsModulo by zero'); Divisor := TBigNum.Create; Divisor.Assign(ADivisor); NoRemainder := False; Divisor.ReAlloc(Used, True); Dif := Used - Divisor.Used; Count := 0; if Dif > 0 then begin Move(Divisor.Value^[0], Divisor.Value^[Dif], Divisor.Used shl 2); FillChar(Divisor.Value^[0], Dif shl 2, 0); Inc(Divisor.Used, Dif); Count := Dif * 32; end; while AbsCompare(Divisor) >= 0 do begin Inc(Count); Divisor.Mult2; end; while (Count <> 0) and not NoRemainder do begin Divisor.Div2; case AbsCompare(Divisor) of 1: begin AbsSubtract(Divisor); end; 0: begin NoRemainder := True; AbsSubtract(Divisor); end; end; Dec(Count); end; AbsModulo := NoRemainder; Divisor.Destroy; end; function TBigNum.AbsModuloLong(ADivisor: Integer): Integer; asm PUSH ESI PUSH EBX MOV EBX,ADivisor MOV ESI,Self MOV ECX,[ESI.TBigNum.Used] MOV ESI,[ESI.TBigNum.Value] MOV EDX,ECX DEC EDX SHL EDX,2 ADD ESI,EDX XOR EDX,EDX STD @@0:MOV EAX,[ESI] SUB ESI,4 DIV EBX DEC ECX JNZ @@0 MOV EAX,EDX CLD POP EBX POP ESI end; procedure TBigNum.FromLong(AValue: LongInt); begin if AValue < 0 then begin Sign := True; AValue := -AValue; end else Sign := False; if Alloc < 1 then Realloc(1, False); Move(AValue, Value^[0], SizeOf(LongInt));; Used := 1; if Alloc > Used then FillChar(Value^[Used], (Alloc - Used) shl 2, 0); CountUsed; end; function TBigNum.ToLong: LongInt; begin if (Used > 1) or (Used = 1) and Critical then raise EBigNumTooBig.Create('Value don''t fit in a LongInt'); if Used = 1 then Result := Value^[0] else Result := 0; if Sign then Result := -Result; end; procedure TBigNum.FromStr(S: string); var I: Integer; begin if Length(S) = 0 then raise EBigNumInvalidArg.Create('Not a valid number in FromStr'); Used := 0; if S[1] = '-' then begin Sign := True; I := 2; end else begin Sign := False; I := 1; end; while I <= Length(S) do begin Mult10; if (S[I] > '9') or (S[I] < '0') then raise EBigNumInvalidArg.Create('Not a valid number in FromStr'); AbsIncrement(Byte(S[I]) - Byte('0')); Inc(I); end; end; function TBigNum.ToStr: string; var M: TBigNum; Res: string; begin if Used = 0 then begin Result := '0'; Exit; end; M := TBigNum.Create; M.Assign(Self); while M.Used > 0 do Res := Char(Byte('0') + M.Div10) + Res; if Sign then Result := '-' + Res else Result := Res; M.Destroy; end; procedure TBigNum.Realloc(Ints: Integer; Preserve: Boolean); var NewValue: PIntArr; begin if Ints <= Alloc then begin if Preserve then begin FillChar(Value^[Used], (Alloc - Used) shl 2, 0); end; Exit; end; if Ints < Alloc * 2 then Ints := Alloc * 2; if Preserve then begin GetMem(NewValue, Ints * SizeOf(Integer)); Move(Value^, NewValue^, Used shl 2); FillChar(NewValue^[Used], (Ints - Used) shl 2, 0); FreeMem(Value, Alloc * SizeOf(Integer)); Value := NewValue; Alloc := Ints; end else begin FreeMem(Value, Alloc * SizeOf(Integer)); Alloc := Ints; GetMem(Value, Alloc * SizeOf(Integer)); end; end; function TBigNum.Critical: Boolean; begin Critical := (Used > 0) and (Value^[Used - 1] and (1 shl (SizeOf(Integer) * 8 - 1)) <> 0); end; procedure TBigNum.CountUsed; begin Used := Alloc; while (Used > 0) and (Value^[Used - 1] = 0) do Dec(Used); end; (* var BigA, BigB: TBigNum; I: Integer; begin BigA.Init; { Caution: Because of the new dynamic memory allocation } BigB.Init; { you have to use Init and Done. } WriteLn('Fibonacci numbers:'); BigA.Val('0'); BigB.Val('1'); for I := 1 to 370 do begin WriteLn(BigB.Str: 79); BigA.Add(BigB); BigA.Swap(BigB); end; WriteLn(BigB.Str: 79); WriteLn('Factorials:'); BigA.Val('1'); BigB.Val('1'); for I := 1 to 49 do begin WriteLn(BigA.Str: 70, ' = ', BigB.Str, '!'); BigB.AbsIncrement(1); BigA.Multiply(BigB); end; for I := 1 to 49 do begin WriteLn(BigA.Str: 70, ' = ', BigB.Str, '!'); BigA.Divide(BigB); BigB.AbsDecrement(1); end; WriteLn(BigA.Str: 70, ' = ', BigB.Str, '!'); WriteLn('Powers of 2:'); BigA.Val('1'); BigB.Val('2'); for I := 1 to 250 do begin WriteLn(BigA.Str: 79); BigA.Multiply(BigB); end; for I := 1 to 250 do begin WriteLn(BigA.Str: 79); BigA.Divide(BigB); end; WriteLn(BigA.Str: 79); BigB.Done; BigA.Done; Write('Press enter to exit.'); ReadLn;*) end.