Как реализовать случайную функцию с плавающей точкой, чтобы она не теряла энтропию?(PHP) - PullRequest
8 голосов
/ 11 сентября 2010

Я пытаюсь генерировать случайные числа с плавающей точкой, используя только байты, которые я получаю из / dev / urandom. В настоящее время моя лучшая идея состоит в том, чтобы добиться точности платформы, выполнив что-то вроде:

$maximumPrecision = strlen('' . 1/3) - 2;

и затем создайте строку 0-9 в цикле, сколько раз нам сообщает $ MaximumPrecision. Например, если точность равна 12, я сгенерирую 12 случайных чисел и объединю их. Я думаю, что это ужасная идея.

Обновление: имеет ли это смысл?

$bytes =getRandomBytes(7); // Just a function that returns random bytes.
$bytes[6] = $bytes[6] & chr(15); // Get rid off the other half
$bytes .= chr(0); // Add a null byte

$parts = unpack('V2', $bytes);

$number = $parts[1] + pow(2.0, 32) * $parts[2];
$number /= pow(2.0, 52);

Ответы [ 2 ]

4 голосов
/ 11 сентября 2010

Тип float в PHP обычно реализован как IEEE double .Этот формат имеет 52-битную точность мантиссы, поэтому в принципе он должен генерировать 2 52 разных одинаковых чисел в [0, 1).

Таким образом, вы можете извлечь 52 бита из / dev / urandom, интерпретировать их как целое число и разделить на 2 52 . Например :

// assume we have 52 bits of data, little endian.
$bytes = "\x12\x34\x56\x78\x9a\xbc\x0d\x00";
//                                  ^   ^^ 12 bits of padding.

$parts = unpack('V2', $bytes);

$theNumber = $parts[1] + pow(2.0, 32) * $parts[2];  // <-- this is precise.
$theNumber /= pow(2.0, 52);                         // <-- this is precise.
1 голос
/ 11 сентября 2010

Проблема здесь в том, что IEEE число двойной точности определяется в показателях степени основания 2:

n = 2^exponent * 1.mantissa

Так как вы хотите получить показатель степени -1, и нет целого числа n такого, что 2^n = 0.1, это усложняется.

При этом получается число от 1 до 2. Вы можете вычесть 1, но при этом вы потеряете небольшое количество энтропии (хотя ответ KennyTM дает число в этом диапазоне и использует всю энтропию - - этот ответ пытается создать представление напрямую):

$source = fopen("/dev/urandom", "rb");

//build big-endian double
//take only 32 bits because of 32-bit platforms
$byte_batch_1 = fread($source, 4); //32-bit
$byte_batch_2 = fread($source, 4); //32-bit, we only need 20

$offset = (1 << 10) -1;

$first_word = unpack("N", $byte_batch_2);
$first_word = reset($first_word);
$first_word &= 0xFFFFF; //leave only 20 lower bits
$first_word |= $offset << 20;

$str = pack("N", $first_word) . $byte_batch_1;

//convert to little endian if necessary
if (pack('s', 1) == "\x01\x00") { //little-endian
    $str = strrev($str);
}

$float = unpack("d", $str);
$float = reset($float);
...