【问题标题】:How to generate random 64-bit unsigned integer in C如何在 C 中生成随机 64 位无符号整数
【发布时间】:2016-01-05 17:37:39
【问题描述】:

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

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

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

【问题讨论】:

  • 不要连接rand(),因为你会有各种各样的自相关效应,并且分布不会是均匀的。看看这些:math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/…
  • I also want outcomes like 5, 11, 33387 => 1000000000000000000 和 99999999999999999999 之间的数字比 0 和 1000000000000000000 之间的数字多 10 倍......所以不要指望很快就会看到像 5 这样的数字
  • 您似乎对基数为 10 的数字 (0...9) 和位(基数为 2 的数字)感到困惑。将这些分开思考,以便更好地理解。
  • 你得到5的概率和你得到3771778641802345472的概率一样,等于1/2^64,一个非常小的数字。所以简单地连接位就可以了,除非你有更严格的要求

标签: c random unsigned-integer


【解决方案1】:

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

@Thomas comment。如果生成随机数的时间足够长,代码将创建 5、11 和 33387 之类的数字。如果代码每秒生成 1,000,000,000 个数字,则可能需要一年时间,因为在所有 64 位数字中,小于 100,000 的非常小的数字非常罕见。


rand() simple 返回随机位。一个简单的方法一次拉 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;
}

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

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


[编辑 2020]

Hallvard B. Furuseth 提供了一种很好的方法来确定RAND_MAX 中的位数(当它是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;
}

【讨论】:

  • 这个答案就像一首诗。我的意思是解释。我完全理解与我的问题相关的所有内容。
【解决方案2】:

如果您愿意使用重复的伪随机序列,并且可以处理一堆永远不会发生的值(比如偶数?...不要只使用低位),LCG 或 MCG 是简单的解决方案。 Wikipedia: Linear congruential generator 可以帮助您入门(还有几种类型,包括常用的Wikipedia: Mersenne Twister)。 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());
}

【讨论】:

  • 注意:ll 不需要在 2 个常量中。使用"%016" PRIx64 "\n" 比使用"%016p\n" 更好 - 确保与uint64_t 匹配的打印说明符。 (见&lt;inttypes.h&gt;
【解决方案3】:

如果您有 32 位或 16 位随机值 - 生成 2 个或 4 个随机值并将它们与&lt;&lt;| 组合成一个 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;
}

【讨论】:

  • 问题是 OP 的随机值只有 30 位
  • 不知道为什么这被否决了。如果您提前知道RAND_MAX 的值,uint64_t r = rand(); r = r&lt;&lt;30 | rand(); r = r&lt;&lt;30 | rand(); 是有道理的。希望我能提出几个答案。
  • @ESV 评论中有缺陷的答案// Assuming RAND_MAX is 2^31. 答案是有道理的,如果RAND_MAX is 2^30 - 1,正如OP 所说。 (i486 的 2 次方错误,1 次方)。 r = r&lt;&lt;30 ^ rand();^ vs |)可能会更好,因为这对RAND_MAX is 2^N - 1, N &gt;= 30有意义,而不仅仅是N==30
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

    我已经尝试过这段代码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

    【讨论】:

    • * 建立价值,就像a*b 一样,会破坏所产生价值的分布。 (long)a*babs(e) 都可能导致有符号整数溢出 - 这是 未定义的行为 (UB)。 abs() 返回一个 int。当int/long 范围不同时,使用abs(some_long) 会产生额外的问题。
    【解决方案6】:
    #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 位 unsigned int 的要求?
    • 好吧,我试过你的代码并打印出 1000 个结果。结果是这样的; 04951651604868241121, 00651604895168241121, 03943165433604438241, 00160434265465541121...所以我想我们不能用这种方法得到我需要的东西。
    • 你的意思是,你不想要前导零?
    • 前导零也是,但是,使用这种解决方案几乎不可能得到像 00000000000000345432 这样的结果
    • 我猜 rand() 不是那么灵活
    【解决方案7】:

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

    如果你不这样做,你可能会通过做同样的事情来逃脱,但你的伪随机生成器中可能存在一些伪影,可以为各种 hi-jinx 提供立足点。

    假设我们有一个开放的二进制文件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”? =)
    猜你喜欢
    • 1970-01-01
    • 2016-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-11
    • 1970-01-01
    • 2012-04-17
    相关资源
    最近更新 更多