在C语言中如何生成一个64位的无符号随机整数

17

我需要使用C生成64位无符号整数。即范围应为018446744073709551615RAND_MAX1073741823

我在链接中找到一些解决方案,可能是重复的,但答案大多将一些rand()结果连接在一起或进行一些递增算术运算。因此,结果始终为18位或20位数字。我也希望得到像51133387这样的结果,而不仅仅是3771778641802345472

顺便说一下,我真的没有太多关于C语言的经验,但任何方法、代码示例和想法都可能是有益的。


10
不要将rand()连接起来,否则会产生各种自相关效应,并且分布将不是均匀的。请参考:http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/c-lang.html - Bathsheba
15
我也希望看到像5、11、33387这样的结果。但是要知道,在1000000000000000000和9999999999999999999之间的数字数量比0到1000000000000000000之间的数字数量多10倍,因此不要指望很快就能看到像5这样的数字。 - Thomas Ayoub
1
你似乎对十进制数字(0...9)和比特(二进制数字)感到困惑。在思考时要将它们分开,以便更好地理解。 - hyde
1
你得到数字5的概率就像你得到3771778641802345472这个数字的概率一样,都等于1/2^64,是非常非常小的一个数。因此,简单地连接这些位就可以了,除非你有更严格的要求。 - phuclv
显示剩余5条评论
7个回答

9

关于“结果始终为18位或20位数字。”

请参见@Thomas的评论。如果您生成足够长时间的随机数,代码将创建像5、11和33387这样的数字。如果代码每秒生成1,000,000,000个数字,则可能需要一年的时间,因为在所有64位数字中,非常小的数字<100,000是非常罕见的。


rand() 简单返回随机位。一个简单的方法每次提取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,每次会生成至少15位二进制数。以下代码将调用rand()5次-有点浪费。相反,可以保存移位出来的位以供下一个随机数使用,但这会带来其他问题。请留待以后再处理。
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 提供了一种很好的方法来确定当RAND_MAXMersenne 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;
}

3
这个回答就像一首诗,我的意思是解释得很好。我完全理解了与我的问题相关的一切。 - Erdi İzgi

6

2
如果您有足够好的随机字节源(例如,在Linux机器上的/dev/random或/dev/urandom),您可以直接从该源消耗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;
}

如果你用“从函数调用获取字节”替换“从随机设备读取随机字节”,那么你只需要调整方法#2中的移位即可。
你更有可能得到一个“位数很多的数字”而不是一个“位数很少的数字”(在0到2 ** 64之间的所有数字中,大约95%拥有19个或更多十进制位数,所以你通常会得到这样的数字)。

“hi-jinx”是什么?=) - Daniel Stevens

2
如果您愿意使用重复的伪随机序列,并且可以处理一堆永远不会发生的值(例如偶数?...不要只使用低位),LCG或MCG是简单的解决方案。 维基百科:线性同余生成器 可以帮助您入门(还有几种更常用的类型,包括维基百科:梅森旋转演算法)。而此网站可以为模数和乘数生成一些质数。(警告:这个序列是可猜测的,因此它不安全)
#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());
}

注意:在2个常量中不需要使用 ll。最好使用 "%016 PRIx64 \ n" 而不是 "%016 p \ n" - 确保与 uint64_t 匹配的打印说明符。 (请参阅 <inttypes.h> - chux - Reinstate Monica

1

我已经尝试了这段代码在这里,看起来它在那里运行得很好。

#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;
}

我运行了几次迭代,得到了以下输出:
value 1869044101095834648 value 2104046041914393000
value 1587782446298476296 value 604955295827516250 value 41152208336759610 value 57792837533816000

3
* 来构建值,如 a*b,会破坏生成的值的分布。 (long)a*babs(e) 都可能导致有符号整数溢出 - 这是 未定义行为 (UB)。 abs() 返回一个 int。当 int/long 范围不同时,使用 abs(some_long) 会产生额外的问题。 - chux - Reinstate Monica

0
如果您有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;
}

1
问题在于 OP 的随机值只有 30 位。 - phuclv
1
不确定为什么这个被踩了。如果您事先知道RAND_MAX的值,uint64_t r = rand(); r = r<<30 | rand(); r = r<<30 | rand();是有意义的。但愿我能将其推到前几个答案。 - ESV
答案中的注释“//假设RAND_MAX是2^31。”是有缺陷的。如果RAND_MAX是2^30-1,那么答案就是有意义的,正如OP所说的那样(i486的2的幂次方错误,差一)。如果使用r = r<<30 ^ rand();^而不是|),那么它甚至可以更好,因为这对于RAND_MAX是2^N-1,N>=30,而不仅仅是N==30是有意义的。 - chux - Reinstate Monica

-1
#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] = '\0';

        //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] = '\0';

    char *ptr ;

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

    return ret;
}

这如何满足一个64位无符号随机整数的要求? - Paul R
好的,我尝试了你的代码并打印出了1000个结果。结果是这样的:04951651604868241121、00651604895168241121、03943165433604438241、00160434265465541121...所以我想我们不能用这种方法得到我需要的东西。 - Erdi İzgi
你的意思是,你不想要前导零吗? - machine_1
前导零也是问题,但使用这个解决方案几乎不可能得到像00000000000000345432这样的结果。 - Erdi İzgi
我猜测 rand() 不是那么灵活的。 - machine_1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接