Last active
March 18, 2024 14:25
-
-
Save tooruu/f1d0965c04db3b6f84d814f0be947073 to your computer and use it in GitHub Desktop.
Number conversion benchmark and validator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
abstract readonly class BtcConverterBenchmark | |
{ | |
abstract protected function sat2Btc(int $num): string; | |
abstract protected function btc2Sat(string $num): int; | |
public function __construct(protected int $runs = 1_000_000) | |
{ | |
} | |
/** | |
* @param int|string $num If string then it is BTC amount, otherwise - Satoshi. | |
* @param int|string|null $expected Optional. If set, check the result against it. | |
* Must be opposite of $num's type. | |
* @return float Average time in nanoseconds operation took to perform. | |
* | |
* @throws ValueError if $expected is set, and it does not strictly equal the result. | |
*/ | |
final public function run(int|string $num, int|string|null $expected = null): float | |
{ | |
if (gettype($num) === gettype($expected)) { | |
throw new TypeError('$num and $assert can\'t be the same type'); | |
} | |
$worker = is_int($num) ? $this->sat2Btc(...) : $this->btc2Sat(...); | |
$start = hrtime(true); | |
$result = $worker($num); | |
$end = hrtime(true); | |
if ($expected && $expected !== $result) { | |
throw $this->formatError($num, $expected, $result); | |
} | |
$sum = array_reduce( | |
range(2, $this->runs), | |
function (int $carry, int $ignored) use ($worker, $num): int { | |
$start = hrtime(true); | |
$worker($num); | |
$end = hrtime(true); | |
return $carry + $end - $start; | |
}, | |
$end - $start, | |
); | |
return $sum / $this->runs; | |
} | |
protected function formatError(int|string $number, string|int $expected, mixed $actual): ValueError | |
{ | |
if (is_int($number)) { | |
$number = rtrim(rtrim(number_format($number, 8, '.', '_'), '0'), '.'); | |
} | |
return new ValueError( | |
PHP_EOL.'given: '.gettype($number)." $number". | |
PHP_EOL.'want: '.gettype($expected)." $expected". | |
PHP_EOL.'got: '.gettype($actual).' '.print_r($actual, true). | |
PHP_EOL | |
); | |
} | |
} | |
readonly class GMPBenchmark extends BtcConverterBenchmark | |
{ | |
protected GMP $BTC_PRECISION; | |
public function __construct(...$args) | |
{ | |
parent::__construct(...$args); | |
$this->BTC_PRECISION = gmp_init(10 ** 8); | |
} | |
protected function sat2Btc(int $num): string | |
{ | |
// Leading zeros in remainder are cut off, so this doesn't pass tests. | |
return rtrim(rtrim(implode('.', gmp_div_qr($num, $this->BTC_PRECISION)), '0'), '.'); | |
} | |
protected function btc2Sat(string $num): int | |
{ | |
// Only works with integer strings. | |
return gmp_intval(gmp_mul($num, 10 ** 8)); | |
} | |
} | |
readonly class BCMathBenchmark extends BtcConverterBenchmark | |
{ | |
protected const string BTC_PRECISION = '100000000'; | |
protected function sat2Btc(int $num): string | |
{ | |
return rtrim(rtrim(bcdiv((string) $num, self::BTC_PRECISION, 8), '0'), '.'); | |
} | |
protected function btc2Sat(string $num): int | |
{ | |
return (int) bcmul((string) $num, self::BTC_PRECISION); | |
} | |
} | |
readonly class NativeBenchmark extends BtcConverterBenchmark | |
{ | |
protected const int BTC_PRECISION = 10 ** 8; | |
protected function sat2Btc(int $num): string | |
{ | |
return rtrim(rtrim(number_format($num / self::BTC_PRECISION, 8, '.', ''), '0'), '.'); | |
} | |
protected function btc2Sat(string $num): int | |
{ | |
return intval(strval($num * self::BTC_PRECISION)); | |
} | |
} | |
function runBenchmarks(int|string $amount, int|string|null $expected = null): void | |
{ | |
global $gmp, $bc, $native; | |
//echo 'gmp: '.$gmp->run($amount, $expected).PHP_EOL; | |
echo 'bc: '.$bc->run($amount, $expected).PHP_EOL; | |
echo 'native: '.$native->run($amount, $expected).PHP_EOL; | |
echo PHP_EOL; | |
} | |
[$gmp, $bc, $native] = [new GMPBenchmark, new BCMathBenchmark, new NativeBenchmark]; | |
runBenchmarks(amount: 1_000_000_000, expected: '10'); | |
runBenchmarks(amount: '10', expected: 1_000_000_000); | |
runBenchmarks(amount: 9876, expected: '0.00009876'); | |
runBenchmarks(amount: '0.00009876', expected: 9876); | |
runBenchmarks(amount: 123456789, expected: '1.23456789'); | |
runBenchmarks(amount: '1.23456789', expected: 123456789); | |
runBenchmarks(amount: 2_100_000_000_000_000, expected: '21000000'); | |
runBenchmarks(amount: '21000000.00', expected: 2_100_000_000_000_000); | |
runBenchmarks(amount: '1.0', expected: 100_000_000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment