Last active
April 2, 2026 21:04
-
-
Save ghostwriter/f374a58987582d48e0688e0fa981bc31 to your computer and use it in GitHub Desktop.
PythonRandom in PHP
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); | |
| /** | |
| * #BlackLivesMatter - github.com/ghostwriter | |
| **/ | |
| final class PythonRandom | |
| { | |
| private const LOWER_MASK = 0x7FFFFFFF; | |
| private const MATRIX_A = 0x9908B0DF; | |
| private const PERIOD = 397; | |
| private const STATE_SIZE = 624; | |
| private const UPPER_MASK = 0x80000000; | |
| private const DEFAULT_SEED = 19650218; | |
| private const INIT_MULTIPLIER = 1812433253; | |
| private const INIT_ARRAY_MULTIPLIER_1 = 1664525; | |
| private const INIT_ARRAY_MULTIPLIER_2 = 1566083941; | |
| private const UINT32_MASK = 0xFFFFFFFF; | |
| private const TEMPERING_MASK_B = 0x9D2C5680; | |
| private const TEMPERING_MASK_C = 0xEFC60000; | |
| private const FLOAT_HIGH_SHIFT = 5; | |
| private const FLOAT_LOW_SHIFT = 6; | |
| private const FLOAT_HIGH_MULTIPLIER = 67108864.0; // 2^26 | |
| private const FLOAT_DIVISOR = 9007199254740992.0; // 2^53 | |
| /** | |
| * Next Gaussian value (for Box-Muller transform). | |
| */ | |
| private ?float $nextGaussian = null; | |
| /** | |
| * Current index in the state vector. | |
| */ | |
| private int $stateIndex = self::STATE_SIZE; | |
| /** | |
| * State vector for the generator. | |
| * | |
| * @var array<int, int> | |
| */ | |
| private array $stateVector = []; | |
| /** | |
| * @param int $seed Seed value for PRNG | |
| * | |
| * @throws InvalidArgumentException | |
| */ | |
| public function __construct(int $seed) | |
| { | |
| $this->initialize($seed); | |
| } | |
| /** | |
| * Generate a Gaussian random value. | |
| */ | |
| public function generate(float $mean = 0.0, float $standardDeviation = 1.0): float | |
| { | |
| if (null !== $this->nextGaussian) { | |
| $z = $this->nextGaussian; | |
| $this->nextGaussian = null; | |
| } else { | |
| $angle = $this->randomFloat() * 2.0 * M_PI; | |
| $radius = sqrt(-2.0 * log(1.0 - $this->randomFloat())); | |
| $z = cos($angle) * $radius; | |
| $this->nextGaussian = sin($angle) * $radius; | |
| } | |
| return $mean + $z * $standardDeviation; | |
| } | |
| /** | |
| * Generate a 32-bit unsigned integer. | |
| */ | |
| private function generateUInt32(): int | |
| { | |
| if (self::STATE_SIZE <= $this->stateIndex) { | |
| for ($i = 0; self::STATE_SIZE > $i; ++$i) { | |
| $bits = ($this->stateVector[$i] & self::UPPER_MASK) | |
| | ($this->stateVector[($i + 1) % self::STATE_SIZE] & self::LOWER_MASK); | |
| $this->stateVector[$i] | |
| = $this->stateVector[($i + self::PERIOD) % self::STATE_SIZE] | |
| ^ ($bits >> 1); | |
| if ($bits & 1) { | |
| $this->stateVector[$i] ^= self::MATRIX_A; | |
| } | |
| } | |
| $this->stateIndex = 0; | |
| } | |
| $bits = $this->stateVector[$this->stateIndex++]; | |
| $bits ^= $bits >> 11; | |
| $bits ^= ($bits << 7) & self::TEMPERING_MASK_B; | |
| $bits ^= ($bits << 15) & self::TEMPERING_MASK_C; | |
| $bits ^= $bits >> 18; | |
| return $bits & self::UINT32_MASK; | |
| } | |
| /** | |
| * Initialize the state vector with an array key. | |
| * | |
| * @param array<int, int> $keyArray | |
| */ | |
| private function initialize(int $seed): void | |
| { | |
| $this->initializeWithDefaultSeed(); | |
| $i = 1; | |
| $j = 0; | |
| $keyArray =$this->normalizeSeed($seed); | |
| $iterations = max(self::STATE_SIZE, count($keyArray)); | |
| for (; 0 < $iterations; --$iterations) { | |
| $this->stateVector[$i] = ( | |
| ($this->stateVector[$i] | |
| ^ (($this->stateVector[$i - 1] ^ ($this->stateVector[$i - 1] >> 30)) * self::INIT_ARRAY_MULTIPLIER_1)) | |
| + $keyArray[$j] + $j | |
| ) & self::UINT32_MASK; | |
| ++$i; | |
| ++$j; | |
| if (self::STATE_SIZE <= $i) { | |
| $this->stateVector[0] = $this->stateVector[self::STATE_SIZE - 1]; | |
| $i = 1; | |
| } | |
| if (count($keyArray) <= $j) { | |
| $j = 0; | |
| } | |
| } | |
| for ($iterations = self::STATE_SIZE - 1; 0 < $iterations; --$iterations) { | |
| $this->stateVector[$i] = ( | |
| ($this->stateVector[$i] | |
| ^ (($this->stateVector[$i - 1] ^ ($this->stateVector[$i - 1] >> 30)) * self::INIT_ARRAY_MULTIPLIER_2)) | |
| - $i | |
| ) & self::UINT32_MASK; | |
| ++$i; | |
| if (self::STATE_SIZE <= $i) { | |
| $this->stateVector[0] = $this->stateVector[self::STATE_SIZE - 1]; | |
| $i = 1; | |
| } | |
| } | |
| $this->stateVector[0] = self::UPPER_MASK; | |
| } | |
| /** | |
| * Initialize the state vector with a seed. | |
| */ | |
| private function initializeWithDefaultSeed(): void | |
| { | |
| $this->stateVector[0] = self::DEFAULT_SEED & self::UINT32_MASK; | |
| for ($i = 1; self::STATE_SIZE > $i; ++$i) { | |
| $this->stateVector[$i] = ( | |
| self::INIT_MULTIPLIER * ($this->stateVector[$i - 1] ^ ($this->stateVector[$i - 1] >> 30)) + $i | |
| ) & self::UINT32_MASK; | |
| } | |
| $this->stateIndex = self::STATE_SIZE; | |
| } | |
| /** | |
| * Normalize the seed into an array of 32-bit unsigned integers. | |
| * | |
| * @return array<int, int> | |
| */ | |
| private function normalizeSeed(int $seed): array | |
| { | |
| if (0 > $seed) { | |
| $seed = -$seed; | |
| } | |
| $seedArray = []; | |
| do { | |
| $seedArray[] = $seed & self::UINT32_MASK; | |
| $seed >>= 32; | |
| } while (0 < $seed); | |
| return $seedArray; | |
| } | |
| /** | |
| * Generate a random float in [0, 1). | |
| */ | |
| private function randomFloat(): float | |
| { | |
| $highBits = $this->generateUInt32() >> self::FLOAT_HIGH_SHIFT; | |
| $lowBits = $this->generateUInt32() >> self::FLOAT_LOW_SHIFT; | |
| return ($highBits * self::FLOAT_HIGH_MULTIPLIER + $lowBits) / self::FLOAT_DIVISOR; | |
| } | |
| } |
Author
ghostwriter
commented
Apr 2, 2026
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment