如何使用按位运算将随机uint64_t转换为范围(0,1)中的随机double

时间:2018-09-03 09:59:14

标签: c++

我能做到的最好的就是这个(结果在0〜0.5之间):

uint64_t const FRACTION_MASK = 0x1fffffffffffffull;
uint64_t const EXPONENT_BITS = 0x3fc0000000000000ull;
double to01(uint64_t i) {
    i = (i & FRACTION_MASK) | EXPONENT_BITS;
    return reinterpret_cast<double&>(i);
}

我尝试并弄错了那些魔术数字。我什至不知道这是否可以产生双精度值可以表示0到0.5之间的所有值。

3 个答案:

答案 0 :(得分:3)

由于对双数进行编码的方式,很难直接在(0,1)范围内生成良好的随机数,但是在[1,2)范围内很容易获得一个随机数[@GemTaylor]

double to_01(uint64_t i)
{
    constexpr uint64_t mask1 = 0x3FF0000000000000ULL;
    constexpr uint64_t mask2 = 0x3FFFFFFFFFFFFFFFULL;
    const uint64_t to_12 = (i | mask1) & mask2;
    double d;
    memcpy(&d, &to_12, 8);
    return d - 1;
}

请注意,并非i的所有位都用于生成结果。另外请注意,此功能仅适用于64位IEEE-754浮点数[@geza]。

答案 1 :(得分:1)

根据我的收集和判断,仅是想将uint_64的位映射到double的位,以使您的double介于[0,1]

双(64位)表示是

-1S×(1.0 + 0.M)×2E偏差(偏差为1023)

符号S为1位,尾数为52位,指数为11位。

首先对double进行了签名,所以由于您希望double> 0.0,因此无法映射符号位。 其次,我们不能超过E-bias> 1022,否则我们将获得超过1.0的值double

因此,用于将uint_64映射为[0,1]之间的两倍的掩码是

0 01111111110 1111111111111111111111111111111111111111111111111111111 十六进制为0x3FEFFFFFFFFFFFFFull

因此是代码

uint64_t const MASK = 0x3FEFFFFFFFFFFFFFull;
double to01(uint64_t i) {
    i = (i & MASK);
    return reinterpret_cast<double&>(i);
}

应该可以解决问题。使用这种方法,您已经在uint_64的64位中使用了61位。

但要警告:如果您的uint_64生成器在[0,uint64_t_max]上是均匀的,那么使用to01获得的生成器在[0,1]上将不是均匀的!

答案 2 :(得分:0)

这就是我用的 我移位12得到52位 我或与1,以便我只得到奇数值,这避免了返回0 然后在返回时减去1.0

如果您是整数源,则均匀分布 您的结果将均匀分布

double convert_uint64_to_double(uint64_t value)
{
    uint64_t u64 = 0x3FF0000000000000ULL | ((value >> 12) | 1) ;
    return *(double*)&u64 - 1.0;
}

double convert_uint32_to_double(uint32_t value)
{
    uint64_t u64 = 0x3FF0000000000000ULL | (((uint64_t(value) << 1) | 1) << 19);
    return *(double*)&u64 - 1.0;
}