implement JS password scoring, td_stock approve logicc

This commit is contained in:
Thanakorn S
2026-04-30 15:14:42 +07:00
parent db5c47b6ca
commit ac4fd9a5ad
3147 changed files with 1298 additions and 343 deletions
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math;
use ZxcvbnPhp\Math\Impl\BinomialProviderPhp73Gmp;
use ZxcvbnPhp\Math\Impl\BinomialProviderFloat64;
use ZxcvbnPhp\Math\Impl\BinomialProviderInt64;
class Binomial
{
private static $provider = null;
private function __construct()
{
throw new \LogicException(__CLASS__ . " is static");
}
/**
* Calculate binomial coefficient (n choose k).
*
* @param int $n
* @param int $k
* @return float
*/
public static function binom(int $n, int $k): float
{
return self::getProvider()->binom($n, $k);
}
public static function getProvider(): BinomialProvider
{
if (self::$provider === null) {
self::$provider = self::initProvider();
}
return self::$provider;
}
/**
* @return string[]
*/
public static function getUsableProviderClasses(): array
{
// In order of priority. The first provider with a value of true will be used.
$possibleProviderClasses = [
BinomialProviderPhp73Gmp::class => function_exists('gmp_binomial'),
BinomialProviderInt64::class => PHP_INT_SIZE >= 8,
BinomialProviderFloat64::class => PHP_FLOAT_DIG >= 15,
];
$possibleProviderClasses = array_filter($possibleProviderClasses);
return array_keys($possibleProviderClasses);
}
private static function initProvider(): BinomialProvider
{
$providerClasses = self::getUsableProviderClasses();
if (!$providerClasses) {
throw new \LogicException("No valid providers");
}
$bestProviderClass = reset($providerClasses);
return new $bestProviderClass();
}
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math;
interface BinomialProvider
{
/**
* Calculate binomial coefficient (n choose k).
*
* @param int $n
* @param int $k
* @return float
*/
public function binom(int $n, int $k): float;
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
use ZxcvbnPhp\Math\BinomialProvider;
abstract class AbstractBinomialProvider implements BinomialProvider
{
public function binom(int $n, int $k): float
{
if ($k < 0 || $n < 0) {
throw new \DomainException("n and k must be non-negative");
}
if ($k > $n) {
return 0;
}
// $k and $n - $k will always produce the same value, so use smaller of the two
$k = min($k, $n - $k);
return $this->calculate($n, $k);
}
abstract protected function calculate(int $n, int $k): float;
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
abstract class AbstractBinomialProviderWithFallback extends AbstractBinomialProvider
{
/**
* @var AbstractBinomialProvider|null
*/
private $fallback = null;
protected function calculate(int $n, int $k): float
{
return $this->tryCalculate($n, $k) ?? $this->getFallbackProvider()->calculate($n, $k);
}
abstract protected function tryCalculate(int $n, int $k): ?float;
abstract protected function initFallbackProvider(): AbstractBinomialProvider;
protected function getFallbackProvider(): AbstractBinomialProvider
{
if ($this->fallback === null) {
$this->fallback = $this->initFallbackProvider();
}
return $this->fallback;
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
class BinomialProviderFloat64 extends AbstractBinomialProvider
{
protected function calculate(int $n, int $k): float
{
$c = 1.0;
for ($i = 1; $i <= $k; $i++, $n--) {
// We're aiming for $c * $n / $i, but the $c * $n part could cause us to lose precision, so use $c / $i * $n instead. The caveat
// here is that in order to get a precise answer, we need to minimize the chances of going above ~2^52. This is mitigated
// somewhat by dealing with whole part and the remainder separately, but it's not perfect and could overflow in practice, which
// would result in a loss of precision.
$c = floor($c / $i) * $n + floor(fmod($c, $i) * $n / $i);
}
return $c;
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
use TypeError;
class BinomialProviderInt64 extends AbstractBinomialProviderWithFallback
{
protected function initFallbackProvider(): AbstractBinomialProvider
{
return new BinomialProviderFloat64();
}
protected function tryCalculate(int $n, int $k): ?float
{
try {
$c = 1;
for ($i = 1; $i <= $k; $i++, $n--) {
// We're aiming for $c * $n / $i, but the $c * $n part could overflow, so use $c / $i * $n instead. The caveat here is that in
// order to get a precise answer, we need to avoid floats, which means we need to deal with whole part and the remainder
// separately.
$c = intdiv($c, $i) * $n + intdiv($c % $i * $n, $i);
}
return (float)$c;
} catch (TypeError $ex) {
return null;
}
}
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace ZxcvbnPhp\Math\Impl;
class BinomialProviderPhp73Gmp extends AbstractBinomialProvider
{
/**
* @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection
* @noinspection PhpComposerExtensionStubsInspection
*/
protected function calculate(int $n, int $k): float
{
return (float)gmp_strval(gmp_binomial($n, $k));
}
}