Skip to content

Instantly share code, notes, and snippets.

@ghostwriter
Last active April 2, 2026 21:04
Show Gist options
  • Select an option

  • Save ghostwriter/f374a58987582d48e0688e0fa981bc31 to your computer and use it in GitHub Desktop.

Select an option

Save ghostwriter/f374a58987582d48e0688e0fa981bc31 to your computer and use it in GitHub Desktop.
PythonRandom in PHP
<?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;
}
}
@ghostwriter
Copy link
Copy Markdown
Author

<?php

declare(strict_types=1);

/**
 * #BlackLivesMatter - github.com/ghostwriter
 **/

// Example usage:

$randomGenerator = new PythonRandom(-20260220);
$randomValue = $randomGenerator->generate(0.0, 1.0);
var_dump([
    'PythonRandom' => $randomValue,
]);

// https://www.online-python.com/fIyr3C8MZc
// https://3v4l.org/WnK0i

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment