diff --git a/AdventOfCode.lpi b/AdventOfCode.lpi index 9255b8b..3061d84 100644 --- a/AdventOfCode.lpi +++ b/AdventOfCode.lpi @@ -137,6 +137,18 @@ + + + + + + + + + + + + diff --git a/UBigInt.pas b/UBigInt.pas new file mode 100644 index 0000000..5d7dd29 --- /dev/null +++ b/UBigInt.pas @@ -0,0 +1,763 @@ +{ + 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; + + function GetSign: Integer; + + // 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; + class function GetOne: 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; + + class function FromHexOrBinString(const AValue: string; const AFromBase: Integer): TBigInt; static; + class function ConvertDigitBlock(const AValue: string; var AStartIndex: Integer; const ACharBlockSize, AFromBase: + Integer): Cardinal; + public + property IsNegative: Boolean read FIsNegative; + property Sign: Integer read GetSign; + class property Zero: TBigInt read GetZero; + class property One: TBigInt read GetOne; + + // Returns the index of the most significant bit, i.e. returns integer k, where 2^k is the largest power of 2 that + // is less than or equal to the absolute value of the number itself. Returns -1 if the given number is 0. + function GetMostSignificantBitIndex: Int64; + function CompareTo(constref AOther: TBigInt): Integer; + function TryToInt64(out AOutput: Int64): Boolean; + // TODO: ToString is currently for debug output only. + function ToString: string; + class function FromInt64(const AValue: Int64): TBigInt; static; + class function FromHexadecimalString(const AValue: string): TBigInt; static; + class function FromBinaryString(const AValue: string): TBigInt; static; + end; + + TBigIntArray = array of TBigInt; + + { Operators } + + 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 shr (const A: TBigInt; const B: Integer): TBigInt; + operator = (const A, B: TBigInt): Boolean; + operator <> (const A, B: TBigInt): Boolean; + operator < (const A, B: TBigInt): Boolean; + operator <= (const A, B: TBigInt): Boolean; + operator > (const A, B: TBigInt): Boolean; + 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); + COne: TBigInt = (FDigits: (1); FIsNegative: False); + +{ TBigInt } + +function TBigInt.GetSign: Integer; +begin + if FIsNegative then + Result := -1 + else if (Length(FDigits) > 1) or (FDigits[0] <> 0) then + Result := 1 + else + Result := 0; +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); + Result.FDigits := Copy(FDigits, AIndex, 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.GetOne: TBigInt; +begin + Result := COne; +end; + +class function TBigInt.AddAbsoluteValues(constref AA, AB: TBigInt; const AReturnNegative: Boolean): TBigInt; +var + i, j, 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 + for j := i to len - 1 do + Result.FDigits[j] := AA.FDigits[j] + else + for j := i to len - 1 do + Result.FDigits[j] := AB.FDigits[j]; + + // 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; + compare, i, j, lastNonZeroDigitIndex, len: Integer; +begin + // Establishes the operand order, such that Abs(a) is not less than Abs(b). + compare := AA.CompareToAbsoluteValues(AB); + if compare = 0 then + begin + Result := Zero; + Exit; + end; + + if compare > 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 + for j := i to len - 1 do + Result.FDigits[j] := a.FDigits[j] + 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; + +class function TBigInt.FromHexOrBinString(const AValue: string; const AFromBase: Integer): TBigInt; +var + charBlockSize, offset, i, j, k, remainder: Integer; + d: Cardinal; +begin + // 2 ^ (32 / charBlockSize) = AFromBase + case AFromBase of + 2: charBlockSize := 32; + 16: charBlockSize := 8; + end; + + if AValue[1] = '-' then + begin + offset := 2; + Result.FIsNegative := True; + end + else begin + offset := 1; + Result.FIsNegative := False; + end; + + // Calculates the first (most significant) digit d of the result. + DivMod(AValue.Length - offset + 1, charBlockSize, i, remainder); + k := offset; + d := 0; + // Checks the first block of chars that is not a full block. + if remainder > 0 then + d := ConvertDigitBlock(AValue, k, remainder, AFromBase); + // Checks full blocks of chars for first digit. + while (d = 0) and (i > 0) do + begin + Dec(i); + d := ConvertDigitBlock(AValue, k, charBlockSize, AFromBase); + end; + + // Checks for zero. + if (d = 0) and (i = 0) then + Result := Zero + else begin + // Initializes the array of digits. + SetLength(Result.FDigits, i + 1); + Result.FDigits[i] := d; + + // Calculates the other digits. + for j := i - 1 downto 0 do + Result.FDigits[j] := ConvertDigitBlock(AValue, k, charBlockSize, AFromBase); + end; +end; + +class function TBigInt.ConvertDigitBlock(const AValue: string; var AStartIndex: Integer; const ACharBlockSize, + AFromBase: Integer): Cardinal; +var + part: string; +begin + part := Copy(AValue, AStartIndex, ACharBlockSize); + Inc(AStartIndex, ACharBlockSize); + case AFromBase of + 2: part := '%' + part; + 8: part := '&' + part; + 16: part := '$' + part; + end; + Result := StrToDWord(part); +end; + +function TBigInt.GetMostSignificantBitIndex: Int64; +var + high, i: Integer; +begin + high := Length(FDigits) - 1; + if (high = 0) and (FDigits[0] = 0) then + Result := -1 + else begin + i := CBitsPerDigit - 1; + while ((1 << i) and FDigits[high]) = 0 do + Dec(i); + Result := high * CBitsPerDigit + i; + 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; + +function TBigInt.TryToInt64(out AOutput: Int64): Boolean; +begin + AOutput := 0; + Result := False; + case Length(FDigits) of + 0: Result := True; + 1: begin + AOutput := FDigits[0]; + if FIsNegative then + AOutput := -AOutput; + Result := True; + end; + 2: begin + if FDigits[1] <= Integer.MaxValue then + begin + AOutput := FDigits[1] * CBase + FDigits[0]; + if FIsNegative then + AOutput := -AOutput; + Result := True; + end + else if (FDigits[1] = Integer.MaxValue + 1) and (FDigits[0] = 0) and FIsNegative then + begin + AOutput := Int64.MinValue; + Result := True; + end; + end; + end; +end; + +function TBigInt.ToString: string; +var + i: Integer; +begin + if FIsNegative then + Result := '(-' + else + Result := ''; + for i := 0 to Length(FDigits) - 2 do + Result := Result + '(' + IntToStr(FDigits[i]) + ' + 2^32 * '; + Result := Result + IntToStr(FDigits[Length(FDigits) - 1]); + for i := 0 to Length(FDigits) - 2 do + Result := Result + ')'; + 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; + +class function TBigInt.FromHexadecimalString(const AValue: string): TBigInt; +begin + Result := FromHexOrBinString(AValue, 16); +end; + +class function TBigInt.FromBinaryString(const AValue: string): TBigInt; +begin + Result := FromHexOrBinString(AValue, 2); +end; + +{ Operators } + +operator := (const A: Int64): TBigInt; +begin + Result := TBigInt.FromInt64(A); +end; + +operator - (const A: TBigInt): TBigInt; +var + len: Integer; +begin + len := Length(A.FDigits); + if (len > 1) or (A.FDigits[0] > 0) then + begin + Result.FDigits := Copy(A.FDigits, 0, len); + Result.FIsNegative := not A.FIsNegative; + end + else + Result := TBigInt.Zero; +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 + begin + Result := TBigInt.Zero; + Exit; + end; + + // 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 lastDigit > 0 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); + for i := 0 to digitShifts - 1 do + Result.FDigits[i] := 0; + for i := 0 to len - 1 do + Result.FDigits[i + digitShifts] := A.FDigits[i]; + end; + + Result.FIsNegative := A.IsNegative; +end; + +operator shr(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 + begin + Result := TBigInt.Zero; + Exit; + end; + + // Determines full digit shifts and bit shifts. + DivMod(B, CBitsPerDigit, digitShifts, bitShifts); + + // Handles shift to zero. + if digitShifts >= Length(A.FDigits) then + begin + Result := TBigInt.Zero; + Exit; + end; + + if bitShifts > 0 then + begin + reverseShift := CBitsPerDigit - bitShifts; + len := Length(A.FDigits); + lastDigit := A.FDigits[len - 1] >> bitShifts; + newLength := len - digitShifts; + + if lastDigit = 0 then + SetLength(Result.FDigits, newLength - 1) + else + SetLength(Result.FDigits, newLength); + + // Performs full digit shifts by shifting the access index j for A.FDigits. + j := digitShifts; + for i := 0 to newLength - 2 do + begin + // Performs bit shifts. + Result.FDigits[i] := A.FDigits[j] >> bitShifts; + Inc(j); + Result.FDigits[i] := Result.FDigits[i] or (A.FDigits[j] << reverseShift); + end; + + if lastDigit > 0 then + Result.FDigits[newLength - 1] := lastDigit; + end + else + // Performs full digit shifts by copy if there are no bit shifts. + Result.FDigits := Copy(A.FDigits, digitShifts, Length(A.FDigits) - digitShifts); + + Result.FIsNegative := A.IsNegative; +end; + +operator = (const A, B: TBigInt): Boolean; +begin + Result := A.CompareTo(B) = 0; +end; + +operator <> (const A, B: TBigInt): Boolean; +begin + Result := A.CompareTo(B) <> 0; +end; + +operator < (const A, B: TBigInt): Boolean; +begin + Result := A.CompareTo(B) < 0; +end; + +operator <= (const A, B: TBigInt): Boolean; +begin + Result := A.CompareTo(B) <= 0; +end; + +operator > (const A, B: TBigInt): Boolean; +begin + Result := A.CompareTo(B) > 0; +end; + +operator >= (const A, B: TBigInt): Boolean; +begin + Result := A.CompareTo(B) >= 0; +end; + +end. + diff --git a/UNumberTheory.pas b/UNumberTheory.pas index 99b7fd5..a247344 100644 --- a/UNumberTheory.pas +++ b/UNumberTheory.pas @@ -22,7 +22,7 @@ unit UNumberTheory; interface uses - Classes, SysUtils; + Classes, SysUtils, Generics.Collections, Math; type @@ -34,6 +34,52 @@ type class function LeastCommonMultiple(AValue1, AValue2: Int64): Int64; end; + TInt64Array = array of Int64; + + { TIntegerFactor } + + TIntegerFactor = record + Factor: Int64; + Exponent: Byte; + end; + + TIntegerFactors = specialize TList; + + { TIntegerFactorization } + + TIntegerFactorization = class + public + class function PollardsRhoAlgorithm(const AValue: Int64): TInt64Array; + class function GetNormalized(constref AIntegerFactorArray: TInt64Array): TIntegerFactors; + end; + + { TDividersEnumerator } + + TDividersEnumerator = class + private + FFactors: TIntegerFactors; + FCurrentExponents: array of Byte; + function GetCount: Integer; + public + constructor Create(constref AIntegerFactorArray: TInt64Array); + destructor Destroy; override; + function GetCurrent: Int64; + function MoveNext: Boolean; + procedure Reset; + property Current: Int64 read GetCurrent; + property Count: Integer read GetCount; + end; + + { TDividers } + + TDividers = class + private + FFactorArray: TInt64Array; + public + constructor Create(constref AIntegerFactorArray: TInt64Array); + function GetEnumerator: TDividersEnumerator; + end; + implementation { TNumberTheory } @@ -58,5 +104,177 @@ begin Result := (Abs(AValue1) div GreatestCommonDivisor(AValue1, AValue2)) * Abs(AValue2); end; +{ TIntegerFactorization } + +// https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm +class function TIntegerFactorization.PollardsRhoAlgorithm(const AValue: Int64): TInt64Array; +var + primes: specialize TList; + composites: specialize TStack; + factor, n: Int64; + i: Integer; + + function G(const AX, AC: Int64): Int64; + begin + Result := (AX * AX + AC) mod n; + end; + + function FindFactor(const AStartValue, AC: Int64): Int64; + var + x, y, d: Int64; + begin + x := AStartValue; + y := x; + d := 1; + while d = 1 do + begin + x := G(x, AC); + y := G(G(y, AC), AC); + d := TNumberTheory.GreatestCommonDivisor(Abs(x - y), n); + end; + Result := d; + end; + +begin + primes := specialize TList.Create; + composites := specialize TStack.Create; + + n := Abs(AValue); + while (n and 1) = 0 do + begin + primes.Add(2); + n := n shr 1; + end; + + composites.Push(n); + while composites.Count > 0 do + begin + n := composites.Pop; + i := 0; + repeat + factor := FindFactor(2 + (i + 1) div 2, 1 - i div 2); + if factor < n then + begin + composites.Push(factor); + composites.Push(n div factor); + end; + Inc(i); + until (factor < n) or (i > 3); + if factor = n then + primes.Add(factor); + end; + + Result := primes.ToArray; + + primes.Free; + composites.Free; +end; + +class function TIntegerFactorization.GetNormalized(constref AIntegerFactorArray: TInt64Array): TIntegerFactors; +var + i: Integer; + factor: Int64; + normal: TIntegerFactor; + found: Boolean; +begin + Result := TIntegerFactors.Create; + for factor in AIntegerFactorArray do + begin + found := False; + for i := 0 to Result.Count - 1 do + if Result[i].Factor = factor then + begin + found := True; + normal := Result[i]; + Inc(normal.Exponent); + Result[i] := normal; + Break; + end; + if not found then + begin + normal.Factor := factor; + normal.Exponent := 1; + Result.Add(normal); + end; + end; +end; + +{ TDividersEnumerator } + +function TDividersEnumerator.GetCount: Integer; +var + factor: TIntegerFactor; +begin + if FFactors.Count > 0 then + begin + Result := 1; + for factor in FFactors do + Result := Result * factor.Exponent; + Dec(Result); + end + else + Result := 0; +end; + +constructor TDividersEnumerator.Create(constref AIntegerFactorArray: TInt64Array); +begin + FFactors := TIntegerFactorization.GetNormalized(AIntegerFactorArray); + SetLength(FCurrentExponents, FFactors.Count); +end; + +destructor TDividersEnumerator.Destroy; +begin + FFactors.Free; +end; + +function TDividersEnumerator.GetCurrent: Int64; +var + i: Integer; +begin + Result := 1; + for i := Low(FCurrentExponents) to High(FCurrentExponents) do + if FCurrentExponents[i] > 0 then + Result := Result * Round(Power(FFactors[i].Factor, FCurrentExponents[i])); +end; + +function TDividersEnumerator.MoveNext: Boolean; +var + i: Integer; +begin + Result := False; + i := 0; + while (i <= High(FCurrentExponents)) and (FCurrentExponents[i] >= FFactors[i].Exponent) do + begin + FCurrentExponents[i] := 0; + Inc(i); + end; + + if i <= High(FCurrentExponents) then + begin + Inc(FCurrentExponents[i]); + Result := True; + end; +end; + +procedure TDividersEnumerator.Reset; +var + i: Integer; +begin + for i := Low(FCurrentExponents) to High(FCurrentExponents) do + FCurrentExponents[i] := 0; +end; + +{ TDividers } + +constructor TDividers.Create(constref AIntegerFactorArray: TInt64Array); +begin + FFactorArray := AIntegerFactorArray; +end; + +function TDividers.GetEnumerator: TDividersEnumerator; +begin + Result := TDividersEnumerator.Create(FFactorArray); +end; + end. diff --git a/UPolynomial.pas b/UPolynomial.pas new file mode 100644 index 0000000..f4cec85 --- /dev/null +++ b/UPolynomial.pas @@ -0,0 +1,297 @@ +{ + Solutions to the Advent Of Code. + Copyright (C) 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 UPolynomial; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, UBigInt; + +type + TInt64Array = array of Int64; + + { TBigIntPolynomial } + + TBigIntPolynomial = object + private + FCoefficients: array of TBigInt; + function GetDegree: Integer; + function GetCoefficient(const AIndex: Integer): TBigInt; + public + property Degree: Integer read GetDegree; + property Coefficient[const AIndex: Integer]: TBigInt read GetCoefficient; + function CalcValueAt(const AX: Int64): TBigInt; + function CalcSignVariations: Integer; + + // Returns 2^n * f(x), given a polynomial f(x) and exponent n. + function ScaleByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; + + // Returns f(s * x), given a polynomial f(x) and scale factor s. + function ScaleVariable(const AScaleFactor: TBigInt): TBigIntPolynomial; + + // Returns f(2^n * x), given a polynomial f(x) and an exponent n. + function ScaleVariableByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; + + // Returns f(x / 2), given a polynomial f(x). + function ScaleVariableByHalf: TBigIntPolynomial; + + // Returns f(x + 1), given a polynomial f(x). + function TranslateVariableByOne: TBigIntPolynomial; + + // Returns a polynomial with the reverse order of coefficients, i.e. the polynomial + // a_0 * x^n + a_1 * x^(n - 1) + ... + a_(n - 1) * x + a_n, + // given a polynomial + // a_n * x^n + a_(n - 1) * x^(n - 1) + ... + a_1 * x + a_0. + function RevertOrderOfCoefficients: TBigIntPolynomial; + + // Returns a polynomial with all coefficents shifted down one position, and the constant term removed. This should + // only be used when the constant term is zero and is then equivalent to a division of polynomial f(x) by x. + function DivideByVariable: TBigIntPolynomial; + function IsEqualTo(const AOther: TBigIntPolynomial): Boolean; + function ToString: string; + class function Create(const ACoefficients: array of TBigInt): TBigIntPolynomial; static; + end; + + { Operators } + + operator = (const A, B: TBigIntPolynomial): Boolean; + operator <> (const A, B: TBigIntPolynomial): Boolean; + +implementation + +{ TBigIntPolynomial } + +function TBigIntPolynomial.GetDegree: Integer; +begin + Result := Length(FCoefficients) - 1; +end; + +function TBigIntPolynomial.GetCoefficient(const AIndex: Integer): TBigInt; +begin + Result := FCoefficients[AIndex]; +end; + +function TBigIntPolynomial.CalcValueAt(const AX: Int64): TBigInt; +var + i: Integer; +begin + Result := TBigInt.Zero; + for i := High(FCoefficients) downto 0 do + Result := Result * AX + FCoefficients[i]; +end; + +function TBigIntPolynomial.CalcSignVariations: Integer; +var + current, last, i: Integer; +begin + Result := 0; + last := 0; + for i := 0 to Length(FCoefficients) - 1 do + begin + current := FCoefficients[i].Sign; + if (current <> 0) and (last <> current) then + begin + if last <> 0 then + Inc(Result); + last := current + end; + end; +end; + +function TBigIntPolynomial.ScaleByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; +var + len, i: Integer; +begin + len := Length(FCoefficients); + SetLength(Result.FCoefficients, len); + for i := 0 to len - 1 do + Result.FCoefficients[i] := FCoefficients[i] << AExponent; +end; + +function TBigIntPolynomial.ScaleVariable(const AScaleFactor: TBigInt): TBigIntPolynomial; +var + len, i: Integer; + factor: TBigInt; +begin + if AScaleFactor <> TBigInt.Zero then + begin + len := Length(FCoefficients); + SetLength(Result.FCoefficients, len); + Result.FCoefficients[0] := FCoefficients[0]; + factor := AScaleFactor; + for i := 1 to len - 1 do begin + Result.FCoefficients[i] := FCoefficients[i] * factor; + factor := factor * AScaleFactor; + end; + end + else begin + SetLength(Result.FCoefficients, 1); + Result.FCoefficients[0] := TBigInt.Zero; + end; +end; + +function TBigIntPolynomial.ScaleVariableByPowerOfTwo(const AExponent: Cardinal): TBigIntPolynomial; +var + len, i: Integer; + shift: Cardinal; +begin + len := Length(FCoefficients); + SetLength(Result.FCoefficients, len); + Result.FCoefficients[0] := FCoefficients[0]; + shift := AExponent; + for i := 1 to len - 1 do begin + Result.FCoefficients[i] := FCoefficients[i] << shift; + Inc(shift, AExponent); + end; +end; + +function TBigIntPolynomial.ScaleVariableByHalf: TBigIntPolynomial; +var + len, i: Integer; +begin + len := Length(FCoefficients); + SetLength(Result.FCoefficients, len); + Result.FCoefficients[0] := FCoefficients[0]; + for i := 1 to len - 1 do + Result.FCoefficients[i] := FCoefficients[i] >> i; +end; + +function TBigIntPolynomial.TranslateVariableByOne: TBigIntPolynomial; +var + len, i, j: Integer; + factors: array of Cardinal; +begin + len := Length(FCoefficients); + SetLength(Result.FCoefficients, len); + SetLength(factors, len); + for i := 0 to len - 1 do + begin + Result.FCoefficients[i] := TBigInt.Zero; + factors[i] := 1; + end; + + // Calculates new coefficients. + for i := 0 to len - 1 do + begin + for j := 0 to len - i - 1 do + begin + if (i <> 0) and (j <> 0) then + factors[j] := factors[j] + factors[j - 1]; + Result.FCoefficients[i] := Result.FCoefficients[i] + factors[j] * FCoefficients[j + i]; + end; + end; +end; + +function TBigIntPolynomial.RevertOrderOfCoefficients: TBigIntPolynomial; +var + len, skip, i: Integer; +begin + // Counts the trailing zeros to skip. + len := Length(FCoefficients); + skip := 0; + while (skip < len) and (FCoefficients[skip] = 0) do + Inc(skip); + + // Copies the other coefficients in reverse order. + SetLength(Result.FCoefficients, len - skip); + for i := skip to len - 1 do + Result.FCoefficients[len - i - 1] := FCoefficients[i]; +end; + +function TBigIntPolynomial.DivideByVariable: TBigIntPolynomial; +var + len: Integer; +begin + len := Length(FCoefficients); + if len > 1 then + Result.FCoefficients := Copy(FCoefficients, 1, len - 1) + else begin + SetLength(Result.FCoefficients, 1); + Result.FCoefficients[0] := TBigInt.Zero; + end; +end; + +function TBigIntPolynomial.IsEqualTo(const AOther: TBigIntPolynomial): Boolean; +var + i: Integer; +begin + if Length(FCoefficients) = Length(AOther.FCoefficients) then + begin + Result := True; + for i := 0 to Length(FCoefficients) - 1 do + if FCoefficients[i] <> AOther.FCoefficients[i] then + begin + Result := False; + Break; + end; + end + else + Result := False; +end; + +function TBigIntPolynomial.ToString: string; +var + i: Integer; +begin + Result := FCoefficients[0].ToString; + for i := 1 to Length(FCoefficients) - 1 do + if i > 1 then + Result := Result + ' + ' + FCoefficients[i].ToString + ' * x^' + IntToStr(i) + else + Result := Result + ' + ' + FCoefficients[i].ToString + ' * x'; +end; + +class function TBigIntPolynomial.Create(const ACoefficients: array of TBigInt): TBigIntPolynomial; +var + high, i: integer; +begin + high := -1; + for i := Length(ACoefficients) - 1 downto 0 do + if ACoefficients[i] <> 0 then + begin + high := i; + Break; + end; + if high >= 0 then + begin + SetLength(Result.FCoefficients, high + 1); + for i := 0 to high do + Result.FCoefficients[i] := ACoefficients[i]; + end + else begin + SetLength(Result.FCoefficients, 1); + Result.FCoefficients[0] := TBigInt.Zero; + end; +end; + +{ Operators } + +operator = (const A, B: TBigIntPolynomial): Boolean; +begin + Result := A.IsEqualTo(B); +end; + +operator <> (const A, B: TBigIntPolynomial): Boolean; +begin + Result := not A.IsEqualTo(B); +end; + +end. + diff --git a/UPolynomialRoots.pas b/UPolynomialRoots.pas new file mode 100644 index 0000000..fc244e5 --- /dev/null +++ b/UPolynomialRoots.pas @@ -0,0 +1,205 @@ +{ + Solutions to the Advent Of Code. + Copyright (C) 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 UPolynomialRoots; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, Generics.Collections, UPolynomial, UBigInt; + +type + + { TIsolatingInterval } + + // Represents an isolating interval of the form [C / 2^K, (C + H) / 2^K] in respect to [0, 1] or [A, B] in respect to + // [0, 2^boundexp], with A = C * 2^boundexp / 2^K and B = (C + H) * 2^boundexp / 2^K. + TIsolatingInterval = record + C: TBigInt; + K, H, BoundExp: Cardinal; + A, B: TBigInt; + end; + + TIsolatingIntervals = specialize TList; + + TIsolatingIntervalArray = array of TIsolatingInterval; + + { TPolynomialRoots } + + TPolynomialRoots = class + private + // Returns the exponent (base two) of an upper bound for the roots of the given polynomial, i.e. all real roots of + // the given polynomial are less or equal than 2^b, where b is the returned positive integer. + class function CalcUpperRootBound(constref APolynomial: TBigIntPolynomial): Cardinal; + class function CreateIsolatingInterval(constref AC: TBigInt; const AK, AH: Cardinal; constref ABoundExp: Cardinal): + TIsolatingInterval; + public + // Returns root-isolating intervals for non-negative, non-multiple roots. + class function BisectIsolation(constref APolynomial: TBigIntPolynomial): TIsolatingIntervalArray; + // Returns root-isolating intervals for non-multiple roots in the interval [0, 2^boundexp]. + class function BisectIsolation(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal; + const AFindIntegers: Boolean = False): TIsolatingIntervalArray; + // Returns non-negative, non-multiple, integer roots in the interval [0, 2^boundexp]. + class function BisectInteger(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal): + TBigIntArray; + end; + +implementation + +{ TPolynomialRoots } + +class function TPolynomialRoots.CalcUpperRootBound(constref APolynomial: TBigIntPolynomial): Cardinal; +var + i, sign: Integer; + an, ai, max: TBigInt; + numeratorBit, denominatorBit: Int64; +begin + // We need a_n > 0 here, so we use -sign(a_n) instead of actually flipping the polynomial. + // Sign is not 0 because a_n is not 0. + an := APolynomial.Coefficient[APolynomial.Degree]; + sign := -an.Sign; + + // This is a simplification of Cauchy's bound to avoid division and make it a power of two. + // https://en.wikipedia.org/wiki/Geometrical_properties_of_polynomial_roots#Bounds_of_positive_real_roots + max := TBigInt.Zero; + for i := 0 to APolynomial.Degree - 1 do begin + ai := sign * APolynomial.Coefficient[i]; + if max < ai then + max := ai; + end; + numeratorBit := max.GetMostSignificantBitIndex + 1; + denominatorBit := an.GetMostSignificantBitIndex; + Result := numeratorBit - denominatorBit; +end; + +class function TPolynomialRoots.CreateIsolatingInterval(constref AC: TBigInt; const AK, AH: Cardinal; + constref ABoundExp: Cardinal): TIsolatingInterval; +begin + Result.C := AC; + Result.K := AK; + Result.H := AH; + Result.BoundExp := ABoundExp; + if ABoundExp >= AK then + begin + Result.A := AC << (ABoundExp - AK); + Result.B := (AC + AH) << (ABoundExp - AK); + end + else begin + Result.A := AC << (ABoundExp - AK); + Result.B := (AC + AH) << (ABoundExp - AK); + end; +end; + +class function TPolynomialRoots.BisectIsolation(constref APolynomial: TBigIntPolynomial): TIsolatingIntervalArray; +var + boundExp: Cardinal; +begin + boundExp := CalcUpperRootBound(APolynomial); + Result := BisectIsolation(APolynomial, boundExp); +end; + +// This is adapted from https://en.wikipedia.org/wiki/Real-root_isolation#Bisection_method +class function TPolynomialRoots.BisectIsolation(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal; + const AFindIntegers: Boolean): TIsolatingIntervalArray; +type + TWorkItem = record + C: TBigInt; + K: Cardinal; + P: TBigIntPolynomial; + end; + TWorkStack = specialize TStack; +var + item: TWorkItem; + stack: TWorkStack; + n, v: Integer; + varq: TBigIntPolynomial; + iso: TIsolatingIntervals; +begin + iso := TIsolatingIntervals.Create; + stack := TWorkStack.Create; + + item.C := 0; + item.K := 0; + item.P := APolynomial.ScaleVariableByPowerOfTwo(ABoundExp); + stack.Push(item); + n := item.P.Degree; + + while stack.Count > 0 do + begin + item := stack.Pop; + if item.P.Coefficient[0] = TBigInt.Zero then + begin + // Found an integer root at 0. + item.P := item.P.DivideByVariable; + Dec(n); + iso.Add(CreateIsolatingInterval(item.C, item.K, 0, ABoundExp)); + end; + + varq := item.P.RevertOrderOfCoefficients.TranslateVariableByOne; + v := varq.CalcSignVariations; + if (v > 1) + or ((v = 1) and AFindIntegers and (item.K < ABoundExp)) then + begin + // Bisects, first new work item is (2c, k + 1, 2^n * q(x/2)). + item.C := item.C << 1; + Inc(item.K); + item.P := item.P.ScaleVariableByHalf.ScaleByPowerOfTwo(n); + stack.Push(item); + // ... second new work item is (2c + 1, k + 1, 2^n * q((x+1)/2)). + item.C := item.C + 1; + item.P := item.P.TranslateVariableByOne; + stack.Push(item); + end + else if v = 1 then + begin + // Found isolating interval. + iso.Add(CreateIsolatingInterval(item.C, item.K, 1, ABoundExp)); + end; + end; + Result := iso.ToArray; + iso.Free; + stack.Free; +end; + +class function TPolynomialRoots.BisectInteger(constref APolynomial: TBigIntPolynomial; constref ABoundExp: Cardinal): + TBigIntArray; +var + intervals: TIsolatingIntervalArray; + i: TIsolatingInterval; + r: specialize TList; + value: Int64; +begin + // Calculates isolating intervals. + intervals := BisectIsolation(APolynomial, ABoundExp, True); + r := specialize TList.Create; + + for i in intervals do + if i.H = 0 then + r.Add(i.A) + else if i.A.TryToInt64(value) and (APolynomial.CalcValueAt(value) = 0) then + r.Add(value) + else if i.B.TryToInt64(value) and (APolynomial.CalcValueAt(value) = 0) then + r.Add(value); + + Result := r.ToArray; + r.Free; +end; + +end. + diff --git a/solvers/UNeverTellMeTheOdds.pas b/solvers/UNeverTellMeTheOdds.pas index 6f5c98d..af87de3 100644 --- a/solvers/UNeverTellMeTheOdds.pas +++ b/solvers/UNeverTellMeTheOdds.pas @@ -1,6 +1,6 @@ { Solutions to the Advent Of Code. - Copyright (C) 2023 Stefan Müller + Copyright (C) 2023-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 @@ -22,26 +22,37 @@ unit UNeverTellMeTheOdds; interface uses - Classes, SysUtils, Generics.Collections, Math, USolver; + Classes, SysUtils, Generics.Collections, Math, USolver, UNumberTheory, UBigInt, UPolynomial, UPolynomialRoots; type { THailstone } - THailstone = record - X, Y, Z: Int64; - VX, VY, VZ: Integer; + THailstone = class + public + P0, P1, P2: Int64; + V0, V1, V2: Integer; + constructor Create(const ALine: string); + constructor Create; end; - THailstones = specialize TList; + THailstones = specialize TObjectList; + + TInt64Array = array of Int64; { TNeverTellMeTheOdds } TNeverTellMeTheOdds = class(TSolver) private FMin, FMax: Int64; - FHailStones: THailstones; + FHailstones: THailstones; function AreIntersecting(constref AHailstone1, AHailstone2: THailstone): Boolean; + function FindRockThrow(const AIndex0, AIndex1, AIndex2: Integer): Int64; + procedure CalcCollisionPolynomials(constref AHailstone0, AHailstone1, AHailstone2: THailstone; out OPolynomial0, + OPolynomial1: TBigIntPolynomial); + function CalcRockThrowCollisionOptions(constref AHailstone0, AHailstone1, AHailstone2: THailstone): TInt64Array; + function ValidateRockThrow(constref AHailstone0, AHailstone1, AHailstone2: THailstone; const AT0, AT1: Int64): + Int64; public constructor Create(const AMin: Int64 = 200000000000000; const AMax: Int64 = 400000000000000); destructor Destroy; override; @@ -53,6 +64,26 @@ type implementation +{ THailstone } + +constructor THailstone.Create(const ALine: string); +var + split: TStringArray; +begin + split := ALine.Split([',', '@']); + P0 := StrToInt64(Trim(split[0])); + P1 := StrToInt64(Trim(split[1])); + P2 := StrToInt64(Trim(split[2])); + V0 := StrToInt(Trim(split[3])); + V1 := StrToInt(Trim(split[4])); + V2 := StrToInt(Trim(split[5])); +end; + +constructor THailstone.Create; +begin + +end; + { TNeverTellMeTheOdds } function TNeverTellMeTheOdds.AreIntersecting(constref AHailstone1, AHailstone2: THailstone): Boolean; @@ -60,58 +91,432 @@ var m1, m2, x, y: Double; begin Result := False; - m1 := AHailstone1.VY / AHailstone1.VX; - m2 := AHailstone2.VY / AHailstone2.VX; + m1 := AHailstone1.V1 / AHailstone1.V0; + m2 := AHailstone2.V1 / AHailstone2.V0; if m1 <> m2 then begin - x := (AHailstone2.Y - m2 * AHailstone2.X - AHailstone1.Y + m1 * AHailstone1.X) / (m1 - m2); + x := (AHailstone2.P1 - m2 * AHailstone2.P0 + - AHailstone1.P1 + m1 * AHailstone1.P0) + / (m1 - m2); if (FMin <= x) and (x <= FMax) - and (x * Sign(AHailstone1.VX) >= AHailstone1.X * Sign(AHailstone1.VX)) - and (x * Sign(AHailstone2.VX) >= AHailstone2.X * Sign(AHailstone2.VX)) then + and (x * Sign(AHailstone1.V0) >= AHailstone1.P0 * Sign(AHailstone1.V0)) + and (x * Sign(AHailstone2.V0) >= AHailstone2.P0 * Sign(AHailstone2.V0)) + then begin - y := m1 * (x - AHailstone1.X) + AHailstone1.Y; + y := m1 * (x - AHailstone1.P0) + AHailstone1.P1; if (FMin <= y) and (y <= FMax) then Result := True end; end; end; +function TNeverTellMeTheOdds.FindRockThrow(const AIndex0, AIndex1, AIndex2: Integer): Int64; +var + t0, t1: TInt64Array; + i, j: Int64; +begin + t0 := CalcRockThrowCollisionOptions(FHailstones[AIndex0], FHailstones[AIndex1], FHailstones[AIndex2]); + t1 := CalcRockThrowCollisionOptions(FHailstones[AIndex1], FHailstones[AIndex0], FHailstones[AIndex2]); + + Result := 0; + for i in t0 do + begin + for j in t1 do + begin + Result := ValidateRockThrow(FHailstones[AIndex0], FHailstones[AIndex1], FHailstones[AIndex2], i, j); + if Result > 0 then + Break; + end; + if Result > 0 then + Break; + end; +end; + +procedure TNeverTellMeTheOdds.CalcCollisionPolynomials(constref AHailstone0, AHailstone1, AHailstone2: THailstone; out + OPolynomial0, OPolynomial1: TBigIntPolynomial); +var + k: array[0..74] of TBigInt; +begin + // Solving this non-linear equation system, with velocities V_i and start positions P_i: + // V_0 * t_0 + P_0 = V_x * t_0 + P_x + // V_1 * t_1 + P_1 = V_x * t_1 + P_x + // V_2 * t_2 + P_2 = V_x * t_2 + P_x + // Which gives: + // P_x = (V_0 - V_x) * t_0 + P_0 + // V_x = (V_0 * t_0 - V_1 * t_1 + P_0 - P_1) / (t_0 - t_1) + // And with vertex components: + // 1: 0 = (t_1 - t_0) * (V_00 * t_0 - V_20 * t_2 + P_00 - P_20) + // - (t_2 - t_0) * (V_00 * t_0 - V_10 * t_1 + P_00 - P_10) + // 2: t_1 = (((V_01 - V_21) * t_2 + P_11 - P_21) * t_0 + (P_01 - P_11) * t_2) + // / ((V_01 - V_11) * t_0 + (V_11 - V_21) * t_2 + P_01 - P_21) + // 3: t_2 = (((V_02 - V_12) * t_1 + P_22 - P_12) * t_0 + (P_02 - P_22) * t_1) + // / ((V_02 - V_22) * t_0 + (V_22 - V_12) * t_1 + P_02 - P_12) + // for t_0, t_1, t_2 not pairwise equal. + // With some substitutions depending only on t_0 this gives + // 1: 0 = (t_1 - t_0) * (a_1 - V_20 * t_2) - (t_2 - t_0) * (a_2 - V_10 * t_1) + // 2: t_1 = (b_0 + b_1 * t_2) / (c_0 + c_1 * t_2) + // 3: t_2 = (d_0 + d_1 * t_1) / (e_0 + e_1 * t_1) + // And 3 in 2 gives: + // 4: f_2 * t_1^2 + f_1 * t_1 - f_0 = 0 + // Then, with 4 and 3 in 1 and many substitutions (see constants k below, now independent of t_0), the equation + // 5: 0 = p_0(t_0) + p_1(t_0) * sqrt(p_2(t_0)) + // can be constructed, where p_0, p_1, and p_2 are polynomials in t_0. Since we are searching for an integer solution, + // we assume that there is an integer t_0 that is a root of both p_0 and p_1, which would solve the equation. + + // Subsitutions depending on t_0: + // a_1 = V_00 * t_0 + P_00 - P_20 + // a_2 = V_00 * t_0 + P_00 - P_10 + // b_0 = (P_11 - P_21) * t_0 + // b_1 = (V_01 - V_21) * t_0 + P_01 - P_11 + // c_0 = (V_01 - V_11) * t_0 + P_01 - P_21 + // c_1 = V_11 - V_21 + // d_0 = (P_22 - P_12) * t_0 + // d_1 = (V_02 - V_12) * t_0 + P_02 - P_22 + // e_0 = (V_02 - V_22) * t_0 + P_02 - P_12 + // e_1 = V_22 - V_12 + // f_0 = b_1 * d_0 + b_0 * e_0 + // f_1 = c_0 * e_0 + c_1 * d_0 - b_0 * e_1 - b_1 * d_1 + // f_2 = c_0 * e_1 + c_1 * d_1 + + // Calculations for equation 5 (4 and 3 in 1). + // 1: 0 = (t_1 - t_0) * (a_1 - V_20 * t_2) - (t_2 - t_0) * (a_2 - V_10 * t_1) + // 3: (e_0 + e_1 * t_1) * t_2 = (d_0 + d_1 * t_1) + // 0 = (t_1 - t_0) * (a_1 - V_20 * t_2) - (t_2 - t_0) * (a_2 - V_10 * t_1) + // = (t_1 - t_0) * (a_1 * (e_0 + e_1 * t_1) - V_20 * (e_0 + e_1 * t_1) * t_2) - ((e_0 + e_1 * t_1) * t_2 - (e_0 + e_1 * t_1) * t_0) * (a_2 - V_10 * t_1) + // = (t_1 - t_0) * (a_1 * (e_0 + e_1 * t_1) - V_20 * (d_0 + d_1 * t_1)) - ((d_0 + d_1 * t_1) - (e_0 + e_1 * t_1) * t_0) * (a_2 - V_10 * t_1) + // = (t_1 - t_0) * (a_1 * e_0 + a_1 * e_1 * t_1 - V_20 * d_0 - V_20 * d_1 * t_1) - (d_0 + d_1 * t_1 - e_0 * t_0 - e_1 * t_1 * t_0) * (a_2 - V_10 * t_1) + // = (a_1 * e_1 - V_20 * d_1) * t_1^2 + (a_1 * e_0 - V_20 * d_0 - t_0 * (a_1 * e_1 - V_20 * d_1)) * t_1 - t_0 * (a_1 * e_0 - V_20 * d_0) + // - ( - V_10 * (d_1 - e_1 * t_0) * t_1^2 + ((d_1 - e_1 * t_0) * a_2 - V_10 * (d_0 - e_0 * t_0)) * t_1 + (d_0 - e_0 * t_0) * a_2) + // = (a_1 * e_1 - V_20 * d_1 + V_10 * (d_1 - e_1 * t_0)) * t_1^2 + // + (a_1 * e_0 - V_20 * d_0 - t_0 * (a_1 * e_1 - V_20 * d_1) - (d_1 - e_1 * t_0) * a_2 + V_10 * (d_0 - e_0 * t_0)) * t_1 + // + t_0 * (V_20 * d_0 - a_1 * e_0) + (e_0 * t_0 - d_0) * a_2 + // Inserting 4, solved for t_0: t_1 = - f_1 / (2 * f_2) + sqrt((f_1 / (2 * f_2))^2 + f_0 / f_2) + // = (a_1 * e_1 - V_20 * d_1 + V_10 * (d_1 - e_1 * t_0)) * (f_1^2 + 2 * f_0 * f_2 - f_1 * sqrt(f_1^2 + 4 * f_0 * f_2)) + // + (a_1 * e_0 - V_20 * d_0 - t_0 * (a_1 * e_1 - V_20 * d_1) - (d_1 - e_1 * t_0) * a_2 + V_10 * (d_0 - e_0 * t_0)) * (- f_1 * f_2 + f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) + // + t_0 * (V_20 * d_0 - a_1 * e_0) * 2 * f_2^2 + (e_0 * t_0 - d_0) * a_2 * 2 * f_2^2 + + // a_1 = V_00 * t_0 + k_0 + // a_2 = V_00 * t_0 + k_1 + // b_0 = k_2 * t_0 + // b_1 = k_10 * t_0 + k_3 + // c_0 = k_11 * t_0 + k_4 + // d_0 = k_5 * t_0 + // d_1 = k_12 * t_0 + k_6 + // e_0 = k_13 * t_0 + k_7 + // f_2 = (k_11 * t_0 + k_4) * k_9 + k_8 * (k_12 * t_0 + k_6) + // = (k_11 * k_9 + k_8 * k_12) * t_0 + k_4 * k_9 + k_8 * k_6 + // = k_14 * t_0 + k_15 + // f_1 = (k_11 * t_0 + k_4) * (k_13 * t_0 + k_7) + k_8 * k_5 * t_0 - k_2 * t_0 * k_9 - (k_10 * t_0 + k_3) * (k_12 * t_0 + k_6) + // = (k_11 * k_13 - k_10 * k_12) * t_0^2 + (k_11 * k_7 + k_4 * k_12 + k_8 * k_5 - k_2 * k_9 - k_10 * k_6 - k_3 * k_12) * t_0 + k_4 * k_7 - k_3 * k_6 + // = k_16 * t_0^2 + k_17 * t_0 + k_18 + // f_0 = (k_10 * t_0 + k_3) * k_5 * t_0 + k_2 * t_0 * (k_13 * t_0 + k_7) + // = (k_10 * k_5 + k_2 * k_13) * t_0^2 + (k_3 * k_5 + k_2 * k_7) * t_0 + // = k_19 * t_0^2 + k_20 * t_0 + + k[0] := AHailstone0.P0 - AHailstone2.P0; + k[1] := AHailstone0.P0 - AHailstone1.P0; + k[2] := AHailstone1.P1 - AHailstone2.P1; + k[3] := AHailstone0.P1 - AHailstone1.P1; + k[4] := AHailstone0.P1 - AHailstone2.P1; + k[5] := AHailstone2.P2 - AHailstone1.P2; + k[6] := AHailstone0.P2 - AHailstone2.P2; + k[7] := AHailstone0.P2 - AHailstone1.P2; + k[8] := AHailstone1.V1 - AHailstone2.V1; + k[9] := AHailstone2.V2 - AHailstone1.V2; + k[10] := AHailstone0.V1 - AHailstone2.V1; + k[11] := AHailstone0.V1 - AHailstone1.V1; + k[12] := AHailstone0.V2 - AHailstone1.V2; + k[13] := AHailstone0.V2 - AHailstone2.V2; + + k[14] := k[11] * k[9] + k[8] * k[12]; + k[15] := k[4] * k[9] + k[8] * k[6]; + k[16] := k[11] * k[13] - k[10] * k[12]; + k[17] := k[11] * k[7] + k[4] * k[13] + k[8] * k[5] - k[2] * k[9] - k[10] * k[6] - k[3] * k[12]; + k[18] := k[4] * k[7] - k[3] * k[6]; + k[19] := k[10] * k[5] + k[2] * k[13]; + k[20] := k[3] * k[5] + k[2] * k[7]; + + // Additional substitutions. + // a_1 * k_9 - V_20 * d_1 + // = (V_00 * t_0 + k_0) * k_9 - V_20 * (k_12 * t_0 + k_6) + // = (V_00 * k_9 - V_20 * k_12) * t_0 + k_0 * k_9 - V_20 * k_6 + // = k_21 * t_0 + k_22 + // d_1 - k_9 * t_0 + // = k_12 * t_0 + k_6 - k_9 * t_0 + // = (k_12 - k_9) * t_0 + k_6 + // a_1 * e_0 - V_20 * d_0 + // = (V_00 * t_0 + k_0) * (k_13 * t_0 + k_7) - V_20 * k_5 * t_0 + // = V_00 * k_13 * t_0^2 + (V_00 * k_7 + k_0 * k_13 - V_20 * k_5) * t_0 + k_0 * k_7 + // = k_23 * t_0^2 + k_24 * t_0 + k_25 + // d_0 - e_0 * t_0 + // = k_5 * t_0 - k_13 * t_0^2 - k_7 * t_0 + // = - k_13 * t_0^2 + k_26 * t_0 + // f_1^2 + // = (k_16 * t_0^2 + k_17 * t_0 + k_18)^2 + // = k_16^2 * t_0^4 + k_17^2 * t_0^2 + k_18^2 + 2 * k_16 * t_0^2 * k_17 * t_0 + 2 * k_16 * t_0^2 * k_18 + 2 * k_17 * t_0 * k_18 + // = k_16^2 * t_0^4 + 2 * k_16 * k_17 * t_0^3 + (k_17^2 + 2 * k_16 * k_18) * t_0^2 + 2 * k_17 * k_18 * t_0 + k_18^2 + // = k_16^2 * t_0^4 + k_27 * t_0^3 + k_29 * t_0^2 + k_30 * t_0 + k_18^2 + // f_2^2 + // = (k_14 * t_0 + k_15)^2 + // = k_14^2 * t_0^2 + 2 * k_14 * k_15 * t_0 + k_15^2 + // = k_14^2 * t_0^2 + k_31 * t_0 + k_15^2 + // f_0 * f_2 + // = (k_19 * t_0^2 + k_20 * t_0) * (k_14 * t_0 + k_15) + // = k_19 * k_14 * t_0^3 + (k_19 * k_15 + k_20 * k_14) * t_0^2 + k_20 * k_15 * t_0 + // = k_33 * t_0^3 + k_34 * t_0^2 + k_35 * t_0 + // f_1^2 + 4 * f_0 * f_2 + // = k_16^2 * t_0^4 + k_27 * t_0^3 + k_29 * t_0^2 + k_30 * t_0 + k_18^2 + 4 * (k_33 * t_0^3 + k_34 * t_0^2 + k_35 * t_0) + // = k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59 + // f_1^2 + 2 * f_0 * f_2 + // = k_16^2 * t_0^4 + k_27 * t_0^3 + k_29 * t_0^2 + k_30 * t_0 + k_18^2 + 2 * (k_33 * t_0^3 + k_34 * t_0^2 + k_35 * t_0) + // = k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59 + + k[21] := AHailstone0.V0 * k[9] - AHailstone2.V0 * k[12]; + k[22] := k[0] * k[9] - AHailstone2.V0 * k[6]; + k[23] := AHailstone0.V0 * k[13]; + k[24] := AHailstone0.V0 * k[7] + k[0] * k[13] - AHailstone2.V0 * k[5]; + k[25] := k[0] * k[7]; + k[26] := k[5] - k[7]; + k[27] := 2 * k[16] * k[17]; + k[28] := k[17] * k[17]; + k[29] := k[28] + 2 * k[16] * k[18]; + k[30] := 2 * k[17] * k[18]; + k[31] := 2 * k[14] * k[15]; + k[32] := k[14] * k[14]; + k[33] := k[19] * k[14]; + k[34] := k[19] * k[15] + k[20] * k[14]; + k[35] := k[20] * k[15]; + k[36] := k[15] * k[15]; + k[37] := k[16] * k[16]; + k[38] := k[27] + 2 * k[33]; + k[39] := k[29] + 2 * k[34]; + k[40] := k[30] + 2 * k[35]; + k[41] := k[21] + AHailstone1.V0 * (k[12] - k[9]); + k[42] := k[22] + AHailstone1.V0 * k[6]; + k[43] := k[23] - k[21] - AHailstone1.V0 * k[13] - (k[12] - k[9]) * AHailstone0.V0; + k[44] := k[24] - k[22] + AHailstone1.V0 * k[26] - (k[12] - k[9]) * k[1] - k[6] * AHailstone0.V0; + k[45] := k[25] - k[6] * k[1]; + k[46] := k[43] * k[14] - k[41] * k[16]; + k[47] := k[43] * k[15] + k[44] * k[14] - k[42] * k[16] - k[41] * k[17]; + k[48] := k[44] * k[15] + k[45] * k[14] - k[42] * k[17] - k[41] * k[18]; + k[49] := k[45] * k[15] - k[42] * k[18]; + k[50] := k[43] * k[16]; + k[51] := k[41] * k[37] - k[50] * k[14]; + k[52] := k[43] * k[17] + k[44] * k[16]; + k[53] := k[41] * k[38] + k[42] * k[37] - k[52] * k[14] - k[50] * k[15]; + k[54] := k[43] * k[18] + k[44] * k[17] + k[45] * k[16]; + k[55] := k[41] * k[39] + k[42] * k[38] - k[54] * k[14] - k[52] * k[15]; + k[56] := k[44] * k[18] + k[45] * k[17]; + k[57] := k[41] * k[40] + k[42] * k[39] - k[56] * k[14] - k[54] * k[15]; + k[58] := k[45] * k[18]; + k[59] := k[18] * k[18]; + k[60] := k[41] * k[59] + k[42] * k[40] - k[58] * k[14] - k[56] * k[15]; + k[61] := k[42] * k[59] - k[58] * k[15]; + k[62] := k[13] * AHailstone0.V0 - k[23]; + k[63] := 2 * k[32] * k[62]; + k[64] := k[13] * k[1] - k[26] * AHailstone0.V0 - k[24]; + k[65] := 2 * (k[31] * k[62] + k[32] * k[64]); + k[66] := - k[26] * k[1] - k[25]; + k[67] := 2 * (k[36] * k[62] + k[31] * k[64] + k[32] * k[66]); + k[68] := 2 * (k[36] * k[64] + k[31] * k[66]); + k[69] := 2 * k[36] * k[66]; + k[70] := k[51] + k[63]; + k[71] := k[53] + k[65]; + k[72] := k[55] + k[67]; + k[73] := k[57] + k[68]; + k[74] := k[60] + k[69]; + // Unused, they are part of the polynomial inside the square root. + //k[75] := k[27] + 4 * k[33]; + //k[76] := k[29] + 4 * k[34]; + //k[77] := k[30] + 4 * k[35]; + + // Continuing calculations for equation 5. + // 0 = (k_21 * t_0 + k_22 + V_10 * ((k_12 - k_9) * t_0 + k_6)) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59 -+ f_1 * sqrt(f_1^2 + 4 * f_0 * f_2)) + // + (k_23 * t_0^2 + k_24 * t_0 + k_25 - t_0 * (k_21 * t_0 + k_22) - ((k_12 - k_9) * t_0 + k_6) * a_2 - V_10 * (k_13 * t_0^2 - k_26 * t_0)) * (- f_1 * f_2 +- f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) + // - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + // 0 = (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59 -+ f_1 * sqrt(f_1^2 + 4 * f_0 * f_2)) + // + ((k_23 - k_21 - V_10 * k_13 - (k_12 - k_9) * V_00) * t_0^2 + (k_24 - k_22 + V_10 * k_26 - (k_12 - k_9) * k_1 - k_6 * V_00) * t_0 + k_25 - k_6 * k_1) * (- f_1 * f_2 +- f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) + // - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + // 0 = (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) + // -+ (k_41 * t_0 + k_42) * f_1 * sqrt(f_1^2 + 4 * f_0 * f_2) + // + (k_43 * t_0^2 + k_44 * t_0 + k_45) * (- f_1 * f_2 +- f_2 * sqrt(f_1^2 + 4 * f_0 * f_2)) + // - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + // 0 = (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) + // -+ (k_41 * t_0 + k_42) * f_1 * sqrt(f_1^2 + 4 * f_0 * f_2) + // - (k_43 * t_0^2 + k_44 * t_0 + k_45) * f_1 * f_2 + // +- (k_43 * t_0^2 + k_44 * t_0 + k_45) * f_2 * sqrt(f_1^2 + 4 * f_0 * f_2) + // - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + // 0 = +- ((k_43 * t_0^2 + k_44 * t_0 + k_45) * f_2 - (k_41 * t_0 + k_42) * f_1) * sqrt(f_1^2 + 4 * f_0 * f_2) + // + (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) + // - (k_43 * t_0^2 + k_44 * t_0 + k_45) * f_1 * f_2 + // - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * a_2 * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + // 0 = +- ((k_43 * t_0^2 + k_44 * t_0 + k_45) * (k_14 * t_0 + k_15) - (k_41 * t_0 + k_42) * (k_16 * t_0^2 + k_17 * t_0 + k_18)) * sqrt(f_1^2 + 4 * f_0 * f_2) + // + (k_41 * t_0 + k_42) * (k_37 * t_0^4 + k_38 * t_0^3 + k_39 * t_0^2 + k_40 * t_0 + k_59) + // - (k_43 * t_0^2 + k_44 * t_0 + k_45) * (k_16 * t_0^2 + k_17 * t_0 + k_18) * (k_14 * t_0 + k_15) + // - 2 * t_0 * (k_23 * t_0^2 + k_24 * t_0 + k_25) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + 2 * (k_13 * t_0^2 - k_26 * t_0) * (V_00 * t_0 + k_1) * (k_14^2 * t_0^2 + k_31 * t_0 + k_15^2) + // 0 = +- ( + // (k_43 * k_14 - k_41 * k_16) * t_0^3 + // + (k_43 * k_15 + k_44 * k_14 - k_42 * k_16 - k_41 * k_17) * t_0^2 + // + (k_44 * k_15 + k_45 * k_14 - k_42 * k_17 - k_41 * k_18) * t_0 + // + k_45 * k_15 - k_42 * k_18 + // ) * sqrt(f_1^2 + 4 * f_0 * f_2) + // + (k_41 * k_37 - k_43 * k_16 * k_14) * t_0^5 + // + (k_41 * k_38 + k_42 * k_37 - k_43 * k_17 * k_14 - k_44 * k_16 * k_14 - k_43 * k_16 * k_15) * t_0^4 + // + (k_41 * k_39 + k_42 * k_38 - k_43 * k_18 * k_14 - k_44 * k_17 * k_14 - k_45 * k_16 * k_14 - k_43 * k_17 * k_15 - k_44 * k_16 * k_15) * t_0^3 + // + (k_41 * k_40 + k_42 * k_39 - k_44 * k_18 * k_14 - k_45 * k_17 * k_14 - k_43 * k_18 * k_15 - k_44 * k_17 * k_15 - k_45 * k_16 * k_15) * t_0^2 + // + (k_41 * k_59 + k_42 * k_40 - k_45 * k_18 * k_14 - k_44 * k_18 * k_15 - k_45 * k_17 * k_15) * t_0 + // + k_42 * k_59 - k_45 * k_18 * k_15 + // + 2 * (k_13 * V_00 * k_14^2 - k_23 * k_14^2) * t_0^5 + // + 2 * (k_13 * V_00 * k_31 + k_13 * k_1 * k_14^2 - k_26 * V_00 * k_14^2 - k_23 * k_31 - k_24 * k_14^2) * t_0^4 + // + 2 * (k_13 * V_00 * k_15^2 + k_13 * k_1 * k_31 - k_26 * V_00 * k_31 - k_26 * k_1 * k_14^2 - k_23 * k_15^2 - k_24 * k_31 - k_25 * k_14^2) * t_0^3 + // + 2 * (k_13 * k_1 * k_15^2 - k_26 * V_00 * k_15^2 - k_26 * k_1 * k_31 - k_24 * k_15^2 - k_25 * k_31) * t_0^2 + // + 2 * (- k_26 * k_1 * k_15^2 - k_25 * k_15^2) * t_0 + // 0 = +- (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49) * sqrt(f_1^2 + 4 * f_0 * f_2) + // + (k_51 + k_63) * t_0^5 + (k_53 + k_65) * t_0^4 + (k_55 + k_67) * t_0^3 + (k_57 + k_68) * t_0^2 + (k_60 + k_69) * t_0 + k_61 + // 0 = +- (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49) * sqrt(k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) + // + k_70 * t_0^5 + k_71 * t_0^4 + k_72 * t_0^3 + k_73 * t_0^2 + k_74 * t_0 + k_61 + + OPolynomial0 := TBigIntPolynomial.Create([k[61], k[74], k[73], k[72], k[71], k[70]]); + OPolynomial1 := TBigIntPolynomial.Create([k[49], k[48], k[47], k[46]]); + + // Squaring that formula eliminates the square root, but may lead to a polynomial with all coefficients zero in some + // cases. Therefore this part is merely included for the interested reader. + // -+ (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49) * sqrt(k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) = + // k_70 * t_0^5 + k_71 * t_0^4 + k_72 * t_0^3 + k_73 * t_0^2 + k_74 * t_0 + k_61 + // (k_46 * t_0^3 + k_47 * t_0^2 + k_48 * t_0 + k_49)^2 * (k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) = + // (k_70 * t_0^5 + k_71 * t_0^4 + k_72 * t_0^3 + k_73 * t_0^2 + k_74 * t_0 + k_61)^2 + // 0 = + // (k_46^2 * t_0^6 + // + 2 * k_46 * k_47 * t_0^5 + // + k_47^2 * t_0^4 + 2 * k_46 * k_48 * t_0^4 + // + 2 * k_46 * k_49 * t_0^3 + 2 * k_47 * k_48 * t_0^3 + // + k_48^2 * t_0^2 + 2 * k_47 * k_49 * t_0^2 + // + 2 * k_48 * k_49 * t_0 + // + k_49^2 + // ) * (k_37 * t_0^4 + k_75 * t_0^3 + k_76 * t_0^2 + k_77 * t_0 + k_59) + // - k_70^2 * t_0^10 + // - 2 * k_70 * k_71 * t_0^9 + // - (k_71^2 + 2 * k_70 * k_72) * t_0^8 + // - 2 * (k_70 * k_73 + k_71 * k_72) * t_0^7 + // - (k_72^2 + 2 * k_70 * k_74 + 2 * k_71 * k_73) * t_0^6 + // - 2 * (k_70 * k_61 + k_71 * k_74 + k_72 * k_73) * t_0^5 + // - (k_73^2 + 2 * k_71 * k_61 + 2 * k_72 * k_74) * t_0^4 + // - 2 * (k_72 * k_61 + k_73 * k_74) * t_0^3 + // - (k_74^2 + 2 * k_73 * k_61) * t_0^2 + // - 2 * k_74 * k_61 * t_0 + // - k_61^2 + // 0 = ak_10 * t_0^10 + ak_9 * t_0^9 + ak_8 * t_0^8 + ak_7 * t_0^7 + ak_6 * t_0^6 + ak_5 * t_0^5 + ak_4 * t_0^4 + ak_3 * t_0^3 + ak_2 * t_0^2 + ak_1 * t_0 + ak_0 + + //k[78] := k[46] * k[46]; + //k[79] := 2 * k[46] * k[47]; + //k[80] := k[47] * k[47] + 2 * k[46] * k[48]; + //k[81] := 2 * (k[46] * k[49] + k[47] * k[48]); + //k[82] := k[48] * k[48] + 2 * k[47] * k[49]; + //k[83] := 2 * k[48] * k[49]; + //k[84] := k[49] * k[49]; + //ak[0] := k[59] * k[84] - k[61] * k[61]; + //ak[1] := k[77] * k[84] + k[59] * k[83] - 2 * k[74] * k[61]; + //ak[2] := k[76] * k[84] + k[77] * k[83] + k[59] * k[82] - k[74] * k[74] - 2 * k[73] * k[61]; + //ak[3] := k[76] * k[83] + k[77] * k[82] + k[59] * k[81] + k[75] * k[84] + // - 2 * (k[72] * k[61] + k[73] * k[74]); + //ak[4] := k[37] * k[84] + k[76] * k[82] + k[77] * k[81] + k[59] * k[80] + k[75] * k[83] - k[73] * k[73] + // - 2 * (k[71] * k[61] + k[72] * k[74]); + //ak[5] := k[37] * k[83] + k[76] * k[81] + k[77] * k[80] + k[59] * k[79] + k[75] * k[82] + // - 2 * (k[70] * k[61] + k[71] * k[74] + k[72] * k[73]); + //ak[6] := k[37] * k[82] + k[76] * k[80] + k[77] * k[79] + k[59] * k[78] + k[75] * k[81] - k[72] * k[72] + // - 2 * (k[70] * k[74] + k[71] * k[73]); + //ak[7] := k[37] * k[81] + k[76] * k[79] + k[77] * k[78] + k[75] * k[80] - 2 * (k[70] * k[73] + k[71] * k[72]); + //ak[8] := k[37] * k[80] + k[75] * k[79] + k[76] * k[78] - k[71] * k[71] - 2 * k[70] * k[72]; + //ak[9] := k[37] * k[79] + k[75] * k[78] - 2 * k[70] * k[71]; + //ak[10] := k[37] * k[78] - k[70] * k[70]; +end; + +function TNeverTellMeTheOdds.CalcRockThrowCollisionOptions(constref AHailstone0, AHailstone1, AHailstone2: THailstone): + TInt64Array; +var + a0, a1: TBigIntPolynomial; + a0Roots, a1Roots: TBigIntArray; + options: specialize TList; + i, j: TBigInt; + val: Int64; +begin + CalcCollisionPolynomials(AHailstone0, AHailstone1, AHailstone2, a0, a1); + a0Roots := TPolynomialRoots.BisectInteger(a0, 64); + a1Roots := TPolynomialRoots.BisectInteger(a1, 64); + + options := specialize TList.Create; + for i in a0Roots do + for j in a1Roots do + if (i = j) and i.TryToInt64(val) then + options.Add(val); + Result := options.ToArray; + options.Free; +end; + +function TNeverTellMeTheOdds.ValidateRockThrow(constref AHailstone0, AHailstone1, AHailstone2: THailstone; const AT0, + AT1: Int64): Int64; +var + divisor, t: Int64; + rock: THailstone; +begin + // V_x = (V_0 * t_0 - V_1 * t_1 + P_0 - P_1) / (t_0 - t_1) + divisor := AT0 - AT1; + rock := THailstone.Create; + rock.V0 := (AHailstone0.V0 * AT0 - AHailstone1.V0 * AT1 + AHailstone0.P0 - AHailstone1.P0) div divisor; + rock.V1 := (AHailstone0.V1 * AT0 - AHailstone1.V1 * AT1 + AHailstone0.P1 - AHailstone1.P1) div divisor; + rock.V2 := (AHailstone0.V2 * AT0 - AHailstone1.V2 * AT1 + AHailstone0.P2 - AHailstone1.P2) div divisor; + + // P_x = (V_0 - V_x) * t_0 + P_0 + rock.P0 := (AHailstone0.V0 - rock.V0) * AT0 + AHailstone0.P0; + rock.P1 := (AHailstone0.V1 - rock.V1) * AT0 + AHailstone0.P1; + rock.P2 := (AHailstone0.V2 - rock.V2) * AT0 + AHailstone0.P2; + + Result := rock.P0 + rock.P1 + rock.P2; + + // Checks collision with the third hailstone. + if ((AHailstone2.V0 = rock.V0) and (AHailstone2.P0 <> rock.P0)) + or ((AHailstone2.V1 = rock.V1) and (AHailstone2.P1 <> rock.P1)) + or ((AHailstone2.V2 = rock.V2) and (AHailstone2.P2 <> rock.P2)) then + Result := 0 + else begin + t := (AHailstone2.P0 - rock.P0) div (rock.V0 - AHailstone2.V0); + if (t <> (AHailstone2.P1 - rock.P1) div (rock.V1 - AHailstone2.V1)) + or (t <> (AHailstone2.P2 - rock.P2) div (rock.V2 - AHailstone2.V2)) then + Result := 0; + end; + + rock.Free; +end; + constructor TNeverTellMeTheOdds.Create(const AMin: Int64; const AMax: Int64); begin FMin := AMin; FMax := AMax; - FHailStones := THailstones.Create; + FHailstones := THailstones.Create; end; destructor TNeverTellMeTheOdds.Destroy; begin - FHailStones.Free; + FHailstones.Free; inherited Destroy; end; procedure TNeverTellMeTheOdds.ProcessDataLine(const ALine: string); -var - split: TStringArray; - hailstone: THailstone; begin - split := ALine.Split([',', '@']); - hailstone.X := StrToInt64(Trim(split[0])); - hailstone.Y := StrToInt64(Trim(split[1])); - hailstone.Z := StrToInt64(Trim(split[2])); - hailstone.VX := StrToInt(Trim(split[3])); - hailstone.VY := StrToInt(Trim(split[4])); - hailstone.VZ := StrToInt(Trim(split[5])); - FHailStones.Add(hailstone); + FHailstones.Add(THailstone.Create(ALine)); end; procedure TNeverTellMeTheOdds.Finish; var i, j: Integer; begin - for i := 0 to FHailStones.Count - 2 do - for j := i + 1 to FHailStones.Count - 1 do - if AreIntersecting(FHailStones[i], FHailStones[j]) then + for i := 0 to FHailstones.Count - 2 do + for j := i + 1 to FHailstones.Count - 1 do + if AreIntersecting(FHailstones[i], FHailstones[j]) then Inc(FPart1); + + if FHailstones.Count >= 3 then + FPart2 := FindRockThrow(0, 1, 2); end; function TNeverTellMeTheOdds.GetDataFileName: string; diff --git a/tests/AdventOfCodeFPCUnit.lpi b/tests/AdventOfCodeFPCUnit.lpi index 0c232ac..e34e993 100644 --- a/tests/AdventOfCodeFPCUnit.lpi +++ b/tests/AdventOfCodeFPCUnit.lpi @@ -40,10 +40,6 @@ - - - - @@ -140,6 +136,18 @@ + + + + + + + + + + + + diff --git a/tests/AdventOfCodeFPCUnit.lpr b/tests/AdventOfCodeFPCUnit.lpr index 1da4c13..27320a1 100644 --- a/tests/AdventOfCodeFPCUnit.lpr +++ b/tests/AdventOfCodeFPCUnit.lpr @@ -9,7 +9,7 @@ uses UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases, UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases, UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases, - UNeverTellMeTheOddsTestCases; + UNeverTellMeTheOddsTestCases, UBigIntTestCases, UPolynomialTestCases, UPolynomialRootsTestCases; {$R *.res} diff --git a/tests/UBigIntTestCases.pas b/tests/UBigIntTestCases.pas new file mode 100644 index 0000000..9639ac6 --- /dev/null +++ b/tests/UBigIntTestCases.pas @@ -0,0 +1,1033 @@ +{ + 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 UBigIntTestCases; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, UBigInt; + +type + // TODO: TBigIntAbsTestCase + // TODO: TBigIntFromDecTestCase + // TestCase('80091110002223289436355210101231765016693209176113648', 10, '80091110002223289436355210101231765016693209176113648'); + // TestCase('-3201281944789146858325672846129872035678639439423746384198412894', 10, '-3201281944789146858325672846129872035678639439423746384198412894'); + // TestCase('000000000000000000000000000000000000000080091', 10, '80091'); + // TestCase('0000000000000000000000000000000000000000000000', 10, '0'); + // TestCase('-000000000000000000000000000000004687616873215464763146843215486643215', 10, '-4687616873215464763146843215486643215'); + // TestTryFromDec + // TODO: TBigIntFromOctTestCase + // OctValue, 8, DecValue: + // TestCase('3132521', 8, '832849'); + // TestCase('772350021110002226514', 8, '9123449598017875276'); + // TestCase('-247515726712721', 8, '-11520970560977'); + // TestCase('000000000000000000000000000000000000000072', 8, '58'); + // TestCase('000000000000000000000000000000000000000000000000000000000', 8, '0'); + // OctValue, HexValue: + // TestCase('7440737076307076026772', '3C83BE3E638F82DFA'); + // TestCase('1336625076203401614325664', '16F6547C8380E31ABB4'); + // TestCase('3254410316166274457257121050201413373225', '6AC84338ECBC97ABCA22840C2DF695'); + // TestCase('1441330072562106272767270117307274151276215', '642D81D5C88CBAFBAE09EC75E1A57C8D'); + // TestCase('3533665174054677131163436413756431236774257271133', '3ADED4F82CDF964E71E85FBA329EFE2BD725B'); + // TestCase('57346415317074055631627141161054073014006076155710653', '5EE686B3C782DCCE5CC271160EC18061F1B791AB'); + // TestCase('000000000000000000000000000000245745251354033134', 8, '5838773085550172'); + // TestTryFromOct + // TODO: TBigIntUnaryPlusTestCase + // TODO: TBigIntBitwiseLogicalTestCase + // TODO: TBigIntComplementTestCase + // TODO: TBigIntConversionTestCase + // TODO: TBigIntIncrementDecrementTestCase + // TODO: TBigIntQuotientTestCase + + { TBigIntSignTestCase } + + TBigIntSignTestCase = class(TTestCase) + private + procedure Test(const AHexValue: string; const AExpectedSign: Integer); + published + procedure TestZero; + procedure TestShortPositive; + procedure TestShortNegative; + procedure TestLongPositive; + procedure TestLongNegative; + end; + + { TBigIntMostSignificantBitIndexTestCase } + + TBigIntMostSignificantBitIndexTestCase = class(TTestCase) + private + procedure Test(const ABinValue: string; const AExpectedIndex: Int64); + published + procedure TestZero; + procedure TestShort; + procedure TestLong; + end; + + { TBigIntFromInt64TestCase } + + TBigIntFromInt64TestCase = class(TTestCase) + private + procedure Test(const AValue: Int64); + published + procedure TestShortPositive; + procedure TestShortNegative; + procedure TestLongPositive; + procedure TestLongNegative; + procedure TestZero; + end; + + { TBigIntFromHexTestCase } + + TBigIntFromHexTestCase = class(TTestCase) + private + procedure TestShort(const AHexValue: string; const ADecValue: Int64); + published + procedure TestPositive; + procedure TestNegative; + procedure TestZero; + procedure TestLeadingZeros; + // TODO: TestTryFromHex + end; + + { TBigIntFromBinTestCase } + + TBigIntFromBinTestCase = class(TTestCase) + private + procedure TestShort(const ABinValue: string; const ADecValue: Int64); + published + procedure TestPositive; + procedure TestNegative; + procedure TestLeadingZeros; + // TODO: TestTryFromBin + end; + + { TBigIntUnaryMinusTestCase } + + TBigIntUnaryMinusTestCase = class(TTestCase) + private + procedure Test(const AValue: Int64); + procedure TestTwice(const AValue: Int64); + published + procedure TestZero; + procedure TestPositive; + procedure TestNegative; + procedure TestPositiveTwice; + procedure TestNegativeTwice; + end; + + { TBigIntSumTestCase } + + TBigIntSumTestCase = class(TTestCase) + private + procedure Test(const AHexValueLeft, AHexValueRight, AHexValueSum: string); + published + procedure TestShort; + procedure TestPositivePlusPositive; + procedure TestNegativePlusNegative; + procedure TestLargePositivePlusSmallNegative; + procedure TestSmallPositivePlusLargeNegative; + procedure TestLargeNegativePlusSmallPositive; + procedure TestSmallNegativePlusLargePositive; + procedure TestZeroPlusPositive; + procedure TestPositivePlusZero; + procedure TestZero; + procedure TestSumShorterLeft; + procedure TestSumShorterRight; + procedure TestSumEndsInCarry; + procedure TestSumEndsInCarryNewDigit; + procedure TestSumMultiCarry; + end; + + { TBigIntDifferenceTestCase } + + TBigIntDifferenceTestCase = class(TTestCase) + private + procedure Test(const AHexValueMinuend, AHexValueSubtrahend, AHexValueDifference: string); + published + procedure TestShort; + procedure TestLargePositiveMinusSmallPositive; + procedure TestSmallPositiveMinusLargePositive; + procedure TestLargeNegativeMinusSmallNegative; + procedure TestSmallNegativeMinusLargeNegative; + procedure TestNegativeMinusPositive; + procedure TestPositiveMinusNegative; + procedure TestLargeOperands; + procedure TestPositiveMinusZero; + procedure TestZeroMinusPositive; + procedure TestZero; + procedure TestDifferenceShorterLeft; + procedure TestDifferenceShorterRight; + procedure TestDifferenceEndsInCarry; + procedure TestDifferenceEndsInCarryLosingDigit; + procedure TestDifferenceMultiCarry; + end; + + { TBigIntProductTestCase } + + TBigIntProductTestCase = class(TTestCase) + private + procedure Test(const AHexValueLeft, AHexValueRight, AHexValueProduct: string); + published + procedure TestShort; + procedure TestLongPositiveTimesPositive; + procedure TestLongPositiveTimesNegative; + procedure TestLongNegativeTimesPositive; + procedure TestLongNegativeTimesNegative; + procedure TestZeroTimesPositive; + procedure TestZeroTimesNegative; + procedure TestZero; + end; + + { TBigIntShiftLeftTestCase } + + TBigIntShiftLeftTestCase = class(TTestCase) + private + procedure Test(const AHexValueOperand: string; const AShift: Integer; const AHexValueResult: string); + published + procedure TestShort; + procedure TestShortWithCarry; + procedure TestLongWithCarry; + procedure TestLongWithMultiDigitCarry; + procedure TestWithAlignedDigits; + procedure TestZero; + end; + + { TBigIntShiftRightTestCase } + + TBigIntShiftRightTestCase = class(TTestCase) + private + procedure Test(const AHexValueOperand: string; const AShift: Integer; const AHexValueResult: string); + published + procedure TestShort; + procedure TestShortWithCarry; + procedure TestLongWithCarry; + procedure TestLongWithMultiDigitCarry; + procedure TestWithAlignedDigits; + procedure TestShiftToZero; + procedure TestZero; + end; + + { TBigIntEqualityTestCase } + + TBigIntEqualityTestCase = class(TTestCase) + private + procedure TestEqual(const AValue: Int64); + procedure TestEqualHex(const AHexValue: string); + procedure TestNotEqualHex(const AHexValueLeft, AHexValueRight: string); + published + procedure TestShortEqual; + procedure TestLongEqual; + procedure TestZeroEqual; + procedure TestShortNotEqual; + procedure TestLongNotEqualSign; + procedure TestZeroNotEqual; + end; + + { TBigIntComparisonTestCase } + + TBigIntComparisonTestCase = class(TTestCase) + private + procedure TestLessThan(const AHexValueLeft, AHexValueRight: string); + procedure TestGreaterThan(const AHexValueLeft, AHexValueRight: string); + procedure TestEqual(const AHexValue: string); + published + procedure TestLessSameLength; + procedure TestLessShorterLeft; + procedure TestLessNegativeShorterRight; + procedure TestGreaterSameLength; + procedure TestGreaterShorterRight; + procedure TestGreaterNegativeShorterLeft; + procedure TestEqualPositive; + procedure TestEqualNegative; + procedure TestEqualZero; + end; + +implementation + +{ TBigIntSignTestCase } + +procedure TBigIntSignTestCase.Test(const AHexValue: string; const AExpectedSign: Integer); +var + a: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValue); + AssertEquals(AExpectedSign, a.Sign); +end; + +procedure TBigIntSignTestCase.TestZero; +begin + Test('0', 0); +end; + +procedure TBigIntSignTestCase.TestShortPositive; +begin + Test('36A', 1); +end; + +procedure TBigIntSignTestCase.TestShortNegative; +begin + Test('-29B7', -1); +end; + +procedure TBigIntSignTestCase.TestLongPositive; +begin + Test('1240168AB09ABDF8283B77124', 1); +end; + +procedure TBigIntSignTestCase.TestLongNegative; +begin + Test('-192648F1305DD04757A24C873F29F60B6184B5', -1); +end; + +{ TBigIntMostSignificantBitIndexTestCase } + +procedure TBigIntMostSignificantBitIndexTestCase.Test(const ABinValue: string; const AExpectedIndex: Int64); +var + a: TBigInt; +begin + a := TBigInt.FromBinaryString(ABinValue); + AssertEquals('BigInt from binary string ''' + ABinValue + ''' did not have its most significant bit at index ''' + + IntToStr(AExpectedIndex) + '''.', + AExpectedIndex, a.GetMostSignificantBitIndex); +end; + +procedure TBigIntMostSignificantBitIndexTestCase.TestZero; +begin + Test('0', -1); +end; + +procedure TBigIntMostSignificantBitIndexTestCase.TestShort; +begin + Test('1', 0); + Test('11111101111001', 13); + Test('10010111101100001110110101111000', 31); +end; + +procedure TBigIntMostSignificantBitIndexTestCase.TestLong; +begin + Test('111100010101010100011101010100011', 32); + Test('11101001101010111101000101010001010101010101111111001010101010010100101000101011111001000111001001100011', 103); + Test('111101100011110110111011010111100000000001010111101110101101101100101010110111101011010101001100', 95); +end; + +{ TBigIntFromInt64TestCase } + +procedure TBigIntFromInt64TestCase.Test(const AValue: Int64); +var + a: TBigInt; + act: Int64; +begin + a := TBigInt.FromInt64(AValue); + AssertTrue('BigInt from ''' + IntToStr(AValue) + ''' could not be converted back to an Int64 value.', + a.TryToInt64(act)); + AssertEquals('BigInt from ''' + IntToStr(AValue) + ''' converted back to Int64 was not equal to initial value.', + AValue, act); +end; + +procedure TBigIntFromInt64TestCase.TestShortPositive; +begin + Test(17); + Test(4864321); + Test(Cardinal.MaxValue); +end; + +procedure TBigIntFromInt64TestCase.TestShortNegative; +begin + Test(-54876); + Test(Integer.MinValue); +end; + +procedure TBigIntFromInt64TestCase.TestLongPositive; +begin + Test(5800754643214654); + Test(Int64.MaxValue); +end; + +procedure TBigIntFromInt64TestCase.TestLongNegative; +begin + Test(-94136445555883); + Test(Int64.MinValue); +end; + +procedure TBigIntFromInt64TestCase.TestZero; +begin + Test(0); +end; + +{ TBigIntFromHexTestCase } + +procedure TBigIntFromHexTestCase.TestShort(const AHexValue: string; const ADecValue: Int64); +var + a, b: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValue); + b := TBigInt.FromInt64(ADecValue); + AssertTrue('BigInt from hexadecimal string ''' + AHexValue + ''' was not equal to ''' + IntToStr(ADecValue) + '''.', + a = b); +end; + +procedure TBigIntFromHexTestCase.TestPositive; +begin + TestShort('91', 145); + TestShort('800E111000222', 2252766466540066); + TestShort('DE0B227802AB233', 999995000000000563); + TestShort('19C0048155000', 453000000000000); + TestShort('3B9ACA00', 1000000000); +end; + +procedure TBigIntFromHexTestCase.TestNegative; +begin + TestShort('-47', -71); + TestShort('-800E111000222', -2252766466540066); + TestShort('-4512FF3', -72429555); +end; + +procedure TBigIntFromHexTestCase.TestZero; +begin + TestShort('0', 0); + TestShort('-0', 0); +end; + +procedure TBigIntFromHexTestCase.TestLeadingZeros; +begin + TestShort('000000000000000000000000000000004B', 75); + TestShort('-00000000000000000000000000000000A2', -162); + TestShort('00000000000000000000000000000000FF452849DB01', 280672493755137); + TestShort('000000000000000000000000000000000000', 0); + TestShort('-000000000000000000000000000000000000', 0); +end; + +{ TBigIntFromBinTestCase } + +procedure TBigIntFromBinTestCase.TestShort(const ABinValue: string; const ADecValue: Int64); +var + a, b: TBigInt; +begin + a := TBigInt.FromBinaryString(ABinValue); + b := TBigInt.FromInt64(ADecValue); + AssertTrue('BigInt from binary string ''' + ABinValue + ''' was not equal to ''' + IntToStr(ADecValue) + '''.', + a = b); +end; + +procedure TBigIntFromBinTestCase.TestPositive; +begin + TestShort('110101010101101101010010110000101010100101001001010100110', 120109162101379750); +end; + +procedure TBigIntFromBinTestCase.TestNegative; +begin + TestShort('-11100100111010100111000111100011110000100110110111100101010010', -4123780452057839954); +end; + +procedure TBigIntFromBinTestCase.TestLeadingZeros; +begin + TestShort('0000000000000000000000000000000000000000000000000000000000000000000000111', 7); + TestShort('0000000000000000000000000000000000000000000000000000000000000000000000000000000', 0); +end; + +{ TBigIntUnaryMinusTestCase } + +procedure TBigIntUnaryMinusTestCase.Test(const AValue: Int64); +var + a, b: TBigInt; +begin + a := TBigInt.FromInt64(AValue); + b := TBigInt.FromInt64(-AValue); + AssertTrue('Negative BigInt from ''' + IntToStr(AValue) + ''' was not equal to the BigInt from the negative value.', + b = -a); +end; + +procedure TBigIntUnaryMinusTestCase.TestTwice(const AValue: Int64); +var + a: TBigInt; +begin + a := TBigInt.FromInt64(AValue); + AssertTrue('BigInt from ''' + IntToStr(AValue) + '''was not equal to the double negative of itself.', + a = -(-a)); +end; + +procedure TBigIntUnaryMinusTestCase.TestZero; +begin + Test(0); +end; + +procedure TBigIntUnaryMinusTestCase.TestPositive; +begin + Test(234972358233); +end; + +procedure TBigIntUnaryMinusTestCase.TestNegative; +begin + Test(-999214927400); +end; + +procedure TBigIntUnaryMinusTestCase.TestPositiveTwice; +begin + TestTwice(8647613456601); +end; + +procedure TBigIntUnaryMinusTestCase.TestNegativeTwice; +begin + TestTwice(-38600421308534); +end; + +{ TBigIntSumTestCase } + +procedure TBigIntSumTestCase.Test(const AHexValueLeft, AHexValueRight, AHexValueSum: string); +var + a, b, s: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueLeft); + b := TBigInt.FromHexadecimalString(AHexValueRight); + s := TBigInt.FromHexadecimalString(AHexValueSum); + AssertTrue('Sum of BigInt from ''' + AHexValueLeft + ''' and from ''' + AHexValueRight + + ''' was not equal to the BigInt from ''' + AHexValueSum + '''.', + s = a + b); +end; + +procedure TBigIntSumTestCase.TestShort; +begin + Test('5', '7', 'C'); +end; + +procedure TBigIntSumTestCase.TestPositivePlusPositive; +begin + Test('7000822000111', '9000911000333', '10001133000444'); +end; + +procedure TBigIntSumTestCase.TestNegativePlusNegative; +begin + Test('-129B92A84643D608141', '-4574DBA206951ECFE', '-12E10783E84A6B26E3F'); +end; + +procedure TBigIntSumTestCase.TestLargePositivePlusSmallNegative; +begin + Test('87BAD26984', '-8DCB20461', '7EDE206523'); +end; + +procedure TBigIntSumTestCase.TestSmallPositivePlusLargeNegative; +begin + Test('A58301E4006', '-9851DA0FD433', '-8DF9A9F1942D'); +end; + +procedure TBigIntSumTestCase.TestLargeNegativePlusSmallPositive; +begin + Test('-1FDB60CB5698870', '99CB1E00DE', '-1FDB572EA4B8792'); +end; + +procedure TBigIntSumTestCase.TestSmallNegativePlusLargePositive; +begin + Test('-1ED598BBFEC2', '59CD4F02ECB56', '57DFF5772CC94'); +end; + +procedure TBigIntSumTestCase.TestZeroPlusPositive; +begin + Test('0', '9BB000911FF5A000333', '9BB000911FF5A000333'); +end; + +procedure TBigIntSumTestCase.TestPositivePlusZero; +begin + Test('23009605A16BCBB294A1FD', '0', '23009605A16BCBB294A1FD'); +end; + +procedure TBigIntSumTestCase.TestZero; +begin + Test('0', '0', '0'); +end; + +procedure TBigIntSumTestCase.TestSumShorterLeft; +begin + Test('3FFFF', '9000911000222', '9000911040221'); +end; + +procedure TBigIntSumTestCase.TestSumShorterRight; +begin + Test('9000911000555', '3000EEEE', '900094100F443'); +end; + +procedure TBigIntSumTestCase.TestSumEndsInCarry; +begin + Test('4000444000220', 'FFFFFFFF', '400054400021F'); +end; + +procedure TBigIntSumTestCase.TestSumEndsInCarryNewDigit; +begin + Test('99990000', '99990055', '133320055'); +end; + +procedure TBigIntSumTestCase.TestSumMultiCarry; +begin + Test('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'FFFFFFFF', '1000000000000000000000000FFFFFFFE'); +end; + +{ TBigIntDifferenceTestCase } + +procedure TBigIntDifferenceTestCase.Test(const AHexValueMinuend, AHexValueSubtrahend, AHexValueDifference: string); +var + a, b, s: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueMinuend); + b := TBigInt.FromHexadecimalString(AHexValueSubtrahend); + s := TBigInt.FromHexadecimalString(AHexValueDifference); + AssertTrue('Difference of BigInt from ''' + AHexValueMinuend + ''' and from ''' + AHexValueSubtrahend + + ''' was not equal to the BigInt from ''' + AHexValueDifference + '''.', + s = a - b); +end; + +procedure TBigIntDifferenceTestCase.TestShort; +begin + Test('230', '6D', '1C3'); +end; + +procedure TBigIntDifferenceTestCase.TestLargePositiveMinusSmallPositive; +begin + Test('910AAD86E5002455', '72DE020F932A1', '91037FA6C406F1B4'); +end; + +procedure TBigIntDifferenceTestCase.TestSmallPositiveMinusLargePositive; +begin + Test('3127541F4C0AA', '3818CD9FBE652B', '-3506585DC9A481'); +end; + +procedure TBigIntDifferenceTestCase.TestLargeNegativeMinusSmallNegative; +begin + Test('-B12BE1E20098', '-148F3137CED396', '13DE0555ECD2FE'); +end; + +procedure TBigIntDifferenceTestCase.TestSmallNegativeMinusLargeNegative; +begin + Test('-AF3FF1EC626908C', '-18295', '-AF3FF1EC6250DF7'); +end; + +procedure TBigIntDifferenceTestCase.TestNegativeMinusPositive; +begin + Test('-E493506B19', '20508ED255', '-104E3DF3D6E'); +end; + +procedure TBigIntDifferenceTestCase.TestPositiveMinusNegative; +begin + Test('114EEC66851AFD98', '-100AA4308C5249FBBFADEB89CD6A7D9CC', '100AA4308C5249FBC0C2DA5035BC2D764'); +end; + +procedure TBigIntDifferenceTestCase.TestLargeOperands; +begin + Test('1069FC8EA3D99C39E1C07AA078B77B5CC679CB448563345A878C603D32FB0F8FEFE02AD9574A5EB6', + '1069FC8EA3D99C39E1C07AA078B77B5CC679CB448563345A878C603D32FB0F8FEFE023C522B87F8C', + '7143491DF2A'); +end; + +procedure TBigIntDifferenceTestCase.TestPositiveMinusZero; +begin + Test('8ABB000911FF5A000333', '0', '8ABB000911FF5A000333'); +end; + +procedure TBigIntDifferenceTestCase.TestZeroMinusPositive; +begin + Test('0', '243961982DDD64F81B2', '-243961982DDD64F81B2'); +end; + +procedure TBigIntDifferenceTestCase.TestZero; +begin + Test('0', '0', '0'); +end; + +procedure TBigIntDifferenceTestCase.TestDifferenceShorterLeft; +begin + Test('3FFFF', '9000911040221', '-9000911000222'); +end; + +procedure TBigIntDifferenceTestCase.TestDifferenceShorterRight; +begin + Test('900094100F443', '3000EEEE', '9000911000555'); +end; + +procedure TBigIntDifferenceTestCase.TestDifferenceEndsInCarry; +begin + Test('400054400021F', 'FFFFFFFF', '4000444000220'); +end; + +procedure TBigIntDifferenceTestCase.TestDifferenceEndsInCarryLosingDigit; +begin + Test('133320055', '99990000', '99990055'); +end; + +procedure TBigIntDifferenceTestCase.TestDifferenceMultiCarry; +begin + Test('1000000000000000000000000FFFFFFFE', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'FFFFFFFF'); +end; + +{ TBigIntProductTestCase } + +procedure TBigIntProductTestCase.Test(const AHexValueLeft, AHexValueRight, AHexValueProduct: string); +var + a, b, s: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueLeft); + b := TBigInt.FromHexadecimalString(AHexValueRight); + s := TBigInt.FromHexadecimalString(AHexValueProduct); + AssertTrue('Product of BigInt from ''' + AHexValueLeft + ''' and from ''' + AHexValueRight + + ''' was not equal to the BigInt from ''' + AHexValueProduct + '''.', + s = a * b); +end; + +procedure TBigIntProductTestCase.TestShort; +begin + Test('9', 'B', '63'); + Test('29E531A', 'DF5F299', '248E3915103E8A'); +end; + +procedure TBigIntProductTestCase.TestLongPositiveTimesPositive; +begin + Test('FFFFFFFF', 'FFFFFFFF', 'FFFFFFFE00000001'); + Test('4873B23699D07741F1544862221B1966AA84512', '4DE0013', '160A322C656DEB1DD6D721D36E35F2E29B4D2A18192056'); + Test('74FD3E6988116762', '22DB271AFC4941', 'FEDC8CD51DEE46BE83C283B5E31E2'); +end; + +procedure TBigIntProductTestCase.TestLongPositiveTimesNegative; +begin + Test('23401834190D12FF3210F0B0129123AA', '-A4C0530234', '-16AF8B019CA1436BBFD1F1FB08494FFC9EF7E09288'); +end; + +procedure TBigIntProductTestCase.TestLongNegativeTimesPositive; +begin + Test('-3ACB78882923810', 'F490B8022A4BCBFF34E01', '-382B2B9851BC93CB0308B502C3B036D71810'); +end; + +procedure TBigIntProductTestCase.TestLongNegativeTimesNegative; +begin + Test('-554923FB201', '-9834FDC032', '32B514C1BA1E774EE8432'); +end; + +procedure TBigIntProductTestCase.TestZeroTimesPositive; +begin + Test('0', '1AF5D0039B888AC00F299', '0'); +end; + +procedure TBigIntProductTestCase.TestZeroTimesNegative; +begin + Test('0', '-1AF5D0039B888AC00F299', '0'); +end; + +procedure TBigIntProductTestCase.TestZero; +begin + Test('0', '0', '0'); +end; + +{ TBigIntShiftLeftTestCase } + +procedure TBigIntShiftLeftTestCase.Test(const AHexValueOperand: string; const AShift: Integer; const AHexValueResult: + string); +var + a, s: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueOperand); + s := TBigInt.FromHexadecimalString(AHexValueResult); + AssertTrue('BigInt from hexadecimal string ''' + AHexValueOperand + ''' shifted left by ''' + IntToStr(AShift) + + ''' was not equal to BigInt from hexadecimal string ''' + AHexValueResult + '''.', + s = (a << AShift)); +end; + +procedure TBigIntShiftLeftTestCase.TestShort; +begin + // BIN 101 + // BIN 101000 + Test('5', 3, '28'); +end; + +procedure TBigIntShiftLeftTestCase.TestShortWithCarry; +begin + // BIN 1110 1101 0111 0001 1010 1010 + // BIN 1 1101 1010 1110 0011 0101 0100 0000 0000 0000 + Test('ED71AA', 13, '1DAE354000'); +end; + +procedure TBigIntShiftLeftTestCase.TestLongWithCarry; +begin + // BIN 1 0011 0000 1011 0010 0011 1110 0100 1100 1111 1001 0101 0100 0100 0101 + // BIN 10 0110 0001 0110 0100 0111 1100 1001 1001 1111 0010 1010 1000 1000 1010 0000 0000 + Test('130B23E4CF95445', 9, '261647C99F2A88A00'); +end; + +procedure TBigIntShiftLeftTestCase.TestLongWithMultiDigitCarry; +begin + Test('C99F12A735A3B83901BF92011', 140, 'C99F12A735A3B83901BF9201100000000000000000000000000000000000'); +end; + +procedure TBigIntShiftLeftTestCase.TestWithAlignedDigits; +begin + // Shifts the left operand by a multiple of the length of full TBigInt digits, so the digits will be shifted, but not + // changed. + Test('10F0F39C5E', 32 * 4, '10F0F39C5E00000000000000000000000000000000'); +end; + +procedure TBigIntShiftLeftTestCase.TestZero; +begin + Test('0', 119, '0'); +end; + +{ TBigIntShiftRightTestCase } + +procedure TBigIntShiftRightTestCase.Test(const AHexValueOperand: string; const AShift: Integer; const AHexValueResult: + string); +var + a, s: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueOperand); + s := TBigInt.FromHexadecimalString(AHexValueResult); + AssertTrue('BigInt from hexadecimal string ''' + AHexValueOperand + ''' shifted right by ''' + IntToStr(AShift) + + ''' was not equal to BigInt from hexadecimal string ''' + AHexValueResult + '''.', + s = (a >> AShift)); +end; + +procedure TBigIntShiftRightTestCase.TestShort; +begin + // BIN 100110101 + // BIN 10011 + Test('135', 4, '13'); +end; + +procedure TBigIntShiftRightTestCase.TestShortWithCarry; +begin + // BIN 1 1101 1010 1110 1001 1000 0111 0000 0000 0000 1111 + // BIN 11 1011 0101 1101 0011 0000 1110 + Test('1DAE987000F', 15, '3B5D30E'); +end; + +procedure TBigIntShiftRightTestCase.TestLongWithCarry; +begin + // BIN 10 0110 0001 0110 0100 0111 1100 1001 1001 1111 0010 1010 1000 1000 1010 0010 0010 1101 1101 + // BIN 100 1100 0010 1100 1000 1111 1001 0011 0011 1110 0101 0101 0001 0001 0100 0100 + Test('261647C99F2A88A22DD', 11, '4C2C8F933E551144'); +end; + +procedure TBigIntShiftRightTestCase.TestLongWithMultiDigitCarry; +begin + Test('647C99F12A088A22FF6DD02187345A3B839401BFB9272', 104, '647C99F12A088A22FF6'); +end; + +// Shifts the left operand by a multiple of the length of full TBigInt digits, so the digits will be shifted, but not +// changed. +procedure TBigIntShiftRightTestCase.TestWithAlignedDigits; +begin + Test('C5E10F0F39000AA2000C020000010000000000000F00000007', 32 * 5, 'C5E10F0F39'); +end; + +procedure TBigIntShiftRightTestCase.TestShiftToZero; +begin + Test('B5D10F0F39882F', 150, '0'); +end; + +procedure TBigIntShiftRightTestCase.TestZero; +begin + Test('0', 3, '0'); +end; + +{ TBigIntEqualityTestCase } + +procedure TBigIntEqualityTestCase.TestEqual(const AValue: Int64); +var + a, b: TBigInt; +begin + a := TBigInt.FromInt64(AValue); + b := TBigInt.FromInt64(AValue); + AssertTrue('Two BigInt from ''' + IntToStr(AValue) + ''' were not equal.', a = b); + AssertFalse('Two BigInt from ''' + IntToStr(AValue) + ''' were un-equal.', a <> b); +end; + +procedure TBigIntEqualityTestCase.TestEqualHex(const AHexValue: string); +var + a, b: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValue); + b := TBigInt.FromHexadecimalString(AHexValue); + AssertTrue('Two BigInt from ''' + AHexValue + ''' were not equal.', a = b); + AssertFalse('Two BigInt from ''' + AHexValue + ''' were un-equal.', a <> b); +end; + +procedure TBigIntEqualityTestCase.TestNotEqualHex(const AHexValueLeft, AHexValueRight: string); +var + a, b: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueLeft); + b := TBigInt.FromHexadecimalString(AHexValueRight); + AssertTrue('BigInt from ''' + AHexValueLeft + ''' and from ''' + AHexValueRight + ''' were not un-equal.', + a <> b); + AssertFalse('BigInt from ''' + AHexValueLeft + ''' and from ''' + AHexValueRight + ''' were equal.', + a = b); +end; + +procedure TBigIntEqualityTestCase.TestShortEqual; +begin + TestEqual(14); + TestEqualHex('8000111000111'); +end; + +procedure TBigIntEqualityTestCase.TestLongEqual; +begin + TestEqualHex('5AB60FF292A014BF1DD0A'); +end; + +procedure TBigIntEqualityTestCase.TestZeroEqual; +begin + TestEqual(0); +end; + +procedure TBigIntEqualityTestCase.TestShortNotEqual; +begin + TestNotEqualHex('9FF99920', '100'); + TestNotEqualHex('20001110002111', '70001110007111'); +end; + +procedure TBigIntEqualityTestCase.TestLongNotEqualSign; +begin + TestNotEqualHex('48843AB320FF0041123A', '-48843AB320FF0041123A'); + TestNotEqualHex('-B13F79D842A30957DD09523667', 'B13F79D842A30957DD09523667'); +end; + +procedure TBigIntEqualityTestCase.TestZeroNotEqual; +begin + TestNotEqualHex('0', 'F'); + TestNotEqualHex('568F7', '0'); +end; + +{ TBigIntComparisonTestCase } + +procedure TBigIntComparisonTestCase.TestLessThan(const AHexValueLeft, AHexValueRight: string); +var + a, b: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueLeft); + b := TBigInt.FromHexadecimalString(AHexValueRight); + AssertTrue('BigInt from ''' + AHexValueLeft + ''' was greater than BigInt from ''' + AHexValueRight + '''.', + a < b); + AssertTrue('BigInt from ''' + AHexValueLeft + ''' was greater or equal than BigInt from ''' + AHexValueRight + '''.', + a <= b); + AssertFalse('BigInt from ''' + AHexValueLeft + ''' was greater than BigInt from ''' + AHexValueRight + '''.', + a > b); + AssertFalse('BigInt from ''' + AHexValueLeft + ''' was greater or equal than BigInt from ''' + AHexValueRight + '''.', + a >= b); +end; + +procedure TBigIntComparisonTestCase.TestGreaterThan(const AHexValueLeft, AHexValueRight: string); +var + a, b: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValueLeft); + b := TBigInt.FromHexadecimalString(AHexValueRight); + AssertFalse('BigInt from ''' + AHexValueLeft + ''' was less than BigInt from ''' + AHexValueRight + '''.', + a < b); + AssertFalse('BigInt from ''' + AHexValueLeft + ''' was less or equal than BigInt from ''' + AHexValueRight + '''.', + a <= b); + AssertTrue('BigInt from ''' + AHexValueLeft + ''' was less than BigInt from ''' + AHexValueRight + '''.', + a > b); + AssertTrue('BigInt from ''' + AHexValueLeft + ''' was less or equal than BigInt from ''' + AHexValueRight + '''.', + a >= b); +end; + +procedure TBigIntComparisonTestCase.TestEqual(const AHexValue: string); +var + a, b: TBigInt; +begin + a := TBigInt.FromHexadecimalString(AHexValue); + b := TBigInt.FromHexadecimalString(AHexValue); + AssertFalse('First BigInt from ''' + AHexValue + ''' was less than the second.', + a < b); + AssertTrue('First BigInt from ''' + AHexValue + ''' was greater than the second.', + a <= b); + AssertFalse('First BigInt from ''' + AHexValue + ''' was greater than the second.', + a > b); + AssertTrue('First BigInt from ''' + AHexValue + ''' was less than the second.', + a >= b); +end; + +procedure TBigIntComparisonTestCase.TestLessSameLength; +begin + TestLessThan('104234FF2B29100C012', '234867AB261F1003429103C'); + TestLessThan('-9812FB2964AC7632865238BBD3', '294625DF51B2A842582244C18163490'); + TestLessThan('-12834653A2856DF8', '-A946C2BF89401B8'); + TestLessThan('-2F846', '0'); + TestLessThan('0', 'FF67B'); +end; + +procedure TBigIntComparisonTestCase.TestLessShorterLeft; +begin + TestLessThan('34FF2B29100C012', '234867AB261F1003429103C'); + TestLessThan('0', 'BFF008112BA00012'); +end; + +procedure TBigIntComparisonTestCase.TestLessNegativeShorterRight; +begin + TestLessThan('-9B72844AC', '1F3486B'); + TestLessThan('-BB884F022111190', '0'); +end; + +procedure TBigIntComparisonTestCase.TestGreaterSameLength; +begin + TestGreaterThan('B042104234FF2B29100C012', '23867AB261F1003429103C'); + TestGreaterThan('1294B77', '-611F3B93'); + TestGreaterThan('-782163498326593562D548AAF715B4DC9143E42C68F39A29BB2', '-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); + TestGreaterThan('783BDA0', '0'); + TestGreaterThan('0', '-99B6D'); +end; + +procedure TBigIntComparisonTestCase.TestGreaterShorterRight; +begin + TestGreaterThan('102343234423FF67278B11ADD345F6BB21232C9129100C012', '234867AB261F1003429103C'); + TestGreaterThan('249D00F63BBAA8668B23', '0'); +end; + +procedure TBigIntComparisonTestCase.TestGreaterNegativeShorterLeft; +begin + TestGreaterThan('81F2A7B78B812', '-23490D7F19F247F91A365B1893BB701'); + TestGreaterThan('0', '-80F88242B34127'); +end; + +procedure TBigIntComparisonTestCase.TestEqualPositive; +begin + TestEqual('A44B80191059CA123318921A219BB'); +end; + +procedure TBigIntComparisonTestCase.TestEqualNegative; +begin + TestEqual('-28912798DD1246DAC9FB4269908012D496896812FF3A8D071B32'); +end; + +procedure TBigIntComparisonTestCase.TestEqualZero; +begin + TestEqual('0'); +end; + +initialization + + RegisterTest(TBigIntSignTestCase); + RegisterTest(TBigIntMostSignificantBitIndexTestCase); + RegisterTest(TBigIntFromInt64TestCase); + RegisterTest(TBigIntFromHexTestCase); + RegisterTest(TBigIntFromBinTestCase); + RegisterTest(TBigIntUnaryMinusTestCase); + RegisterTest(TBigIntSumTestCase); + RegisterTest(TBigIntDifferenceTestCase); + RegisterTest(TBigIntProductTestCase); + RegisterTest(TBigIntShiftLeftTestCase); + RegisterTest(TBigIntShiftRightTestCase); + RegisterTest(TBigIntEqualityTestCase); + RegisterTest(TBigIntComparisonTestCase); +end. + diff --git a/tests/UNeverTellMeTheOddsTestCases.pas b/tests/UNeverTellMeTheOddsTestCases.pas index 8b5c282..2f56a79 100644 --- a/tests/UNeverTellMeTheOddsTestCases.pas +++ b/tests/UNeverTellMeTheOddsTestCases.pas @@ -33,6 +33,7 @@ type function CreateSolver: ISolver; override; published procedure TestPart1; + procedure TestPart2; end; { TNeverTellMeTheOddsExampleTestCase } @@ -42,6 +43,7 @@ type function CreateSolver: ISolver; override; published procedure TestPart1; + procedure TestPart2; end; { TNeverTellMeTheOddsTestCase } @@ -77,6 +79,11 @@ begin AssertEquals(15107, FSolver.GetResultPart1); end; +procedure TNeverTellMeTheOddsFullDataTestCase.TestPart2; +begin + AssertEquals(856642398547748, FSolver.GetResultPart2); +end; + { TNeverTellMeTheOddsExampleTestCase } function TNeverTellMeTheOddsExampleTestCase.CreateSolver: ISolver; @@ -89,6 +96,11 @@ begin AssertEquals(2, FSolver.GetResultPart1); end; +procedure TNeverTellMeTheOddsExampleTestCase.TestPart2; +begin + AssertEquals(47, FSolver.GetResultPart2); +end; + { TNeverTellMeTheOddsTestCase } function TNeverTellMeTheOddsTestCase.CreateSolver: ISolver; diff --git a/tests/UPolynomialRootsTestCases.pas b/tests/UPolynomialRootsTestCases.pas new file mode 100644 index 0000000..91ae105 --- /dev/null +++ b/tests/UPolynomialRootsTestCases.pas @@ -0,0 +1,138 @@ +{ + Solutions to the Advent Of Code. + Copyright (C) 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 UPolynomialRootsTestCases; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, UPolynomial, UPolynomialRoots, UBigInt; + +type + + { TPolynomialRootsTestCase } + + TPolynomialRootsTestCase = class(TTestCase) + private + procedure AssertBisectIntervals(AIsolatingIntervals: TIsolatingIntervalArray; constref AExpectedRoots: + array of Cardinal); + procedure AssertBisectIntegers(ARoots: TBigIntArray; constref AExpectedRoots: array of Cardinal); + published + procedure TestBisectNoBound; + procedure TestBisectWithBound; + procedure TestBisectInteger; + end; + +implementation + +{ TPolynomialRootsTestCase } + +procedure TPolynomialRootsTestCase.AssertBisectIntervals(AIsolatingIntervals: TIsolatingIntervalArray; + constref AExpectedRoots: array of Cardinal); +var + exp: Cardinal; + found: Boolean; + i, foundIndex: Integer; +begin + AssertEquals('Unexpected number of isolating intervals.', Length(AExpectedRoots), Length(AIsolatingIntervals)); + for exp in AExpectedRoots do + begin + found := False; + for i := 0 to Length(AIsolatingIntervals) - 1 do + if (AIsolatingIntervals[i].A <= exp) and (exp <= AIsolatingIntervals[i].B) then + begin + found := True; + foundIndex := i; + Break; + end; + AssertTrue('No isolating interval for expected root ' + IntToStr(exp) + ' found.', found); + Delete(AIsolatingIntervals, foundIndex, 1); + end; +end; + +procedure TPolynomialRootsTestCase.AssertBisectIntegers(ARoots: TBigIntArray; constref AExpectedRoots: + array of Cardinal); +var + exp: Cardinal; + found: Boolean; + i, foundIndex: Integer; +begin + AssertEquals('Unexpected number of integer roots.', Length(AExpectedRoots), Length(ARoots)); + for exp in AExpectedRoots do + begin + found := False; + for i := 0 to Length(ARoots) - 1 do + if ARoots[i] = exp then + begin + found := True; + foundIndex := i; + Break; + end; + AssertTrue('Expected root ' + IntToStr(exp) + ' not found.', found); + Delete(ARoots, foundIndex, 1); + end; +end; + +procedure TPolynomialRootsTestCase.TestBisectNoBound; +const + expRoots: array of Cardinal = (34000, 23017, 5); +var + a: TBigIntPolynomial; + r: TIsolatingIntervalArray; +begin + // y = 3 * (x - 34000) * (x - 23017) * (x - 5) * (x^2 - 19) * (x + 112) + // = 3 * x^6 - 170730 * x^5 + 2329429920 * x^4 + 251300082690 * x^3 - 1270471872603 * x^2 + 4774763204640 * x - 24979889760000 + a := TBigIntPolynomial.Create([-24979889760000, 4774763204640, -1270471872603, 251300082690, 2329429920, -170730, 3]); + r := TPolynomialRoots.BisectIsolation(a); + AssertBisectIntervals(r, expRoots); +end; + +procedure TPolynomialRootsTestCase.TestBisectWithBound; +const + expRoots: array of Cardinal = (23017, 5); +var + a: TBigIntPolynomial; + r: TIsolatingIntervalArray; +begin + // y = 3 * (x - 34000) * (x - 23017) * (x - 5) * (x^2 - 19) * (x + 112) + // = 3 * x^6 - 170730 * x^5 + 2329429920 * x^4 + 251300082690 * x^3 - 1270471872603 * x^2 + 4774763204640 * x - 24979889760000 + a := TBigIntPolynomial.Create([-24979889760000, 4774763204640, -1270471872603, 251300082690, 2329429920, -170730, 3]); + r := TPolynomialRoots.BisectIsolation(a, 15); + AssertBisectIntervals(r, expRoots); +end; + +procedure TPolynomialRootsTestCase.TestBisectInteger; +const + expRoots: array of Cardinal = (23017, 5); +var + a: TBigIntPolynomial; + r: TBigIntArray; +begin + // y = 3 * (x - 34000) * (x - 23017) * (x - 5) * (x^2 - 19) * (x + 112) + // = 3 * x^6 - 170730 * x^5 + 2329429920 * x^4 + 251300082690 * x^3 - 1270471872603 * x^2 + 4774763204640 * x - 24979889760000 + a := TBigIntPolynomial.Create([-24979889760000, 4774763204640, -1270471872603, 251300082690, 2329429920, -170730, 3]); + r := TPolynomialRoots.BisectInteger(a, 15); + AssertBisectIntegers(r, expRoots); +end; + +initialization + + RegisterTest(TPolynomialRootsTestCase); +end. + diff --git a/tests/UPolynomialTestCases.pas b/tests/UPolynomialTestCases.pas new file mode 100644 index 0000000..28c92dc --- /dev/null +++ b/tests/UPolynomialTestCases.pas @@ -0,0 +1,187 @@ +{ + Solutions to the Advent Of Code. + Copyright (C) 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 UPolynomialTestCases; + +{$mode ObjFPC}{$H+} + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, UPolynomial, UBigInt; + +type + + { TBigIntPolynomialTestCase } + + TBigIntPolynomialTestCase = class(TTestCase) + private + procedure TestCreateWithDegree(const ACoefficients: array of TBigInt; const ADegree: Integer); + published + procedure TestCreate; + procedure TestCreateDegreeZero; + procedure TestEqual; + procedure TestUnequalSameLength; + procedure TestUnequalDifferentLength; + procedure TestTrimLeadingZeros; + procedure TestCalcValueAt; + procedure TestSignVariations; + procedure TestScaleByPowerOfTwo; + procedure TestScaleVariable; + procedure TestScaleVariableByPowerOfTwo; + procedure TestTranslateVariableByOne; + procedure TestRevertOrderOfCoefficients; + procedure TestDivideByVariable; + end; + +implementation + +{ TBigIntPolynomialTestCase } + +procedure TBigIntPolynomialTestCase.TestCreateWithDegree(const ACoefficients: array of TBigInt; const ADegree: Integer); +var + a: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create(ACoefficients); + AssertEquals('Degree of created polynomial incorrect.', ADegree, a.Degree); +end; + +procedure TBigIntPolynomialTestCase.TestCreate; +begin + TestCreateWithDegree([992123, 7, 20, 4550022], 3); +end; + +procedure TBigIntPolynomialTestCase.TestCreateDegreeZero; +begin + TestCreateWithDegree([4007], 0); + TestCreateWithDegree([], 0); + TestCreateWithDegree([0], 0); +end; + +procedure TBigIntPolynomialTestCase.TestEqual; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([10, 7, 5, 1034]); + b := TBigIntPolynomial.Create([10, 7, 5, 1034]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +procedure TBigIntPolynomialTestCase.TestUnequalSameLength; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([103, 7, 5, 10]); + b := TBigIntPolynomial.Create([1034, 7, 5, 10]); + AssertTrue('Polynomials are equal.', a <> b); +end; + +procedure TBigIntPolynomialTestCase.TestUnequalDifferentLength; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([40000, 10, 7, 5, 1034]); + b := TBigIntPolynomial.Create([10, 7, 5, 1034]); + AssertTrue('Polynomials are equal.', a <> b); +end; + +procedure TBigIntPolynomialTestCase.TestTrimLeadingZeros; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([10, 7, 5, 1034, 0, 0]); + b := TBigIntPolynomial.Create([10, 7, 5, 1034]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +procedure TBigIntPolynomialTestCase.TestCalcValueAt; +var + a: TBigIntPolynomial; + exp: TBigInt; +begin + a := TBigIntPolynomial.Create([80, 892477222, 0, 921556, 7303]); + exp:= TBigInt.FromInt64(16867124285); + AssertTrue('Polynomial evaluation unexpected.', a.CalcValueAt(15) = exp); +end; + +procedure TBigIntPolynomialTestCase.TestSignVariations; +var + a: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([-10, 15, 0, 10, -20, -15, 0, 0, 5, 10, -10]); + AssertEquals(4, a.CalcSignVariations); +end; + +procedure TBigIntPolynomialTestCase.TestScaleByPowerOfTwo; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([10, 7, 5, 1034]).ScaleByPowerOfTwo(7); + b := TBigIntPolynomial.Create([128 * 10, 128 * 7, 128 * 5, 128 * 1034]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +procedure TBigIntPolynomialTestCase.TestScaleVariable; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([10, 7, 5, 1034]).ScaleVariable(TBigInt.FromInt64(10)); + b := TBigIntPolynomial.Create([10, 70, 500, 1034000]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +procedure TBigIntPolynomialTestCase.TestScaleVariableByPowerOfTwo; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([10, 7, 5, 1034]).ScaleVariableByPowerOfTwo(5); + b := TBigIntPolynomial.Create([10, 7 * 32, 5 * 32 * 32, 1034 * 32 * 32 * 32]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +procedure TBigIntPolynomialTestCase.TestTranslateVariableByOne; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([10, 7, 5, 1034]).TranslateVariableByOne; + b := TBigIntPolynomial.Create([1056, 3119, 3107, 1034]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +procedure TBigIntPolynomialTestCase.TestRevertOrderOfCoefficients; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([0, 10, 7, 5, 1034]).RevertOrderOfCoefficients; + b := TBigIntPolynomial.Create([1034, 5, 7, 10]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +procedure TBigIntPolynomialTestCase.TestDivideByVariable; +var + a, b: TBigIntPolynomial; +begin + a := TBigIntPolynomial.Create([0, 10, 7, 5, 1034]).DivideByVariable; + b := TBigIntPolynomial.Create([10, 7, 5, 1034]); + AssertTrue('Polynomials are not equal.', a = b); +end; + +initialization + + RegisterTest(TBigIntPolynomialTestCase); +end. +