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..6ee26d8
--- /dev/null
+++ b/UBigInt.pas
@@ -0,0 +1,485 @@
+{
+ 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 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/
+ // 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;
+ public
+ property IsNegative: Boolean read FIsNegative;
+ 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;
+ operator - (const A: TBigInt): TBigInt;
+ operator + (const A, B: TBigInt): TBigInt;
+ operator - (const A, B: TBigInt): TBigInt;
+ operator * (const A, B: TBigInt): TBigInt;
+ operator shl (const A: TBigInt; const B: Integer): TBigInt;
+ operator = (const A, B: TBigInt): Boolean;
+
+implementation
+
+const
+ CBase = Cardinal.MaxValue + 1;
+ CMaxDigit = Cardinal.MaxValue;
+ CDigitSize = SizeOf(Cardinal);
+ CBitsPerDigit = CDigitSize * 8;
+ 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;
+ 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, len);
+ 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 := Zero
+ 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 := FromInt64(a1 + a0);
+ bm := FromInt64(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.CompareTo(constref AOther: TBigInt): Integer;
+begin
+ if FIsNegative = AOther.FIsNegative then
+ Result := CompareToAbsoluteValues(AOther)
+ else
+ Result := 1;
+ if FIsNegative then
+ Result := -Result;
+end;
+
+class function TBigInt.FromInt64(const AValue: Int64): TBigInt;
+var
+ absVal: Int64;
+begin
+ if AValue <> Int64.MinValue then
+ begin
+ absVal := Abs(AValue);
+ if absVal >= CBase then
+ Result.FDigits := TDigits.Create(absVal mod CBase, absVal div CBase)
+ else
+ Result.FDigits := TDigits.Create(absVal);
+ Result.FIsNegative := AValue < 0;
+ end
+ else begin
+ Result.FDigits := TDigits.Create(0, 1 << 31);
+ Result.FIsNegative := True;
+ end;
+end;
+
+operator := (const A: Int64): TBigInt;
+begin
+ 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;
+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, B: TBigInt): TBigInt;
+begin
+ if (A = TBigInt.Zero) or (B = TBigInt.Zero) then
+ Result := TBigInt.Zero
+ else
+ Result := TBigInt.MultiplyAbsoluteValues(A, B, A.IsNegative <> B.IsNegative);
+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 := TBigInt.Zero
+ 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], CDigitSize * len);
+ end;
+
+ Result.FIsNegative := A.IsNegative;
+ end;
+end;
+
+operator = (const A, B: TBigInt): Boolean;
+begin
+ Result := A.CompareTo(B) = 0;
+end;
+
+end.
+