在Objective-C中生成随机数

754

我主要是Java开发者,希望找到一种方法来生成0到74之间的伪随机数。在Java中,我会使用以下方法:

Random.nextInt(74)

我对种子或真正的随机性的讨论不感兴趣,只想知道如何在Objective-C中完成同样的任务。我已经在Google上搜索了很多,但似乎有很多不同和相互矛盾的信息。

13个回答

1036

你应该使用arc4random_uniform()函数。它使用比rand更优秀的算法。你甚至不需要设置一个种子。

#include <stdlib.h>
// ...
// ...
int r = arc4random_uniform(74);

arc4random命令说明文档:

NAME
     arc4random, arc4random_stir, arc4random_addrandom -- arc4 random number generator

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <stdlib.h>

     u_int32_t
     arc4random(void);

     void
     arc4random_stir(void);

     void
     arc4random_addrandom(unsigned char *dat, int datlen);

DESCRIPTION
     The arc4random() function uses the key stream generator employed by the arc4 cipher, which uses 8*8 8
     bit S-Boxes.  The S-Boxes can be in about (2**1700) states.  The arc4random() function returns pseudo-
     random numbers in the range of 0 to (2**32)-1, and therefore has twice the range of rand(3) and
     random(3).

     The arc4random_stir() function reads data from /dev/urandom and uses it to permute the S-Boxes via
     arc4random_addrandom().

     There is no need to call arc4random_stir() before using arc4random(), since arc4random() automatically
     initializes itself.

EXAMPLES
     The following produces a drop-in replacement for the traditional rand() and random() functions using
     arc4random():

           #define foo4random() (arc4random() % ((unsigned)RAND_MAX + 1))

97
按照@yood的说明,使用arc4random_uniform(x)。它也在stdlib.h中(在OS X 10.7和iOS 4.3之后),可以给出更均匀分布的随机数。用法为int r = arc4random_uniform(74); - LavaSlider
4
注意:如果你选择了一个不好的范围,从arc4random中得到的分布可能非常差。我之前没有意识到这种期望以2为幂次方的情况。+1采用@yood的版本——对于更大的数字(例如400的范围),这将产生显著的差异。 - Adam
arc4random() 如何“知道”将其返回值限制在模运算符另一侧的整数以下,如果它没有被传递到函数中呢? - codecowboy
4
它并不会返回你指定的数字。它总是返回一个范围在[0,(2^32)-1]之间的整数。取模运算是限制范围上限的方式。 - mota
1
这不会给出一个在0到73之间的随机数吗? - Tom Howard
显示剩余2条评论

430

使用arc4random_uniform(upper_bound)函数生成一个指定范围内的随机数。以下代码将生成一个介于0和73之间(含两端)的数字。

arc4random_uniform(74)

arc4random_uniform(upper_bound) 避免了模运算偏差,正如手册中所述:

arc4random_uniform() 返回小于上限值的均匀分布的随机数。 推荐使用 arc4random_uniform() 而不是类似“arc4random()%upper_bound”这样的构造,因为它避免了在上限不是2的幂时出现的模运算偏差


32
请注意,arc4random_uniform()方法需要iOS 4.3或更高版本。如果您需要支持旧设备,请添加以下检查: #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_3 若未通过此检查,请使用其他解决方案。 - Ron
6
arc4random_uniform()函数需要10.7或更高版本。在10.6中使用它会导致应用程序崩溃。请注意更新您的操作系统版本。 - Tibidabo
@Tibidabo,你的评论非常错误和误导人。我刚刚在iOS 10.3上尝试使用arc4random_uniform(),并没有任何问题。它不需要10.7或更高版本。 - Fourth
1
@Fourth,iOS 10.7 这个版本并不存在,应该是 macOS 10.7。我写下这条评论已经超过5年了,那时候最高版本只有 iOS 5。 - Tibidabo

65

和 C 一样,你会这样做

#include <time.h>
#include <stdlib.h>
...
srand(time(NULL));
int r = rand() % 74;

(假设您的意思是包括0但不包括74,这正是您的Java示例所做的)

编辑:请随意将random()arc4random()替换为rand()(正如其他人指出的那样,它非常糟糕)。


45
你需要为随机数生成器设置种子,否则每次执行时会获得相同的数字模式。 - Alex Reynolds
3
你可以将想要起始的数字加到结果中。 - Florin
2
我一直得到数字9。我想说这相当随机;D - alexyorke
@alexy13,它是“随机”的,因为在运行之前你不知道它是9。哈哈 - Franklin Yu
除非rand()给出的最大数字是-1(mod 74),否则结果不会均匀分布。虽然这可能并不重要,但为了避免这种情况,如果rand()恰好落在那个狭窄的不完整片段中,你应该重新生成一个随机数,如果你知道我的意思的话。 - Fab
显示剩余2条评论

51

我认为我可以添加一个我在许多项目中使用的方法。

- (NSInteger)randomValueBetween:(NSInteger)min and:(NSInteger)max {
    return (NSInteger)(min + arc4random_uniform(max - min + 1));
}

如果我在许多文件中使用它,我通常将宏声明为

#define RAND_FROM_TO(min, max) (min + arc4random_uniform(max - min + 1))

例如。

NSInteger myInteger = RAND_FROM_TO(0, 74) // 0, 1, 2,..., 73, 74

注意:仅适用于 iOS 4.3 / OS X v10.7(狮子)及更高版本


加法是可交换的,即使在使用二进制补码的固定整数上也是如此。因此,max-min+1与max+1-min以及1+max-min完全相同。 - Michael Morris

44

这将给你一个在0到47之间的浮点数

float low_bound = 0;      
float high_bound = 47;
float rndValue = (((float)arc4random()/0x100000000)*(high_bound-low_bound)+low_bound);
或者简单地说
float rndValue = (((float)arc4random()/0x100000000)*47);

下限和上限都可以是负数。下面的示例代码会给你一个介于-35.76和+12.09之间的随机数。

float low_bound = -35.76;      
float high_bound = 12.09;
float rndValue = (((float)arc4random()/0x100000000)*(high_bound-low_bound)+low_bound);

将结果转换为一个更接近的整数值:

int intRndValue = (int)(rndValue + 0.5);

这很糟糕。为什么你要使用float,而不是double?如果你的浮点数值在0到47之间,那么(int)(rndValue+0.5)将只把0.0到0.5的值转换为0,但是从0.5到1.5的值将被转换为1等等。所以数字0和47将只有其他数字的一半出现的频率。 - gnasher729
@gnasher729 抱歉,我不明白你的意思。显然,如果你需要双精度,你可以轻松地用“double”替换“float”。 - Tibidabo
我认为这不是什么大问题。如果你只需要整数值,那么你可以使用arc4random()/0x100000000)*(high_bound-low_bound)+low_bound,通过删除浮点/双精度转换来实现。 - Tibidabo
当您使用这种方式将整数转换为浮点数时,会产生偏差。请改用 drand48 生成浮点数。 - Franklin Yu

37

根据rand(3)的手册页面,rand系列函数已经被random(3)取代。这是因为rand()的低12位会形成循环模式。要获取随机数,只需使用无符号种子调用srandom()生成器,并调用random()。因此,上面代码的等价于:

#import <stdlib.h>
#import <time.h>

srandom(time(NULL));
random() % 74;

在程序中,您只需要调用srandom()一次,除非您想更改种子。虽然您说您不想讨论真正随机的值,但是rand()是一个相当糟糕的随机数生成器,而random()仍然受到模数偏差的影响,因为它将生成介于0和RAND_MAX之间的数字。例如,如果RAND_MAX为3,并且您想要一个介于0和2之间的随机数,则获得0的可能性是获得1或2的两倍。


7
你可以直接调用srandomdev()而不是将时间传递给srandom();这样做同样简单,并且数学上更好。 - benzado

31

最好使用arc4random_uniform。然而,在iOS 4.3以下版本不可用。幸运的是,iOS会在运行时绑定此符号,而不是在编译时(因此不要使用#if预处理指令来检查它是否可用)。

确定是否可用arc4random_uniform的最佳方法是像这样进行:

#include <stdlib.h>

int r = 0;
if (arc4random_uniform != NULL)
    r = arc4random_uniform (74);
else
    r = (arc4random() % 74);

9
这个问题涉及 Objective-C,它使用后期绑定。与在编译/链接时绑定的 C 不同,Objective-C 在运行时绑定符号,无法绑定的符号将被设置为 NULL。虽然您对于这不是有效的 C 是正确的,但它确实是有效的 Objective-C。我在我的 iPhone 应用程序中使用了这个确切的代码。[附言:请纠正您的投票]。 - AW101
虽然 Objective-C 对于 Objc 方法使用了后期绑定,但对于 C 函数则不是这样。如果该函数在运行时不存在,那么这段代码肯定会崩溃。 - Richard J. Ross III
8
根据苹果公司的说法:"...链接器将不可用函数的地址设置为空(NULL)...",请参见3.2清单: http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/cross_development/Using/using.html#//apple_ref/doc/uid/20002000-SW6。这意味着它必须进行弱链接,但不会崩溃。 - AW101
1
检查函数地址是否为NULL是在所有版本的C、C++和Objective-C中都使用的一种方法,无论是在MacOS X还是iOS上。 - gnasher729

14

我编写了自己的随机数工具类,只是为了拥有一个更像Java中Math.random()函数的功能。它只有两个函数,并且全部用C语言编写。

头文件:

//Random.h
void initRandomSeed(long firstSeed);
float nextRandomFloat();

实现文件:

//Random.m
static unsigned long seed;

void initRandomSeed(long firstSeed)
{ 
    seed = firstSeed;
}

float nextRandomFloat()
{
    return (((seed= 1664525*seed + 1013904223)>>16) / (float)0x10000);
}

这是一种生成伪随机数的经典方法。在我的应用程序代理中,我调用:

#import "Random.h"

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    initRandomSeed( (long) [[NSDate date] timeIntervalSince1970] );
    //Do other initialization junk.
}

然后我之后只是说:

float myRandomNumber = nextRandomFloat() * 74;

请注意,此方法返回介于 0.0f(包括)和 1.0f(不包括)之间的随机数字。


3
  1. 随机生成的随机数函数通常不太随机。
  2. 在64位处理器上完全失效。
  3. 使用1970年起的秒数作为随机种子会使得生成的数字具有可预测性。
- gnasher729

7

已经有很好的、简明扼要的回答,但问题是要求生成0到74之间的随机数。可使用:

arc4random_uniform(75)


4
自iOS 9和OS X 10.11起,您可以使用新的GameplayKit类以多种方式生成随机数。您有四种来源类型可供选择:通用随机源(未命名,由系统决定其功能),线性同余、ARC4和Mersenne Twister。这些可以生成随机整数、浮点数和布尔值。在最简单的级别上,您可以像这样从系统内置的随机源生成随机数:
NSInteger rand = [[GKRandomSource sharedRandom] nextInt];

这将生成一个介于-2,147,483,648和2,147,483,647之间的数字。如果您想要一个介于0和上限(不包括)之间的数字,您可以使用以下代码:

NSInteger rand6 = [[GKRandomSource sharedRandom] nextIntWithUpperBound:6];

GameplayKit中有一些方便的构造函数可用于处理骰子。例如,您可以像这样掷一个六面骰子:

GKRandomDistribution *d6 = [GKRandomDistribution d6];
[d6 nextInt];

而且你可以使用GKShuffledDistribution等工具来调整随机分布。


Mersenne是最快的,也是最适合像游戏开发这样的领域,因为生成的随机数的质量通常不太重要。 - Robert Wasmann

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