【问题标题】:Using bit shifting with rand() to allow for a larger random range使用带有 rand() 的位移来允许更大的随机范围
【发布时间】:2021-07-20 08:29:29
【问题描述】:

我正在审查一个为基数映射生成键的函数,发现 rand() 的实现对我来说很新颖。

函数如下:

static int make_random(RadixMap *map)
{
    size_t i = 0;

    for (i = 0; i < map->max - 1; i++){
        uint32_t key = (uint32_t) (rand() | (rand() << 16));<--This was interesting
        check(RadixMap_add(map, key, i) == 0, "Failed to add key %u", key);
    }

    return i;

error:
    return 0;
}

----- Type definitions --------
typedef union RMElement {
    uint64_t raw;
    struct {
        uint32_t key;
        uint32_t value;
    } data;
} RMElement; 

typedef struct RadixMap {
    size_t max; 
    size_t end;
    uint32_t counter;
    RMElement *contents;
    RMElement *temp; 
} RadixMap;

来自 Zed Shaw 的 ex35 Learn C the Hard Way

我发现有趣的具体部分是

uint32_t key = (uint32_t) (rand() | (rand() << 16)); <-- This was interesting

这对我来说很有趣,因为它本来可以简单地做 ..

uint32_t key = rand();

由于 RAND_MAX (0x7FFFFFFF) 小于 uint32_t MAX (0xFFFFFFFF)

移位实现看起来具有以下优点。

  1. 允许更大的随机值范围,0xFFFFFFFF 与 0x7FFFFFFF
  2. 值(初始 0 除外)至少为 5 位十进制 (65537) (0x10001)
  3. 看到“0”的概率降低。

还有以下缺点

  1. 增加代码复杂度?

还有其他原因使用 rand() 的移位实现吗?

我一直试图在我的代码审查中找出使用此实现的原因,并希望确保我的想法是正确的。

【问题讨论】:

  • 在实际生产代码中,如果你想要真正的随机性和安全性,你不应该一开始就使用 rand/srand。我想测试没问题,但看看 /dev/random 和 rdrand
  • 除了减少数字的随机性之外?通过使用| - 你失去了很多你的范围。我希望至少有一个^。基本上,只要您多次调用 random 以形成一个值,您“可能”就会失去分布。我想您可以关闭高 16 位,然后将这些值放在一起以获得您描述的分布,但我宁愿使用更现代的 rand() 函数。
  • 该代码在RAND_MAX = 0x7FFF(windows)(使第 16 位卡在零)和RAND_MAX = 0x7FFFFFFF(linux)(中间位有 75% 的机会被设置)都不正确。所以基本上,这是书中的一个错误。

标签: c


【解决方案1】:

C 标准仅保证 RAND_MAX 至少为 32767。此代码通过调用 rand 两次并移位以确保其获得至少 30 位随机性来解决此问题。

但是,这并不能正确解释RAND_MAX 更大的情况。

rand 函数返回一个已签名的int。如果RAND_MAXINT_MAX 相同,rand() &lt;&lt; 16 很可能会将“1”位移入符号位,从而触发undefined behavior

处理这两种情况的正确方法是:

uint32_t key = rand() | ((uint32_t)rand() << 16));

由于左移,只要移位量小于类型的大小,无符号数就可以很好地定义。

或者更好:

uint32_t key = (((uint32_t)rand() & 0x7FFF) << 17) | 
               (((uint32_t)rand() & 0x7FFF) << 2) | 
               ((uint32_t)rand() & 0x3);

获得完整的 32 位随机性。

【讨论】:

  • 代替uint32_t key = rand() | ((uint32_t)rand() &lt;&lt; 16));,考虑uint32_t key = rand() ^ ((uint32_t)rand() &lt;&lt; 16));以避免RAND_MAX &gt; 0xFFFF时出现多余的一位。
【解决方案2】:

uint32_t key = (uint32_t) (rand() | (rand() &lt;&lt; 16));有缺点。

  • RAND_MAX != 65535 时不统一,这是通常的情况。

  • int 为 16 位时的未定义行为。由于rand() &lt;&lt; 16@ 有符号整数溢出的可能性,在其他情况下也是 UB

  • 演员阵容为时已晚,无法抵御狭窄的int。实际上与uint32_t key = rand() | (rand() &lt;&lt; 16); uint32_t key = rand() + (rand() * (RAND_MAX+(uint32_t key)1); 相同会更有意义。

一个关键的失败是使用| 附加在右边归零的位与RAND_MAX 的位宽不同。

第二个弱点是假设移位比乘以 2 的幂更好。无论哪种方式,一个好的编译器都会生成高效的代码。


相反,根据其RAND_MAX,根据需要调用您的随机函数(1、2 或 3 次)。当RAND_MAX 是梅森数时,下面的效果很好。
Is there any way to compute the width of an integer type at compile-time?

#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12))
// Bit width of RAND_MAX, which is at least 15
#define RAND_MAX_BITS IMAX_BITS(RAND_MAX)

_Static_assert(((RAND_MAX + 1u) & RAND_MAX) == 0, "RAND_MAX is not a Mersenne number");

uint32_t rand32(void) {
  uint32_t r = rand();
  #if RAND_MAX_BITS < 32
    r = (r << RAND_MAX_BITS) | rand();
  #endif
  #if RAND_MAX_BITS*2 < 32
    r = (r << RAND_MAX_BITS) | rand();
  #endif
  return r;
}

(位移)增加代码复杂度?

没有。

还有其他原因使用 rand() 的移位实现吗?

OP 的代码并不统一,因为它通常偏爱一位,其潜在的或位可能超过 15 位。

我一直在试图找出使用这个实现的原因......

不要使用它。

【讨论】:

    【解决方案3】:

    或者,您可以只使用一个非常快速的随机数生成器。 小心不要看到没有太多零字节的值。

    uint64_t
    xorshift128plus(uint64_t seed[2])
    {
        uint64_t x = seed[0];
        uint64_t y = seed[1];
        seed[0] = y;
        x ^= x << 23;
        seed[1] = x ^ y ^ (x >> 17) ^ (y >> 26);
        return s[1] + y;
    }
    

    将结果转换为浮点数或仅取模您的最大 int 值...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-24
      • 1970-01-01
      • 2019-03-04
      • 1970-01-01
      • 2020-01-20
      • 2011-09-06
      • 1970-01-01
      • 2021-07-23
      相关资源
      最近更新 更多