【问题标题】:Strange behaviour of rand() in XcodeXcode 中 rand() 的奇怪行为
【发布时间】:2017-10-22 17:26:13
【问题描述】:

使用 Apple Xcode 时,我在下面包含的 C 程序将始终为 y 返回 5,但 z 将返回一个随机数,即使它们具有相同的表达式。你能找出这种行为的原因吗?

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

int main()
{
    int x,y,z;
    srand((unsigned int)time(NULL));
    x = ((unsigned int)time(NULL));
    y=((double)rand()/(double)(RAND_MAX)*10);
    z=((double)rand()/(double)(RAND_MAX)*10);
    printf("Time %d\n", x);
    printf("\n");
    printf("Number y %d\n", y);
    printf("\n");
    printf("Number z %d\n", z);
    printf("\n");
    return 0;
}

【问题讨论】:

  • 这是因为来自time(种子值)的值增长缓慢,并且除以一个很大的数,所以直到经过更长的时间,商才会有任何不同。第二次调用rand 产生的值与种子完全不同。
  • 这似乎仅限于 Xcode 和 Mac OS X,因为我在 Linux 中运行时没有这个问题。是这样吗?在 Xcode 中,如果我使用 srandom 和 random() 它在两种情况下的工作方式相同。 random() 和 srandom 与 srand 和 rand() 有什么不同吗?
  • 我从 MSVC 得到了类似的结果,但 7 而不是 5。我预计7 最终会上升到8
  • 我在 OS X / XCode 上也收到了5
  • 你拨打time()两次;它有可能在两次调用之间发生变化。如果x 打算保存用作种子的值,最好分配x,然后使用x 作为种子。

标签: c xcode random srand


【解决方案1】:

rand-31

这是对您的程序的改编,它显示了正在发生的事情的更多细节:

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

int main(void)
{
    int x = ((unsigned int)time(NULL));
    srand((unsigned int)x);
    int r1 = rand();
    int y  = ((double)r1/(double)(RAND_MAX)*10);
    int r2 = rand();
    int z  = ((double)r2/(double)(RAND_MAX)*10);
    printf("Time %d\n", x);
    printf("Random 1: %d\n", r1);
    printf("Number y: %d\n", y);
    printf("Random 2: %d\n", r2);
    printf("Number z: %d\n", z);
    return 0;
}

它独立于计算捕获两个rand() 调用的结果,以便可以打印它们。它还确保传递给time() 的值与打印的值相同,尽管它们不同的可能性非常小。

当我运行它几次时,我得到了输出:

Time 1508694677
Random 1: 1292016210
Number y: 6
Random 2: 1709286653
Number z: 7

Time 1508694685
Random 1: 1292150666
Number y: 6
Random 2: 1821604998
Number z: 8

Time 1508694701
Random 1: 1292419578
Number y: 6
Random 2: 2046241688
Number z: 9

Time 1508694841
Random 1: 1294772558
Number y: 6
Random 2: 790587255
Number z: 3

如您所见,rand() 返回的值是不同的,但差别不大,因此计算的值并没有那么不同。

rand-23

一个简单的升级选项是使用drand48() 函数,如下所示:

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

int main(void)
{
    int x = ((unsigned int)time(NULL));
    srand48((unsigned int)x);
    int r1 = lrand48();
    int y  = ((double)r1/(double)(RAND_MAX)*10);
    int r2 = lrand48();
    int z  = ((double)r2/(double)(RAND_MAX)*10);
    printf("RAND_MAX: %d\n", RAND_MAX);
    printf("Time:     %d\n", x);
    printf("Random 1: %d\n", r1);
    printf("Number y: %d\n", y);
    printf("Random 2: %d\n", r2);
    printf("Number z: %d\n", z);
    return 0;
}

代码打印RAND_MAX主要是为了表明它与lrand48()返回的值范围兼容,据记载它返回的值在0..2范围内31- 1.

这会产生不同且可以说是更好的结果:

RAND_MAX: 2147483647
Time:     1508695069
Random 1: 705270938
Number y: 3
Random 2: 1758232243
Number z: 8

RAND_MAX: 2147483647
Time:     1508695074
Random 1: 1465504939
Number y: 6
Random 2: 733780153
Number z: 3

RAND_MAX: 2147483647
Time:     1508695080
Random 1: 1948289010
Number y: 9
Random 2: 1222424564
Number z: 5

rand-13

或者,您可以按照 macOS 文档中的说明进行操作,然后切换到 arc4random()。该链接指向“旧版”XCode 5 文档,但功能最近没有改变。

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

int main(void)
{
    int x = ((unsigned int)time(NULL));
    int r1 = arc4random_uniform(INT_MAX);
    int y  = ((double)r1/(double)(RAND_MAX)*10);
    int r2 = arc4random_uniform(INT_MAX);
    int z  = ((double)r2/(double)(RAND_MAX)*10);
    printf("INT_MAX:  %d\n", INT_MAX);
    printf("RAND_MAX: %d\n", RAND_MAX);
    printf("Time:     %d\n", x);
    printf("Random 1: %d\n", r1);
    printf("Number y: %d\n", y);
    printf("Random 2: %d\n", r2);
    printf("Number z: %d\n", z);
    return 0;
}

这使用arc4random_uniform() 生成一个0..INT_MAX 范围内的值,与生成的其他函数的范围相同。没有 srand()arc4random() 系列函数的类似物。

示例运行:

INT_MAX:  2147483647
RAND_MAX: 2147483647
Time:     1508695894
Random 1: 938614006
Number y: 4
Random 2: 851262647
Number z: 3

INT_MAX:  2147483647
RAND_MAX: 2147483647
Time:     1508695900
Random 1: 1332829945
Number y: 6
Random 2: 386007903
Number z: 1

INT_MAX:  2147483647
RAND_MAX: 2147483647
Time:     1508695913
Random 1: 1953583540
Number y: 9
Random 2: 1273643162
Number z: 5

rand-83

请注意,如果您想要一个介于 0 和 10 之间的随机整数,那么您应该直接使用 arc4random_uniform(10) 而不要进行计算。

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

int main(void)
{
    int x = ((unsigned int)time(NULL));
    int y = arc4random_uniform(10);
    int z = arc4random_uniform(10);
    printf("Time:     %d\n", x);
    printf("Number y: %d\n", y);
    printf("Number z: %d\n", z);
    return 0;
}

示例输出:

Time:     1508696162
Number y: 5
Number z: 3

Time:     1508696163
Number y: 7
Number z: 5

Time:     1508696165
Number y: 3
Number z: 8

arc4random() et al 的另一个优势体现在我设法在一秒钟内运行该程序 3 次。对于其他生成器,由于使用相同的种子,它们会产生相同的结果。

$ rand-83;rand-83;rand-83
Time:     1508696213
Number y: 4
Number z: 0
Time:     1508696213
Number y: 0
Number z: 1
Time:     1508696213
Number y: 9
Number z: 6
$

$ rand-31;rand-31;rand-31
Time 1508696334
Random 1: 1319865409
Number y: 6
Random 2: 1619339200
Number z: 7
Time 1508696334
Random 1: 1319865409
Number y: 6
Random 2: 1619339200
Number z: 7
Time 1508696334
Random 1: 1319865409
Number y: 6
Random 2: 1619339200
Number z: 7
$

JFTR:在运行 macOS High Sierra 10.13 的 MacBook Pro 上编译的代码。我使用 GCC 7.2.0(而不是 XCode 9)来编译,但是系统库——它是这里的关键库。

【讨论】:

  • 我看到这是与 Mac GCC 相关的问题(我也在使用配备 High Sierra 10.13 的 MacBook Pro,但使用 Xcode 9.1 beta),问题是我必须开发到最广泛的兼容性可能,因为我的老师使用的是 Linux 系统,他使用 rand() 和 srand 没有问题。
  • 无法保证您从rand() 函数中获得的随机数质量如何。从 rand() 函数返回的值范围有限保证。可移植代码不能期望使用rand() 函数在不同的机器上得到相同的结果。唯一的问题是期望值和/或用于得出 0..10(或 0..9)范围内的值的算法。还有其他更好的方法可以做到这一点。
  • 请注意Roland Illiganswer 问题Is this C implementation of the Fisher-Yates shuffle correct?,它显示了如何准确地生成0..N-1 范围内的数字而没有偏差。
【解决方案2】:

你刚刚学到了重要的一课:

请勿使用time(NULL) 为您的随机数生成器播种。

是的,我知道“每个人都这样做”。这并不能使它正确。 (事实上​​,“每个人”并没有这样做,尽管它在示例代码中很常见。在实际需要随机数用于生产目的的代码中,这是非常不常见的——甚至在不常见的情况下也不正确。)

事实是,“自纪元以来的秒数”是一个非常随机的数字。前六位需要一整年才能改变;几乎一天,只有低 16 位发生变化,而一秒钟——这足以启动数千个进程——它根本没有变化。因为如果你使用相同的种子保证你能够得到相同的随机序列,显然你需要更加努力地工作以确保你有一个更随机的种子。

那该怎么办?如今,大多数操作系统都有某种机制来生成真正的随机数(或在物理定律允许的范围内尽可能接近随机的数字)。在许多 Unix 和类 Unix 系统上——包括 Mac OS X 和 Linux——你可以通过从 /dev/random 读取四个字节来获得一个非常好的无符号 32 位随机数。这对于您需要的每个随机数来说都太昂贵了,但它作为种子非常好:

uint32_t seed = 0;
FILE* rf = fopen("/dev/random", "r");
if (rf != NULL) {
  if (fread(seed, sizeof seed, 1, rf) != 1) seed = 0;
  fclose(rf);
}
if (!seed) { /* Fallback to some other mechanism */ }
srand(seed);

独立于播种,rand 通常不是一个很好的随机数生成器,特别是在 RAND_MAX 是 32767 的实现中。但是,无论多么好的随机数生成器都无法弥补随机数生成器的不足。一个很好的播种策略。

(有些库会自动播种,但手动播种是界面的一部分是有充分理由的:您可能需要重新运行相同的随机数序列。例如,如果您使用 PRNG 生成随机测试数据并且您的测试程序的某些运行触发了错误,您将需要使用相同的随机数序列重新运行程序以验证错误是否已修复。一种简单的策略是使用命令行标志来提供显式随机数种子,并确保测试工具始终打印产生结果的随机数种子。)

【讨论】:

    【解决方案3】:

    从您的程序中删除(不需要的)混乱后,以下代码:

    1. 干净编译
    2. 消除不需要的局部变量
    3. 执行所需的功能。
    4. 清楚地揭示了time()rand() 函数实际发生的情况。
    5. 记录包含每个头文件的原因

    现在建议的代码:

    #include <stdio.h>  // printf()
    #include <time.h>   // time()
    #include <stdlib.h> // srand(), rand()
    
    int main( void )
    {
        srand((unsigned int)time(NULL));
    
        printf("Time %u\n\n", (unsigned int)time(NULL) );
    
        printf("first call to rand:  %d\n\n", rand());
    
        printf("second call to rand: %d\n\n", rand());
    
        return 0;
    }
    

    建议代码的典型运行会产生类似于以下内容的输出:

    Time 1508783846
    
    first call to rand:  1113266715
    
    second call to rand: 810898123
    

    【讨论】:

      【解决方案4】:

      我实际上设法确定了问题,它与使用 Xcode 9.1 beta 有关,由于某种原因破坏了 srand 和 rand()。恢复到 Xcode 9.0.1 后,一切都恢复正常了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-04-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多