【问题标题】:Create a 128 byte random number创建一个 128 字节的随机数
【发布时间】:2018-11-21 23:28:24
【问题描述】:

如果rand() 函数创建了一个长度为 4 字节的随机数,并且我想创建一个长度为 1024 位(128 字节)的随机数,那么通过连接 @ 987654322@函数256次还是有替代方法?

#include <stdio.h>
#include <string.h>

int main(void) {
  const char data[128];
  memset(&data, 0x36, 128);

  printf("%s\n", data);
  puts("");
  printf("%d\n", sizeof(data)/sizeof(data[0]));
  puts("");

  int i = 0;
  unsigned long rez = 0;

  for(i = 0; i < 20; i++) {
      unsigned int num = rand();
      rez = rez + num;
      printf("%x\n", rez);
  }

  printf("%x\n", rez);
  return 0;
}

【问题讨论】:

  • 为什么是 256 次? 128 字节 / 4 字节 = 32
  • 这当然是 最简单 的方法,一旦你得到正确的数学,根据@Gerhardh - 你只需要调查分布是否是你想要的。如果它是一个简单的 PRNG(例如,如果它遵循t(n) = t(n-1) * m + c modulo x 方法),您可能会发现一些 128 位值是不可能的。
  • 更一般地说,“连接”意味着您必须将当前累积的值反复乘以RAND_MAX + 1,然后将下一个rand()结果添加到产品中。重复直到达到所需的长度。
  • 请注意,rand() 生成介于 0 和 RAND_MAX 之间的随机数,通常小于 2³²-1(经典案例:MSVC++);因此,说它生成一个 4 字节的随机数是不正确的。
  • strcat 与此有关。实现这一目标的最佳方式取决于几个因素。是否支持 1024 位算术?你的rand() 真的会生成 4 个字节的随机位吗?同样,我在上面的评论中描述了“连接”结果的最通用方法:重复使用算术、乘法和加法。

标签: c random


【解决方案1】:

是通过将 rand() 函数连接 256 次来获得此功能的最简单方法,还是有其他方法?

每个rand() 返回一个[0...RAND_MAX] 范围内的值。 RAND_MAX 仅限于 32767 &lt;= RAND_MAX &lt;= INT_MAX

很常见RAND_MAXMersenne number 形式为 2n - 1。代码可以利用这个非常常见的实现依赖值。然后每个rand() 调用都提供RAND_MAX_BITS 而不是OP 建议的32 用于4 字节int@Matteo Italia

[见下方更新]

#include <stdlib.h>

#if RAND_MAX == 0x7FFF
#define RAND_MAX_BITS 15
#elif RAND_MAX == 0x7FFFFFFF
#define RAND_MAX_BITS 31
#else
#error TBD code
#endif

致电rand()⌈size * 8 / RAND_MAX_BITS⌉ 次。这减少了来自size 所需的rand() calls 的数量。

void rand_byte(uint8_t *dest, size_t size) {
  int r_queue = 0;
  int r_bit_count = 0;
  for (size_t i = 0; i < size; i++) {
    int r = 0;
    //printf("%3zu %2d %8x\n", i, r_bit_count, r_queue);
    if (r_bit_count < 8) {
      int need = 8 - r_bit_count;
      r = r_queue << need;
      r_queue = rand();
      r ^= r_queue;  // OK to flip bits already saved in `r`
      r_queue >>= need;
      r_bit_count = RAND_MAX_BITS - need;
    } else {
      r = r_queue;
      r_queue >>= 8;
      r_bit_count -= 8;
    }
    dest[i] = r;
  }
}

int main(void) {
  uint8_t buf[128];
  rand_byte(buf, sizeof buf);
  ...
  return 0;
}

如果您想要最简单但效率较低的代码,只需为@dbush 回答的每个字节调用rand()


[2021 年更新]

@Anonymous Question Guy 发布了一个漂亮的宏,它返回Mersenne number 的位宽,比上面的#if RAND_MAX == 0x7FFF 方法更普遍。

/* Number of bits in inttype_MAX, or in any (1<<b)-1 where 0 <= b < 3E+10 */
#define IMAX_BITS(m) ((m) /((m)%0x3fffffffL+1) /0x3fffffffL %0x3fffffffL *30 \
              + (m)%0x3fffffffL /((m)%31+1)/31%31*5 + 4-12/((m)%31+3))

_Static_assert((RAND_MAX & 1 && (RAND_MAX/2 + 1) & (RAND_MAX/2)) == 0, 
    "RAND_MAX is not a Mersenne number");
#define RAND_MAX_BITS IMAX_BITS(RAND_MAX)

【讨论】:

  • 酷。该文章和维基百科文章都提到“梅森数”通常仅用于指数为素数的数字。但它也经常以你的方式使用。
  • 顺便说一句,this question 可能与您的答案相关。 C标准只定义了rand()返回的可能值的范围;它并不坚持所有这些都可以生产。所以引用的 OS X 实现是一致的。
  • @rici 确实,rand()sqrt()pow() 和许多其他函数的 质量 并未由 C:caveat emptor 严格指定。
  • TBH 而不是浪费时间嗅探内置 rand() 的属性(通常低于标准)我只是嵌入了一个具有完整 32 位或 64 位输出的良好 XorShift 生成器,称之为4 到 2 次就满意了。
  • @MatteoItalia 是的,这个想法可以作为你的一个很好的答案。由于 OP 的问题是关于 rand() 的使用(有时代码要求限制选项) - 这个答案详细说明了如何最大限度地减少对 rand() 的调用。
【解决方案2】:

标准对Crand()的属性进行了模糊的规定;正如评论中所说,实际可用位的数量取决于实现,并且它们的质量在历史上一直受到低于标准的实现的困扰。此外,rand() 会影响程序的全局状态并且在许多实现上不是线程安全的。

鉴于有很好的、已知的和简单的 PRNG,例如来自 XorShift 系列的 PRNG,我将只使用其中一个。

#include <stdint.h> 
/* The state must be seeded so that it is not all zero */
uint64_t s[2];
uint64_t xorshift128plus(void) {
    uint64_t x = s[0];
    uint64_t const y = s[1];
    s[0] = y;
    x ^= x << 23;
    s[1] = x ^ y ^ (x >> 17) ^ (y >> 26);
    return s[1] + y;
}

void next128bits(unsigned char ch[16]) {
    uint64_t t = xorshift128plus();
    memcpy(ch, &t, sizeof(t));
    t = xorshift128plus();
    memcpy(ch + 8, &t, sizeof(t));
} 

【讨论】:

    【解决方案3】:

    在大多数 POSIX(类 Unix)系统上,您还可以从 /dev/urandom 中读取 128 个字节,您可以像以二进制模式打开常规文件一样打开它们 — 即使 POSIX 没有指定设备。

    【讨论】:

    【解决方案4】:

    C 标准规定 RAND_MAX 的最小值为 32767 (0x7fff),因此最好在该假设下工作。

    因为函数只会返回 15 个随机位,所以在一次调用中使用所有位将涉及一些位移和掩码,以便将结果放在正确的位置。最简单的方法是调用rand 128 次,获取每个结果的低位字节,并将其写入您的字节数组:

    unsigned char rand_val[128];
    
    for (int i=0; i<128; i++) {
        rand_val[i] = rand() & 0xff;
    }
    

    不要忘记在代码中在此之前的某个位置准确地调用srand一次

    使用您在评论中提到的strcat 将不起作用,因为此函数适用于以空字符结尾的字符串,并且包含 0 的字节是有效的随机数。

    如果您打算将这些随机值用于涉及密码学的任何事情,则最好使用安全的随机数生成器。如果您有可用的 OpenSSL,请为此目的使用 RAND_bytes

    unsigned char rand_val[128];
    RAND_bytes(rand_val, sizeof(rand_val));
    

    【讨论】:

    • 我使用了类似于您提供的第一个 sn-p 代码的循环,但有没有办法获取整个数组并将其保存到单个变量中?
    • @nice_remark 取决于变量的类型。
    猜你喜欢
    • 2013-05-22
    • 2011-08-06
    • 1970-01-01
    • 2013-05-05
    • 1970-01-01
    • 1970-01-01
    • 2022-09-27
    • 2017-03-18
    • 2012-07-20
    相关资源
    最近更新 更多