Home » Php » math – How to implement a random float function so that it does not lose entropy? (PHP)

# math – How to implement a random float function so that it does not lose entropy? (PHP)

Questions:

I am trying to generate random floats using nothing but bytes I get from /dev/urandom. Currently my best idea is to get the platform precision doing something like:

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

and then construct a string of 0-9 in a loop the number of times \$maximumPrecision tells us. For examle, if the precision is 12, I would generate 12 random numbers and concatenate them. I think it’s an ugly idea.

Update: Does this make sense?

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

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

\$number = \$parts + pow(2.0, 32) * \$parts;
\$number /= pow(2.0, 52);
``````

PHP’s float type typically is implemented as IEEE double. This format has 52 bit precision of mantissa, so in principle it should be able to generate 252 different uniform numbers in [0, 1).

Therefore, you could extract 52 bits from the /dev/urandom, interpret as an integer, and divide by 252. For example:

``````// 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 + pow(2.0, 32) * \$parts;  // <-- this is precise.
\$theNumber /= pow(2.0, 52);                         // <-- this is precise.
``````

The problem here is that the IEEE double precision number is defined in terms of exponents of base 2:

``````n = 2^exponent * 1.mantissa
``````

Since you want an exponent -1, and there’s no integer number `n` such that `2^n = 0.1`, it gets complicated.

This generates a number between 1 and 2. You can subtract `1`, but you’ll lose a tiny amount of entropy if you do that (KennyTM’s answer yields a number in that range, though, and uses all entropy — this answer attempts to create the representation directly):

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

//build big-endian double
//take only 32 bits because of 32-bit platforms
\$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);
``````