From 7a6623c99ce41f6ab840573fa36efdce9d991912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 31 Jan 2024 18:59:28 +0100 Subject: [PATCH 1/6] Added draft of TBigInt object --- AdventOfCode.lpi | 4 + UBigInt.pas | 476 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 480 insertions(+) create mode 100644 UBigInt.pas diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi index 9255b8b..ca33018 100644 --- a/AdventOfCode.lpi +++ b/AdventOfCode.lpi @@ -137,6 +137,10 @@ + + + + diff --git a/UBigInt.pas b/UBigInt.pas new file mode 100644 index 0000000..2277013 --- /dev/null +++ b/UBigInt.pas @@ -0,0 +1,476 @@ +{ + Solutions to the Advent Of Code. + Copyright (C) 2022-2024 Stefan Müller + + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . +} + +unit UBigInt; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, Math; + +type + TDigits = array of Cardinal; + + { TBigInt } + + // This is an abbreviated reimplementation of a C# class created in 2022. + TBigInt = object + private + FDigits: TDigits; + FIsNegative: Boolean; + // Adds A and B, ignoring their signs and using ReturnNegative instead. The result is + // Sign * (Abs(A) + Abs(B)), + // where Sign is 1 for ReturnNegative = False and -1 otherwise. + class function AddAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; + // Subtracts B from A, ignoring their signs. However, the result might be negative, and the sign can be reversed by + // setting ReturnNegative to True. The result is + // Sign * (Abs(A) - Abs(B)), + // where Sign is 1 for ReturnNegative = False and -1 otherwise. + class function SubtractAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; + // Multiplies A and B, ignoring their signs and using ReturnNegative instead. This multiplication uses a recursive + // implementation of the Karatsuba algorithm. See + // https://www.geeksforgeeks.org/karatsuba-algorithm-for-fast-multiplication-using-divide-and-conquer-algorithm/ + // The result is + // Sign * (Abs(a) * Abs(b)) + // where Sign is 1 for ReturnNegative = False and -1 otherwise. + class function MultiplyAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; + // Copies consecutive digits from this BigInt to create a new one. The result will be positive. Leading zeros are + // removed from the result, but AIndex + ACount must not exceed the number of digits of this BigInt. + // AIndex is the first (least significant) digit to be taken. The digit with this index will become the 0th digit of + // the new BigInt. + // ACount is the number of consecutive digits to be taken, and the number of digits of the result. + function GetSegment(const AIndex, ACount: Integer): TBigInt; + // Compares the absolute value of this TBigInt object to the absolute value of another one. Returns -1 if this + // object is less than AOther, 1 if this object is greater than AOther, and 0 if they are equal. + function CompareToAbsoluteValues(constref AOther: TBigInt): Integer; + public + property IsNegative: Boolean read FIsNegative; + constructor InitZero; + constructor Init(const AValue: Int64); + destructor Done; + function CompareTo(constref AOther: TBigInt): Integer; + end; + + operator := (const A: Int64): TBigInt; + operator + (const A, B: TBigInt): TBigInt; + operator - (const A, B: TBigInt): TBigInt; + operator * (const A: TBigInt; const B: Int64): TBigInt; + operator shl (const A: TBigInt; const B: Integer): TBigInt; + operator = (const A: TBigInt; const B: Int64): Boolean; + +implementation + +const + CBase = Cardinal.MaxValue + 1; + CMaxDigit = Cardinal.MaxValue; + CDigitSize = SizeOf(Cardinal); + CBitsPerDigit = CDigitSize * 8; + CHalfBits = CBitsPerDigit >> 1; + CHalfDigitMax = (1 << CHalfBits) - 1; + +{ TBigInt } + +class function TBigInt.AddAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; +var + i, lenA, lenB, len, shorter: Integer; + carry: Cardinal; +begin + lenA := Length(AA.FDigits); + lenB := Length(AB.FDigits); + + // Initializes the digits array of the result, with a simple test to try to predict a carry-over into a new digit. The + // result could still carry into new digit depending on lower digits (carry over multiple digits), which would be + // handled at the end. + if lenA = lenB then + if CMaxDigit - AA.FDigits[lenA - 1] < AB.FDigits[lenB - 1] then + // Result will carry into new digit. + SetLength(Result.FDigits, lenA + 1) + else + SetLength(Result.FDigits, lenA) + else + SetLength(Result.FDigits, Max(lenA, lenB)); + len := Length(Result.FDigits); + + // Calculates the new digits from less to more significant until the end of the shorter operand is reached. + shorter := Min(Length(AA.FDigits), Length(AB.FDigits)); + i := 0; + carry := 0; + while i < shorter do + begin + if (AB.FDigits[i] = CMaxDigit) and (carry > 0) then + begin + Result.FDigits[i] := AA.FDigits[i]; + carry := 1; + end + else + if CMaxDigit - AA.FDigits[i] < AB.FDigits[i] + carry then + begin + Result.FDigits[i] := AB.FDigits[i] + carry - 1 - (CMaxDigit - AA.FDigits[i]); + carry := 1; + end + else begin + Result.FDigits[i] := AA.FDigits[i] + AB.FDigits[i] + carry; + carry := 0; + end; + Inc(i); + end; + + // Copies the missing unchanged digits from the longer operand to the result, if any, before applying remaining + // carry-overs. This avoids additional tests for finding the shorter digit array. + if (i < lenA) or (i < lenB) then + if lenA >= lenB then + Move(AA.FDigits[i], Result.FDigits[i], CDigitSize * (len - i)) + else + Move(AB.FDigits[i], Result.FDigits[i], CDigitSize * (len - i)); + + // Applies the remaining carry-overs until the end of the prepared result digit array. + while (carry > 0) and (i < len) do + begin + if Result.FDigits[i] = CMaxDigit then + Result.FDigits[i] := 0 + else begin + Inc(Result.FDigits[i]); + carry := 0; + end; + Inc(i); + end; + + // Applies the carry-over into a new digit that was not anticipated in the initialization at the top (carry over + // multiple digits). + if carry > 0 then + Insert(1, Result.FDigits, 0); + Result.FIsNegative := AReturnNegative; +end; + +class function TBigInt.SubtractAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; +var + a, b: TBigInt; + carry: Cardinal; + i, lastNonZeroDigitIndex, len: Integer; +begin + // Establishes the operand order, such that Abs(a) is not less than Abs(b). + if (AA.CompareToAbsoluteValues(AB) >= 0) then + begin + a := AA; + b := AB; + Result.FIsNegative := AReturnNegative; + end + else begin + a := AB; + b := AA; + Result.FIsNegative := not AReturnNegative; + end; + + // Calculates the new digits from less to more significant until the end of the shorter operand is reached and all + // carry-overs have been applied. + len := Length(a.FDigits); + SetLength(Result.FDigits, len); + carry := 0; + // Tracks leading zeros for the trim below. + lastNonZeroDigitIndex := 0; + i := 0; + while i < Length(b.FDigits) do + begin + if (a.FDigits[i] = b.FDigits[i]) and (carry > 0) then + begin + Result.FDigits[i] := CMaxDigit; + carry := 1; + lastNonZeroDigitIndex := i; + end + else begin + if a.FDigits[i] < b.FDigits[i] then + begin + Result.FDigits[i] := CMaxDigit - (b.FDigits[i] - a.FDigits[i]) + 1 - carry; + carry := 1; + end + else begin + Result.FDigits[i] := a.FDigits[i] - b.FDigits[i] - carry; + carry := 0; + end; + if (Result.FDigits[i] > 0) then + lastNonZeroDigitIndex := i; + end; + Inc(i); + end; + while carry > 0 do + begin + if a.FDigits[i] = 0 then + begin + Result.FDigits[i] := CMaxDigit; + lastNonZeroDigitIndex := i; + end + else begin + Result.FDigits[i] := a.FDigits[i] - carry; + carry := 0; + if (Result.FDigits[i] > 0) then + lastNonZeroDigitIndex := i; + end; + Inc(i); + end; + + // Copies the missing unchanged digits from the longer operand to the result, if any. If there are none, then no trim + // needs to occur because the most significant digit is not zero. + if i < len then + Move(a.FDigits[i], Result.FDigits[i], CDigitSize * (len - i)) + else if (lastNonZeroDigitIndex + 1 < len) then + // Trims leading zeros from the digits array. + Delete(Result.FDigits, lastNonZeroDigitIndex + 1, len - lastNonZeroDigitIndex - 1); +end; + +class function TBigInt.MultiplyAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; +var + lenA, lenB, lenMax, floorHalfLength, ceilHalfLength: Integer; + a1, a0, b1, b0, a1b1, a0b0: Cardinal; + am, bm, middle, biga1, biga0, bigb1, bigb0, biga1b1, biga0b0: TBigInt; +begin + lenA := Length(AA.FDigits); + lenB := Length(AB.FDigits); + if (lenA <= 1) and (lenB <= 1) then + begin + if (AA.FDigits[0] <= CHalfDigitMax) and (AB.FDigits[0] <= CHalfDigitMax) then + if (AA.FDigits[0] = 0) or (AB.FDigits[0] = 0) then + Result.InitZero + else begin + Result.FDigits := TDigits.Create(AA.FDigits[0] * AB.FDigits[0]); + Result.FIsNegative := AReturnNegative; + end + else begin + // a1, a0, b1, b0 use only the lower (less significant) half of the bits of a digit, so the product of any two of + // these fits in one digit. + a1 := AA.FDigits[0] >> CHalfBits; + a0 := AA.FDigits[0] and CHalfDigitMax; + b1 := AB.FDigits[0] >> CHalfBits; + b0 := AB.FDigits[0] and CHalfDigitMax; + a1b1 := a1 * b1; + a0b0 := a0 * b0; + + if a1b1 > 0 then + Result.FDigits := TDigits.Create(a0b0, a1b1) + else + Result.FDigits := TDigits.Create(a0b0); + Result.FIsNegative := AReturnNegative; + + // The result of (a1 + a0) * (b1 + b0) might not fit in one digit, so one last recursion step is necessary. + am.Init(a1 + a0); + bm.Init(b1 + b0); + middle := (MultiplyAbsoluteValues(am, bm, False) - a1b1 - a0b0) << CHalfBits; + if AReturnNegative then + Result := Result - middle + else + Result := Result + middle; + end; + end + else begin + // Calculates where to split the two numbers. + lenMax := Max(lenA, lenB); + floorHalfLength := lenMax >> 1; + ceilHalfLength := lenMax - floorHalfLength; + + // Performs one recursion step. + if ceilHalfLength < lenA then + begin + biga1 := AA.GetSegment(ceilHalfLength, lenA - ceilHalfLength); + biga0 := AA.GetSegment(0, ceilHalfLength); + + if ceilHalfLength < lenB then + begin + bigb1 := AB.GetSegment(ceilHalfLength, lenB - ceilHalfLength); + bigb0 := AB.GetSegment(0, ceilHalfLength); + biga1b1 := MultiplyAbsoluteValues(biga1, bigb1, AReturnNegative); + biga0b0 := MultiplyAbsoluteValues(biga0, bigb0, AReturnNegative); + Result := (biga1b1 << (2 * ceilHalfLength * CBitsPerDigit)) + + ((MultiplyAbsoluteValues(biga1 + biga0, bigb1 + bigb0, AReturnNegative) - biga1b1 - biga0b0) << (ceilHalfLength * CBitsPerDigit)) + + biga0b0; + end + else begin + biga0b0 := MultiplyAbsoluteValues(biga0, AB, AReturnNegative); + Result := ((MultiplyAbsoluteValues(biga1 + biga0, AB, AReturnNegative) - biga0b0) << (ceilHalfLength * CBitsPerDigit)) + biga0b0; + end; + end + else begin + bigb1 := AB.GetSegment(ceilHalfLength, lenB - ceilHalfLength); + bigb0 := AB.GetSegment(0, ceilHalfLength); + biga0b0 := MultiplyAbsoluteValues(AA, bigb0, AReturnNegative); + Result := ((MultiplyAbsoluteValues(AA, bigb1 + bigb0, AReturnNegative) - biga0b0) << (ceilHalfLength * CBitsPerDigit)) + biga0b0; + end; + end; +end; + +function TBigInt.GetSegment(const AIndex, ACount: Integer): TBigInt; +var + trimmedCount: Integer; +begin + trimmedCount := ACount; + while (trimmedCount > 1) and (FDigits[AIndex + trimmedCount - 1] = 0) do + Dec(trimmedCount); + SetLength(Result.FDigits, trimmedCount); + Move(FDigits[AIndex], Result.FDigits[0], CDigitSize * trimmedCount); + Result.FIsNegative := False; +end; + +function TBigInt.CompareToAbsoluteValues(constref AOther: TBigInt): Integer; +var + i: Integer; +begin + if Length(FDigits) < Length(AOther.FDigits) then + Result := -1 + else if Length(FDigits) > Length(AOther.FDigits) then + Result := 1 + else begin + Result := 0; + for i := High(FDigits) downto 0 do + if FDigits[i] < AOther.FDigits[i] then + begin + Result := -1; + Break; + end + else if FDigits[i] > AOther.FDigits[i] then + begin + Result := 1; + Break; + end; + end; +end; + +constructor TBigInt.InitZero; +begin + FIsNegative := False; + FDigits := TDigits.Create(0); +end; + +constructor TBigInt.Init(const AValue: Int64); +var + absVal: Int64; +begin + FIsNegative := AValue < 0; + if AValue <> Int64.MinValue then + begin + absVal := Abs(AValue); + if absVal >= CBase then + FDigits := TDigits.Create(absVal mod CBase, absVal div CBase) + else + FDigits := TDigits.Create(absVal); + end + else begin + FIsNegative := True; + FDigits := TDigits.Create(0, 1 << 31); + end; +end; + +destructor TBigInt.Done; +begin + SetLength(FDigits, 0); +end; + +function TBigInt.CompareTo(constref AOther: TBigInt): Integer; +begin + if IsNegative = AOther.IsNegative then + Result := CompareToAbsoluteValues(AOther) + else + Result := 1; + if IsNegative then + Result := -Result; +end; + +operator := (const A: Int64): TBigInt; +begin + Result.Done; + Result.Init(A); +end; + +operator + (const A, B: TBigInt): TBigInt; +begin + if A.IsNegative = B.IsNegative then + Result := TBigInt.AddAbsoluteValues(A, B, A.IsNegative) + else + Result := TBigInt.SubtractAbsoluteValues(A, B, A.IsNegative); +end; + +operator - (const A, B: TBigInt): TBigInt; +begin + if A.IsNegative = B.IsNegative then + Result := TBigInt.SubtractAbsoluteValues(A, B, A.IsNegative) + else + Result := TBigInt.AddAbsoluteValues(A, B, A.IsNegative); +end; + +operator * (const A: TBigInt; const B: Int64): TBigInt; +begin + if (a = 0) or (b = 0) then + Result.InitZero + else + Result := TBigInt.MultiplyAbsoluteValues(A, B, A.IsNegative = (B > 0)); +end; + +operator shl(const A: TBigInt; const B: Integer): TBigInt; +var + i, j, digitShifts, bitShifts, reverseShift, len, newLength: Integer; + lastDigit: Cardinal; +begin + // Handles shift of zero. + if A = 0 then + Result.InitZero + else begin + // Determines full digit shifts and bit shifts. + DivMod(B, CBitsPerDigit, digitShifts, bitShifts); + + if bitShifts > 0 then + begin + reverseShift := CBitsPerDigit - bitShifts; + len := Length(A.FDigits); + lastDigit := A.FDigits[len - 1] >> reverseShift; + newLength := len + digitShifts; + + if lastDigit = 0 then + SetLength(Result.FDigits, newLength) + else + SetLength(Result.FDigits, newLength + 1); + + // Performs full digit shifts by shifting the access index j for A.FDigits. + Result.FDigits[digitShifts] := A.FDigits[0] << bitShifts; + j := 0; + for i := digitShifts + 1 to newLength - 1 do + begin + // Performs bit shifts. + Result.FDigits[i] := A.FDigits[j] >> reverseShift; + Inc(j); + Result.FDigits[i] := Result.FDigits[i] or (A.FDigits[j] << bitShifts); + end; + + if Length(Result.FDigits) > newLength then + Result.FDigits[newLength] := lastDigit; + end + else begin + // Performs full digit shifts by copy if there are no bit shifts. + len := Length(A.FDigits); + SetLength(Result.FDigits, len + digitShifts); + Move(A.FDigits[0], Result.FDigits[digitShifts], Length(A.FDigits)); + end; + + Result.FIsNegative := A.IsNegative; + end; +end; + +operator = (const A: TBigInt; const B: Int64): Boolean; +begin + Result := A.CompareTo(B) = 0; +end; + +end. + From bfb33673ee020e5e280dd1bceea5c406ba3a71c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 31 Jan 2024 19:20:10 +0100 Subject: [PATCH 2/6] Fixed some redundant parenthesis --- UBigInt.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UBigInt.pas b/UBigInt.pas index 2277013..a6ce5af 100644 --- a/UBigInt.pas +++ b/UBigInt.pas @@ -203,7 +203,7 @@ begin Result.FDigits[i] := a.FDigits[i] - b.FDigits[i] - carry; carry := 0; end; - if (Result.FDigits[i] > 0) then + if Result.FDigits[i] > 0 then lastNonZeroDigitIndex := i; end; Inc(i); @@ -218,7 +218,7 @@ begin else begin Result.FDigits[i] := a.FDigits[i] - carry; carry := 0; - if (Result.FDigits[i] > 0) then + if Result.FDigits[i] > 0 then lastNonZeroDigitIndex := i; end; Inc(i); From 5a3c3209423af591d24ee830aba7b87326d5583e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 14 Feb 2024 11:56:11 +0100 Subject: [PATCH 3/6] Fixed TBigInt heap memory allocation (fixed memory leaks) --- UBigInt.pas | 170 +++++++++++++++++++++++++++------------------------- 1 file changed, 89 insertions(+), 81 deletions(-) diff --git a/UBigInt.pas b/UBigInt.pas index a6ce5af..9fbbf3f 100644 --- a/UBigInt.pas +++ b/UBigInt.pas @@ -29,20 +29,36 @@ type { TBigInt } - // This is an abbreviated reimplementation of a C# class created in 2022. + // This is an abbreviated reimplementation in Freepascal of a C# class created in 2022. TBigInt = object private FDigits: TDigits; FIsNegative: Boolean; + + // Copies consecutive digits from this BigInt to create a new one. The result will be positive. Leading zeros are + // removed from the result, but AIndex + ACount must not exceed the number of digits of this BigInt. + // AIndex is the first (least significant) digit to be taken. The digit with this index will become the 0th digit of + // the new BigInt. + // ACount is the number of consecutive digits to be taken, and the number of digits of the result. + function GetSegment(const AIndex, ACount: Integer): TBigInt; + + // Compares the absolute value of this TBigInt object to the absolute value of another one. Returns -1 if this + // object is less than AOther, 1 if this object is greater than AOther, and 0 if they are equal. + function CompareToAbsoluteValues(constref AOther: TBigInt): Integer; + + class function GetZero: TBigInt; static; + // Adds A and B, ignoring their signs and using ReturnNegative instead. The result is // Sign * (Abs(A) + Abs(B)), // where Sign is 1 for ReturnNegative = False and -1 otherwise. class function AddAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; + // Subtracts B from A, ignoring their signs. However, the result might be negative, and the sign can be reversed by // setting ReturnNegative to True. The result is // Sign * (Abs(A) - Abs(B)), // where Sign is 1 for ReturnNegative = False and -1 otherwise. class function SubtractAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; + // Multiplies A and B, ignoring their signs and using ReturnNegative instead. This multiplication uses a recursive // implementation of the Karatsuba algorithm. See // https://www.geeksforgeeks.org/karatsuba-algorithm-for-fast-multiplication-using-divide-and-conquer-algorithm/ @@ -50,21 +66,11 @@ type // Sign * (Abs(a) * Abs(b)) // where Sign is 1 for ReturnNegative = False and -1 otherwise. class function MultiplyAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; static; - // Copies consecutive digits from this BigInt to create a new one. The result will be positive. Leading zeros are - // removed from the result, but AIndex + ACount must not exceed the number of digits of this BigInt. - // AIndex is the first (least significant) digit to be taken. The digit with this index will become the 0th digit of - // the new BigInt. - // ACount is the number of consecutive digits to be taken, and the number of digits of the result. - function GetSegment(const AIndex, ACount: Integer): TBigInt; - // Compares the absolute value of this TBigInt object to the absolute value of another one. Returns -1 if this - // object is less than AOther, 1 if this object is greater than AOther, and 0 if they are equal. - function CompareToAbsoluteValues(constref AOther: TBigInt): Integer; public property IsNegative: Boolean read FIsNegative; - constructor InitZero; - constructor Init(const AValue: Int64); - destructor Done; + class property Zero: TBigInt read GetZero; function CompareTo(constref AOther: TBigInt): Integer; + class function FromInt64(const AValue: Int64): TBigInt; static; end; operator := (const A: Int64): TBigInt; @@ -84,8 +90,48 @@ const CHalfBits = CBitsPerDigit >> 1; CHalfDigitMax = (1 << CHalfBits) - 1; + CZero: TBigInt = (FDigits: (0); FIsNegative: False); + { TBigInt } +function TBigInt.GetSegment(const AIndex, ACount: Integer): TBigInt; +var + trimmedCount: Integer; +begin + trimmedCount := ACount; + while (trimmedCount > 1) and (FDigits[AIndex + trimmedCount - 1] = 0) do + Dec(trimmedCount); + SetLength(Result.FDigits, trimmedCount); + Move(FDigits[AIndex], Result.FDigits[0], CDigitSize * trimmedCount); + Result.FIsNegative := False; +end; + +function TBigInt.CompareToAbsoluteValues(constref AOther: TBigInt): Integer; +var + i: Integer; +begin + Result := Length(FDigits) - Length(AOther.FDigits); + if Result = 0 then + begin + for i := High(FDigits) downto 0 do + if FDigits[i] < AOther.FDigits[i] then + begin + Result := -1; + Break; + end + else if FDigits[i] > AOther.FDigits[i] then + begin + Result := 1; + Break; + end; + end; +end; + +class function TBigInt.GetZero: TBigInt; +begin + Result := CZero; +end; + class function TBigInt.AddAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; var i, lenA, lenB, len, shorter: Integer; @@ -245,7 +291,7 @@ begin begin if (AA.FDigits[0] <= CHalfDigitMax) and (AB.FDigits[0] <= CHalfDigitMax) then if (AA.FDigits[0] = 0) or (AB.FDigits[0] = 0) then - Result.InitZero + Result := Zero else begin Result.FDigits := TDigits.Create(AA.FDigits[0] * AB.FDigits[0]); Result.FIsNegative := AReturnNegative; @@ -267,8 +313,8 @@ begin Result.FIsNegative := AReturnNegative; // The result of (a1 + a0) * (b1 + b0) might not fit in one digit, so one last recursion step is necessary. - am.Init(a1 + a0); - bm.Init(b1 + b0); + am := FromInt64(a1 + a0); + bm := FromInt64(b1 + b0); middle := (MultiplyAbsoluteValues(am, bm, False) - a1b1 - a0b0) << CHalfBits; if AReturnNegative then Result := Result - middle @@ -312,86 +358,48 @@ begin end; end; -function TBigInt.GetSegment(const AIndex, ACount: Integer): TBigInt; -var - trimmedCount: Integer; +function TBigInt.CompareTo(constref AOther: TBigInt): Integer; begin - trimmedCount := ACount; - while (trimmedCount > 1) and (FDigits[AIndex + trimmedCount - 1] = 0) do - Dec(trimmedCount); - SetLength(Result.FDigits, trimmedCount); - Move(FDigits[AIndex], Result.FDigits[0], CDigitSize * trimmedCount); - Result.FIsNegative := False; + if FIsNegative = AOther.FIsNegative then + Result := CompareToAbsoluteValues(AOther) + else + Result := 1; + if FIsNegative then + Result := -Result; end; -function TBigInt.CompareToAbsoluteValues(constref AOther: TBigInt): Integer; -var - i: Integer; -begin - if Length(FDigits) < Length(AOther.FDigits) then - Result := -1 - else if Length(FDigits) > Length(AOther.FDigits) then - Result := 1 - else begin - Result := 0; - for i := High(FDigits) downto 0 do - if FDigits[i] < AOther.FDigits[i] then - begin - Result := -1; - Break; - end - else if FDigits[i] > AOther.FDigits[i] then - begin - Result := 1; - Break; - end; - end; -end; - -constructor TBigInt.InitZero; -begin - FIsNegative := False; - FDigits := TDigits.Create(0); -end; - -constructor TBigInt.Init(const AValue: Int64); +class function TBigInt.FromInt64(const AValue: Int64): TBigInt; var absVal: Int64; begin - FIsNegative := AValue < 0; if AValue <> Int64.MinValue then begin absVal := Abs(AValue); if absVal >= CBase then - FDigits := TDigits.Create(absVal mod CBase, absVal div CBase) + Result.FDigits := TDigits.Create(absVal mod CBase, absVal div CBase) else - FDigits := TDigits.Create(absVal); + Result.FDigits := TDigits.Create(absVal); + Result.FIsNegative := AValue < 0; end else begin - FIsNegative := True; - FDigits := TDigits.Create(0, 1 << 31); + Result.FDigits := TDigits.Create(0, 1 << 31); + Result.FIsNegative := True; end; end; -destructor TBigInt.Done; -begin - SetLength(FDigits, 0); -end; - -function TBigInt.CompareTo(constref AOther: TBigInt): Integer; -begin - if IsNegative = AOther.IsNegative then - Result := CompareToAbsoluteValues(AOther) - else - Result := 1; - if IsNegative then - Result := -Result; -end; - operator := (const A: Int64): TBigInt; begin - Result.Done; - Result.Init(A); + Result := TBigInt.FromInt64(A); +end; + +operator - (const A: TBigInt): TBigInt; +var + len: Integer; +begin + len := Length(A.FDigits); + SetLength(Result.FDigits, len); + Move(A.FDigits[0], Result.FDigits[0], len); + Result.FIsNegative := not A.FIsNegative; end; operator + (const A, B: TBigInt): TBigInt; @@ -412,8 +420,8 @@ end; operator * (const A: TBigInt; const B: Int64): TBigInt; begin - if (a = 0) or (b = 0) then - Result.InitZero + if (A = 0) or (B = 0) then + Result := TBigInt.Zero else Result := TBigInt.MultiplyAbsoluteValues(A, B, A.IsNegative = (B > 0)); end; @@ -425,7 +433,7 @@ var begin // Handles shift of zero. if A = 0 then - Result.InitZero + Result := TBigInt.Zero else begin // Determines full digit shifts and bit shifts. DivMod(B, CBitsPerDigit, digitShifts, bitShifts); From cec6985489ebcce0c5a0b16f1d3a4fb45fbb8cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 14 Feb 2024 11:59:42 +0100 Subject: [PATCH 4/6] Updated operators --- UBigInt.pas | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/UBigInt.pas b/UBigInt.pas index 9fbbf3f..97a81fa 100644 --- a/UBigInt.pas +++ b/UBigInt.pas @@ -74,11 +74,12 @@ type end; operator := (const A: Int64): TBigInt; + operator - (const A: TBigInt): TBigInt; operator + (const A, B: TBigInt): TBigInt; operator - (const A, B: TBigInt): TBigInt; - operator * (const A: TBigInt; const B: Int64): TBigInt; + operator * (const A, B: TBigInt): TBigInt; operator shl (const A: TBigInt; const B: Integer): TBigInt; - operator = (const A: TBigInt; const B: Int64): Boolean; + operator = (const A, B: TBigInt): Boolean; implementation @@ -418,12 +419,12 @@ begin Result := TBigInt.AddAbsoluteValues(A, B, A.IsNegative); end; -operator * (const A: TBigInt; const B: Int64): TBigInt; +operator * (const A, B: TBigInt): TBigInt; begin - if (A = 0) or (B = 0) then + if (A = TBigInt.Zero) or (B = TBigInt.Zero) then Result := TBigInt.Zero else - Result := TBigInt.MultiplyAbsoluteValues(A, B, A.IsNegative = (B > 0)); + Result := TBigInt.MultiplyAbsoluteValues(A, B, A.IsNegative <> B.IsNegative); end; operator shl(const A: TBigInt; const B: Integer): TBigInt; @@ -475,7 +476,7 @@ begin end; end; -operator = (const A: TBigInt; const B: Int64): Boolean; +operator = (const A, B: TBigInt): Boolean; begin Result := A.CompareTo(B) = 0; end; From ef1eba4538fec5f78063261e1df11061a511f167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 14 Feb 2024 12:00:54 +0100 Subject: [PATCH 5/6] Fixed shl operator: incorrect move for full digit shifts --- UBigInt.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UBigInt.pas b/UBigInt.pas index 97a81fa..f943fd2 100644 --- a/UBigInt.pas +++ b/UBigInt.pas @@ -469,7 +469,7 @@ begin // Performs full digit shifts by copy if there are no bit shifts. len := Length(A.FDigits); SetLength(Result.FDigits, len + digitShifts); - Move(A.FDigits[0], Result.FDigits[digitShifts], Length(A.FDigits)); + Move(A.FDigits[0], Result.FDigits[digitShifts], CDigitSize * len); end; Result.FIsNegative := A.IsNegative; From 9f619adc01f1ae29dcd0c148b51cb78cce83d568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 14 Feb 2024 12:02:13 +0100 Subject: [PATCH 6/6] Fixed addition: final carry-over was inserted at the wrong end of the number --- UBigInt.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UBigInt.pas b/UBigInt.pas index f943fd2..6ee26d8 100644 --- a/UBigInt.pas +++ b/UBigInt.pas @@ -201,7 +201,7 @@ begin // Applies the carry-over into a new digit that was not anticipated in the initialization at the top (carry over // multiple digits). if carry > 0 then - Insert(1, Result.FDigits, 0); + Insert(1, Result.FDigits, len); Result.FIsNegative := AReturnNegative; end;