如何在 C 中生成随机 64 位无符号整数

How to generate random 64-bit unsigned integer in C

我需要使用 C 生成随机 64 位无符号整数。我的意思是,范围应该是 018446744073709551615RAND_MAX1073741823

我在链接中找到了一些可能重复的解决方案,但答案主要是连接一些 rand() 结果或进行一些增量算术运算。所以结果总是 18 位或 20 位。我还想要像 51133387 这样的结果,而不仅仅是 3771778641802345472.

顺便说一下,我对 C 的经验确实不多,但任何方法、代码示例和想法都可能是有益的。

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

unsigned long long int randomize(unsigned long long int uint_64);

int main(void)
{
    srand(time(0));

    unsigned long long int random_number = randomize(18446744073709551615);

    printf("%llu\n",random_number);

    random_number = randomize(123);

    printf("%llu\n",random_number);

    return 0;

}

unsigned long long int randomize(unsigned long long int uint_64)
{
    char buffer[100] , data[100] , tmp[2];

    //convert llu to string,store in buffer
    sprintf(buffer, "%llu", uint_64);

    //store buffer length
    size_t len = strlen(buffer);

    //x : store converted char to int, rand_num : random number , index of data array
    int x , rand_num , index = 0;

    //condition that prevents the program from generating number that is bigger input value
    bool Condition = 0;

    //iterate over buffer array
    for( int n = 0 ; n < len ; n++ )
    {
        //store the first character of buffer
        tmp[0] = buffer[n];
        tmp[1] = '[=10=]';

        //convert it to integer,store in x
        x = atoi(tmp);


        if( n == 0 )
        {
            //if first iteration,rand_num must be less than or equal to x
            rand_num = rand() % ( x + 1 );

            //if generated random number does not equal to x,condition is true
            if( rand_num != x )
                Condition = 1;

            //convert character that corrosponds to integer to integer and store it in data array;increment index
            data[index] = rand_num + '0';
            index++;
        }
        //if not first iteration,do the following
        else
        {
            if( Condition )
            {
                rand_num = rand() % ( 10 );

                data[index] = rand_num + '0';

                index++;
            }
            else
            {
                rand_num = rand() % ( x + 1 );

                if( rand_num != x )
                    Condition = 1;

                data[index] = rand_num + '0';

                index++;
            }
        }
    }

    data[index] = '[=10=]';

    char *ptr ;

    //convert the data array to unsigned long long int
    unsigned long long int ret = _strtoui64(data,&ptr,10);

    return ret;
}

如果你有足够好的随机字节源(比如 /dev/random 或 /dev/urandom 在 linux 机器上),你可以简单地从该源消耗 8 个字节并将它们连接起来。如果它们是独立的并且呈线性分布,那么你就成功了。

如果你不这样做,你可以通过做同样的事情来逃脱,但你的伪随机生成器中可能会有一些人工制品,这些人工制品为各种喜恶作剧提供了立足点。

示例代码假设我们有一个开放的二进制文件 FILE *source:

/* Implementation #1, slightly more elegant than looping yourself */
uint64_t 64bitrandom() 
{
  uint64_t rv;
  size_t count;

  do {
   count = fread(&rv, sizeof(rv), 1, source);
  } while (count != 1);
  return rv;
}

/* Implementation #2 */
uint64_t 64bitrandom()
{
  uint64_t rv = 0;
  int c;

  for (i=0; i < sizeof(rv); i++) {
     do {
       c = fgetc(source)
     } while (c < 0);
     rv = (rv << 8) | (c & 0xff);
  }
  return rv;
}

如果您将 "read random bytes from a randomness device" 替换为 "get bytes from a function call",您所要做的就是调整方法 #2 中的班次。

与 "small number of digits" 相比,您获得 "number with many digits" 的可能性要大得多(在 0 到 2 ** 64 之间的所有数字中,大约 95% 的小数位数为 19 位或更多,所以这真的是你最能得到的。

如果您有 32 位或 16 位随机值 - 生成 2 或 4 个随机数并将它们与 <<|.

组合成一个 64 位
uint64_t rand_uint64(void) {
    // Assuming RAND_MAX is 2^31.
    uint64_t r = rand();
    r = r<<30 | rand();
    r = r<<30 | rand();
    return r;
}

关于“所以结果总是 18 位或 20 位。”

参见 。如果您生成的随机数足够长,代码将创建类似 5、11 和 33387 的随机数。如果代码生成 1,000,000,000 numbers/second,则可能需要一年时间,因为在所有 64 位数字中,小于 100,000 的非常小的数字非常罕见。


rand() 简单 returns 随机位。一个简单的方法一次拉1位

uint64_t rand_uint64_slow(void) {
  uint64_t r = 0;
  for (int i=0; i<64; i++) {
    r = r*2 + rand()%2;
  }
  return r;
}

假设 RAND_MAX 是 2 - 1 的某个幂,如 OP 的情况 1073741823 == 0x3FFFFFFF,利用 30 至少 15 位每次生成。以下代码将调用 rand() 5 3 次 - 有点浪费。相反,可以为下一个随机数保存移出的位,但这会带来其他问题。改天再说。

uint64_t rand_uint64(void) {
  uint64_t r = 0;
  for (int i=0; i<64; i += 15 /*30*/) {
    r = r*((uint64_t)RAND_MAX + 1) + rand();
  }
  return r;
}

便携式循环计数方法避免了 15 /*30*/ - 但请参阅下面的 2020 年编辑

#if RAND_MAX/256 >= 0xFFFFFFFFFFFFFF
  #define LOOP_COUNT 1
#elif RAND_MAX/256 >= 0xFFFFFF
  #define LOOP_COUNT 2
#elif RAND_MAX/256 >= 0x3FFFF
  #define LOOP_COUNT 3
#elif RAND_MAX/256 >= 0x1FF
  #define LOOP_COUNT 4
#else
  #define LOOP_COUNT 5
#endif

uint64_t rand_uint64(void) {
  uint64_t r = 0;
  for (int i=LOOP_COUNT; i > 0; i--) {
    r = r*(RAND_MAX + (uint64_t)1) + rand();
  }
  return r;
}

评论的自相关效应是由弱 rand() 引起的。 C 没有指定特定的随机数生成方法。以上依赖于 rand() - 或使用的任何基本随机函数 - 是好的。

如果 rand() 低于标准,则代码应使用其他生成器。然而,人们仍然可以使用这种方法来建立更大的随机数。


[编辑 2020]

Hallvard B. Furuseth provides as nice way to determine the number of bits in RAND_MAX when it is a Mersenne Number - 2 减 1 的幂。

#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12))
#define RAND_MAX_WIDTH IMAX_BITS(RAND_MAX)
_Static_assert((RAND_MAX & (RAND_MAX + 1u)) == 0, "RAND_MAX not a Mersenne number");

uint64_t rand64(void) {
  uint64_t r = 0;
  for (int i = 0; i < 64; i += RAND_MAX_WIDTH) {
    r <<= RAND_MAX_WIDTH;
    r ^= (unsigned) rand();
  }
  return r;
}

我试过这段代码here,它似乎在那里工作得很好。

#include <time.h>
#include <stdlib.h>
#include <math.h>

int main(){
  srand(time(NULL));
  int a = rand();
  int b = rand();
  int c = rand();
  int d = rand();
  long e = (long)a*b;
  e = abs(e);
  long f = (long)c*d;
  f = abs(f);

  long long answer = (long long)e*f;

  printf("value %lld",answer);
  return 0;
}

我 运行 几次迭代,我得到以下输出:

值 1869044101095834648
值 2104046041914393000

值 1587782446298476296
值 604955295827516250
值 41152208336759610
值 57792837533816000

如果您不需要加密安全的伪随机数,我建议您使用 MT19937-64。它是 Mersenne Twister PRNG.

的 64 位版本

请不要合并 rand() 输出,也不要建立在其他技巧之上。使用现有实施:

http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt64.html

如果你愿意使用重复的伪随机序列并且你可以处理一堆永远不会发生的值(比如偶数?......不要只使用低位),LCG 或MCG 是简单的解决方案。 Wikipedia: Linear congruential generator can get you started (there are several more types including the commonly used Wikipedia: Mersenne Twister). And this site 可以为下面的模数和乘数生成一对素数。 (注意:这个序列是可以猜到的,因此安全)

#include <stdio.h>
#include <stdint.h>

uint64_t
mcg64(void)
{
    static uint64_t i = 1;
    return (i = (164603309694725029ull * i) % 14738995463583502973ull);
}

int
main(int ac, char * av[])
{
    for (int i = 0; i < 10; i++)
        printf("%016p\n", mcg64());
}