Merge branch 'day24-analytical'

This commit is contained in:
Stefan Müller 2024-05-27 02:53:03 +02:00
commit 859a5db921
12 changed files with 3313 additions and 35 deletions

View File

@ -137,6 +137,18 @@
<Filename Value="solvers\UNeverTellMeTheOdds.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UBigInt.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UPolynomial.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UPolynomialRoots.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
</Units>
</ProjectOptions>
<CompilerOptions>

763
UBigInt.pas Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
}
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.

View File

@ -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<TIntegerFactor>;
{ 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<Int64>;
composites: specialize TStack<Int64>;
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<Int64>.Create;
composites := specialize TStack<Int64>.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.

297
UPolynomial.pas Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
}
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.

205
UPolynomialRoots.pas Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
}
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<TIsolatingInterval>;
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<TWorkItem>;
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<TBigInt>;
value: Int64;
begin
// Calculates isolating intervals.
intervals := BisectIsolation(APolynomial, ABoundExp, True);
r := specialize TList<TBigInt>.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.

View File

@ -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<THailstone>;
THailstones = specialize TObjectList<THailstone>;
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<Int64>;
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<Int64>.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;

View File

@ -40,10 +40,6 @@
<Filename Value="UGearRatiosTestCases.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="..\USolver.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UBaseTestCases.pas"/>
<IsPartOfProject Value="True"/>
@ -140,6 +136,18 @@
<Filename Value="UNeverTellMeTheOddsTestCases.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UPolynomialTestCases.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UPolynomialRootsTestCases.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UBigIntTestCases.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
</Units>
</ProjectOptions>
<CompilerOptions>

View File

@ -9,7 +9,7 @@ uses
UHotSpringsTestCases, UPointOfIncidenceTestCases, UParabolicReflectorDishTestCases, ULensLibraryTestCases,
UFloorWillBeLavaTestCases, UClumsyCrucibleTestCases, ULavaductLagoonTestCases, UAplentyTestCases,
UPulsePropagationTestCases, UStepCounterTestCases, USandSlabsTestCases, ULongWalkTestCases,
UNeverTellMeTheOddsTestCases;
UNeverTellMeTheOddsTestCases, UBigIntTestCases, UPolynomialTestCases, UPolynomialRootsTestCases;
{$R *.res}

1033
tests/UBigIntTestCases.pas Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
}
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.

View File

@ -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 <http://www.gnu.org/licenses/>.
}
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.