1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
<?php
/**
* This script stress tests calculators with random large numbers and ensures that all implementations return the same
* results. It is designed to run in an infinite loop unless a bug is found.
*/
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Brick\Math\Internal\Calculator;
(new class(30) { // max digits
private readonly Calculator\GmpCalculator $gmp;
private readonly Calculator\BcMathCalculator $bcmath;
private readonly Calculator\NativeCalculator $native;
private int $testCounter = 0;
private float $lastOutputTime = 0.0;
private int $currentSecond = 0;
private int $currentSecondTestCounter = 0;
private int $testsPerSecond = 0;
public function __construct(
private readonly int $maxDigits,
) {
$this->gmp = new Calculator\GmpCalculator();
$this->bcmath = new Calculator\BcMathCalculator();
$this->native = new Calculator\NativeCalculator();
}
public function __invoke() : void
{
for (;;) {
$a = $this->generateRandomNumber();
$b = $this->generateRandomNumber();
$c = $this->generateRandomNumber();
$this->runTests($a, $b);
$this->runTests($b, $a);
if ($a !== '0') {
$this->runTests("-$a", $b);
$this->runTests($b, "-$a");
}
if ($b !== '0') {
$this->runTests($a, "-$b");
$this->runTests("-$b", $a);
}
if ($a !== '0' && $b !== '0') {
$this->runTests("-$a", "-$b");
$this->runTests("-$b", "-$a");
}
if ($c !== '0') {
$this->test("$a POW $b MOD $c", fn (Calculator $calc) => $calc->modPow($a, $b, $c));
}
}
}
/**
* @param string $a The left operand.
* @param string $b The right operand.
*/
private function runTests(string $a, string $b) : void
{
$this->test("$a + $b", fn (Calculator $c) => $c->add($a, $b));
$this->test("$a - $b", fn (Calculator $c) => $c->sub($a, $b));
$this->test("$a * $b", fn (Calculator $c) => $c->mul($a, $b));
if ($b !== '0') {
$this->test("$a / $b", fn (Calculator $c) => $c->divQR($a, $b));
$this->test("$a MOD $b", fn (Calculator $c) => $c->mod($a, $b));
}
if ($b !== '0' && $b[0] !== '-') {
$this->test("INV $a MOD $b", fn (Calculator $c) => $c->modInverse($a, $b));
}
$this->test("GCD $a, $b", fn (Calculator $c) => $c->gcd($a, $b));
if ($a[0] !== '-') {
$this->test("SQRT $a", fn (Calculator $c) => $c->sqrt($a));
}
$this->test("$a AND $b", fn (Calculator $c) => $c->and($a, $b));
$this->test("$a OR $b", fn (Calculator $c) => $c->or($a, $b));
$this->test("$a XOR $b", fn (Calculator $c) => $c->xor($a, $b));
}
/**
* @param string $test A string representing the test being executed.
* @param Closure(Calculator): mixed $callback A callback function accepting a Calculator instance and returning a calculation result.
*/
private function test(string $test, Closure $callback) : void
{
$gmpResult = $callback($this->gmp);
$bcmathResult = $callback($this->bcmath);
$nativeResult = $callback($this->native);
if ($gmpResult !== $bcmathResult) {
$this->failure('GMP', 'BCMath', $test);
}
if ($gmpResult !== $nativeResult) {
$this->failure('GMP', 'Native', $test);
}
$this->testCounter++;
$this->currentSecondTestCounter++;
$time = microtime(true);
$second = (int) $time;
if ($second !== $this->currentSecond) {
$this->currentSecond = $second;
$this->testsPerSecond = $this->currentSecondTestCounter;
$this->currentSecondTestCounter = 0;
}
if ($time - $this->lastOutputTime >= 0.1) {
echo "\r", number_format($this->testCounter), ' (', number_format($this->testsPerSecond) . ' / s)';
$this->lastOutputTime = $time;
}
}
/**
* @param string $c1 The name of the first calculator.
* @param string $c2 The name of the second calculator.
* @param string $test A string representing the test being executed.
*/
private function failure(string $c1, string $c2, string $test) : never
{
echo PHP_EOL;
echo 'FAILURE!', PHP_EOL;
echo $c1, ' vs ', $c2, PHP_EOL;
echo $test, PHP_EOL;
die;
}
private function generateRandomNumber() : string
{
$length = random_int(1, $this->maxDigits);
$number = '';
for ($i = 0; $i < $length; $i++) {
$number .= random_int(0, 9);
}
$number = ltrim($number, '0');
if ($number === '') {
return '0';
}
return $number;
}
})();
|