IEEE754 doubles and uniform random distribution in C
IEEE754 floating point numbers have 3 parts: a sign (1bit), an exponent (11bits) and a mantissa (52bits).
To translate into C:
union {
struct {
unsigned long long m : 52;
unsigned long long e : 11;
unsigned long long s : 1;
};
double d;
} X;
To make a uniformly distributed number between -1 and 1, we need:
- A random mantissa
m
- An exponent
e
starts at 1022 (which is actually -1) and has 50% chance of being 1022, 25% chance to be 1021, 12.5% chance to be 1020... - a random sign
s
This leads to the following:
#include <stdint.h>
// Please don't use C rand()
extern uint32_t decent_rand();
double uniform_rand_double_between_minus_one_and_one() {
union {
struct {
unsigned long long m : 52;
unsigned long long e : 11;
unsigned long long s : 1;
};
double d;
} X;
uint32_t bit_divider = decent_rand();
// Random mantissa
X.m = decent_rand() + ((uint64_t)bit_divider<<32);
// Base exponent
X.e = 1022;
// Recover unused entropy
bit_divider >>= 20;
// Infinite loop, for speed
int i = 12;
for(;;) {
for(; i; --i) {
// Consume entropy one bit at a time
if(bit_divider & 1) {
// 50% chance to reduce
X.e -= 1;
bit_divider >>= 1;
// Handle underflow of the exponent
if(X.e == 0) goto very_unlikely;
} else {
// 50% chance to be done
bit_divider >>= 1;
// Set the sign
X.s = bit_divider & 1;
return X.d;
}
}
// We consumed all the entropy, we start again
bit_divider = decent_rand();
i = 32;
}
very_unlikely:
X.s = bit_divider & 1;
return X.d;
}
Then we can shift that space into another slice of the space, but we the sign is a hurdle here. Let's have the function without the sign:
double uniform_rand_double_between_zero_and_one_opti() {
union {
struct {
unsigned long long m : 52;
unsigned long long e : 11;
unsigned long long s : 1;
};
double d;
} X;
uint32_t bit_divider = decent_rand();
// Random mantissa
X.m = decent_rand() + ((uint64_t)bit_divider<<32);
// Base exponent
X.e = 1022;
// Recover unused entropy
bit_divider >>= 20;
// Infinite loop, for speed
int i = 12;
for(;;) {
for(; i; --i) {
// Consume entropy one bit at a time
// Handle underflow of the exponent
if(bit_divider & 1 && X.e != 0) [[unlikely]] {
// 50% chance to reduce
X.e -= 1;
bit_divider >>= 1;
} else {
// 50% chance to be done
goto out;
}
}
// We consumed all the entropy, we start again
bit_divider = decent_rand();
i=32;
}
out:
X.s = 0;
return X.d;
}
double uniform_rand_between(double a, double b) {
return uniform_rand_double_between_zero_and_one()*(b-a) + a
}
There you have it, a uniform random number generator for real numbers stored as IEEE754 doubles.