乱数生成関数の差異

2014. 06. 04
詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応」を読み始めて、乱数の生成にarc4random()関数を使用していたので、以前使用していたrand()random()関数との違いを調べてみました。


●宣言

rand()、random()、arc4random()、arc4random_uniform()関数は、いずれもstdlib.hで以下のように宣言されています。

int rand(void);

long random(void);

u_int32_t arc4random(void);

u_int32_t arc4random_uniform(u_int32_t /*upper_bound*/)
                __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);

arc4random()とarc4random_uniform()関数は、戻り値がu_int32_t(32bit unsigned int)なので、型変換する際には注意が必要です。
(「Qiita/Objective-C/arc4randomの罠」や「だいきちのブログ/気ままにObjective-C/arc4random()でマイナス値が出ることについての考察」参照)


●各関数の差異

乱数発生の内部処理に関しては「和田維作のホームページ/良い乱数・悪い乱数」などを参照していただくとして、実際に利用する立場としてどの関数を使うべきかを考えます。

rand()関数は「Mac OS X Manual Page/srand(3)」で"悪い乱数ジェネレータ(bad random number generator)"であるとしてrandom()関数を使用するように求めています。

random()関数は「Mac OS X Manual Page/random(3)」で"良い乱数ジェネレータ(better random number generator)"としていますが、rand()と同様にそのままでは再現性を有するという欠点があり、暗号化品質を求めるならばarc4random()を使用するよう推奨されています。

ただしrand()はsrand()、random()はsrandom()関数でシード値を与えることで再現性を回避することはできます。

arc4random()関数は、rand()やrandom()の2倍の範囲を持つARC4(「Wikipedia/RC4」参照)由来の乱数ジェネレータですが、上限が2のべき乗でない場合にモジュロバイアスが発生するという欠点があります。

例えば0から(2のべき乗でない)9までの乱数を求めようとすると、出現する値が偏在してしまうという問題があります。
(「Learn iPhone, iOS, Objective-c, cocos2dx, Unity and ....?/Objective-C - arc4random()が生成する乱数の偏りについて」参照)

arc4random_uniform()関数は、arc4random()のモジュロバイアス問題を解決しているので推奨されています。

またarc4random_uniform()はシード値を与える必要も無いので、(Mac OS X 10.7またはiOS 4.3以降であれば)通常はarc4random_uniform()を使うと良いでしょう。


●各関数の正規分布

実際に各関数を実行した結果が気になるところですので試してみます。

テスト環境)
・iMac 21.5" Late 2013(OS X 10.9.3)
・Xcode 5.1.1
・iOS シミュレータ ver.7.1(iPhone Retina 4inch 64bit)

※実機テストではないのでご注意ください。

各関数で0〜9の乱数を100,000個発生し、0〜9の各整数についての標準偏差を計算しました。
(標準偏差については「経済指標のかんどころ/第14章 統計/平均と標準偏差」を参照)

rand()関数の標準偏差

// 生成する乱数の数と乱数の範囲の設定
const int sampleNumber = 100000;    // 標本数
const int rangeNumber = 10;              // 乱数上限

// 乱数を収納する可変配列の作成
NSMutableArray *randArray = [NSMutableArray array];

// 乱数を生成し、可変配列へ収納
for (int i = 0; i < sampleNumber; i++) {
    randArray[i] = @(rand() % rangeNumber);    // 乱数生成
}

// 分散と標準偏差用の変数の初期化
double randSum = 0, randSD = 0;

for (int j = 0; j < rangeNumber; j++) {
    // 抽出用に可変配列をコピー
    NSMutableArray *randArrayCopy = [NSMutableArray arrayWithArray:randArray];
    // 抽出条件の設定
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K == %d", @"self", j];
    // コピーした可変配列から特定の整数を持つ要素を抽出
    [randArrayCopy filterUsingPredicate:pred];
    // 抽出した要素数から平均個数を引いて偏差を求め、2乗して求めた分散を順次加算
    randSum += pow(abs((int)randArrayCopy.count - sampleNumber / rangeNumber), 2);
}

// 集計した分散を標本数で割り、平方根を取って標準偏差を求める
randSD = sqrt(randSum / rangeNumber);

// 標準偏差の出力
NSLog(@"randSD = %f", randSD);

他の関数は乱数生成の行以外は同様です。
rand()とrandom()に関してはsrand()およびsrandom()でシードを与えた場合も試しました。
(シードは最初の可変配列を作る前に作成しています。)

// rand() + srand()標準偏差
srand((unsigned int)time(NULL));

// random()標準偏差
randomArray[i] = @(random() % rangeNumber);

// random() + srandom()標準偏差
srandom((unsigned int)time(NULL));

// arc4random()標準偏差
arc4randomArray[i] = @(arc4random() % rangeNumber);

// arc4random_uniform()標準偏差
arc4random_uniformArray[i] = @(arc4random_uniform(rangeNumber));

10回試行した結果を下表に示します。

試行回数rand()rand() +
srand()
random()random() +
srandom()
arc4random()arc4random
_uniform()
1138.35169.597100.77965.41787.782103.105
2138.35188.729100.779112.28494.74958.579
3138.35192.961100.77987.45898.28882.492
4138.35186.401100.779121.59867.549105.556
5138.351130.972100.779121.43898.821165.072
6138.351105.360100.77955.88776.107138.548
7138.35194.855100.779123.53282.932103.739
8138.35193.039100.77958.762112.18069.430
9138.351117.240100.779138.66264.48296.635
10138.35186.202100.77979.577147.49398.373
算術平均138.35196.536100.77996.461593.038102.153

こうして見るとrand()とrandom()に関しては再現性があり、シードを与えることでそれを回避できるとともに分散が減少することが分かります。
しかしモジュロバイアスを回避しているはずのarc4random_uniform()の方が、arc4random()より分散が増大していることも分かります。

納得できなかったので標本数を1,000,000個にして試しました。

試行回数rand()rand() +
srand()
random()random() +
srandom()
arc4random()arc4random
_uniform()
1336.788195.484212.112375.150294.668324.041
2336.788265.282212.112171.093306.084287.701
3336.788267.296212.112282.826258.322223.949
4336.788342.466212.112205.370317.153290.070
5336.788163.419212.112288.707254.005204.910
6336.788269.651212.112169.555254.979433.367
7336.788469.068212.112360.910404.388242.687
8336.788379.788212.112293.812288.210270.242
9336.788383.043212.112156.895302.076243.467
10336.788386.001212.112120.170289.877224.977
算術平均336.788312.150212.112242.449296.976274.541

arc4random_uniform()がarc4random()に比べて若干減少したものの、今度はただのrandom()よりシードを与えたrandom()が上回るとともに、arc4random()とarc4random_uniform()がrandom()よりも上回る結果となりました。

この結果が試行回数が少ないせいか、他の条件が悪いせいなのか、環境に依るものかは分かりません。
(プログラムではrand() → rand() + srand() → ・・・ → arc4random_uniform()と一連で実行しているので、その影響があるかもしれません。それぞれ単体で実行した場合と結果が異なるかと思います。)

ですがrand()は非推奨であり、random()も再現性を伴うため使用するのが憚れますし、arc4random_uniform()がモジュロバイアスを回避するものと明言されている以上、今回の試行結果から使用を躊躇する理由にはならないでしょう。

一先ず現状ではiOS 4.3以降をターゲットにしているならば、arc4random_uniform()を使用することを勧めます。



参考文献

Mac OS X Manual Page/srand(3)

Mac OS X Manual Page/random(3)

Mac OS X Manual Page/arc4random(3)

Mac OS X Manual Page/arc4random_uniform(3)

Qiita/Objective-C/arc4randomの罠

だいきちのブログ/気ままにObjective-C/arc4random()でマイナス値が出ることについての考察

和田維作のホームページ/良い乱数・悪い乱数

Wikipedia/RC4

Learn iPhone, iOS, Objective-c, cocos2dx, Unity and ....?/Objective-C - arc4random()が生成する乱数の偏りについて

経済指標のかんどころ/第14章 統計/平均と標準偏差

詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応
(2013/11/02)
大重 美幸

商品詳細を見る

NSLogでNSInteger変数を出力する際の注意

2014. 05. 19
詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応」を読み始めて、NSInteger変数をNSLogで出力する際に(「Part 2 Objective-Cの基礎知識」の「Chapter 2-1 Objective-Cのプログラム」の「変数の宣言とデータ型(p.35)」など)、32bit環境では問題ないのですが64bit環境では警告が出るので調べてみました。



●警告内容

警告文は「Value of type 'NSInteger' should not be used as format arguments; add an explicit cast to 'long' instead(NSInteger型の値はフォーマット引数として使用しないでください。代わりにlongへの明示的なキャストを追加してください)」とあり、(long)でキャストすると共に書式指定子%dを%ldに変更するようにと指示されます。

8036.jpg



●原因

これはiOSの64ビット化に伴い、一部のデータ型のバイト長が32ビットと64ビットのランタイム環境間で異なるためです。

Cocoa Touch 64ビット移行ガイド」の「データ型に関する変更点」の「2つの規約:ILP32とLP64(p.8)」に、これまでの32bitランタイムでの規約(ILP32)と新たに追加された64bitランタイムでの規約(LP64)におけるバイト長と揃え境界(アライメント)が示されています。

表1-1 OS XおよびiOSにおける、各種整数型のバイト長と揃え境界

整数型バイト長
(ILP32)
揃え境界
(ILP32)
バイト長
(LP64)
揃え境界
(LP64)
char1バイト1バイト1バイト1バイト
BOOL、bool1バイト1バイト1バイト1バイト
short2バイト2バイト2バイト2バイト
int4バイト4バイト4バイト4バイト
long4バイト4バイト8バイト8バイト
long long8バイト4バイト8バイト8バイト
ポインタ4バイト4バイト8バイト8バイト
size_t4バイト4バイト8バイト8バイト
time_t4バイト4バイト8バイト8バイト
NSInteger4バイト4バイト8バイト8バイト
CFIndex4バイト4バイト8バイト8バイト
fpos_t8バイト4バイト8バイト8バイト
off_t8バイト4バイト8バイト8バイト

表1-2 OS XおよびiOSにおける、浮動小数点数型のバイト長と揃え境界

浮動小数点数型バイト長
(ILP32)
揃え境界
(ILP32)
バイト長
(LP64)
揃え境界
(LP64)
float4バイト4バイト4バイト4バイト
double8バイト8バイト8バイト8バイト
CGFloat4バイト4バイト8バイト8バイト

NSIntegerはNSObjCRuntime.hで次のように定義されています。

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

32bitランタイムではint(4バイト)でしたが、64bitランタイムではlong(8バイト)に定義が変更されています。

このためNSInteger変数をlongにキャストし、「文字列操作プログラミングガイド」の「書式指定子(p.16)」にあるようにint指定子の%dからlong指定子の%ldにする必要があるのです。

表1 NSStringやCFStringの整形メソッド/関数で使える書式指定子

指定子説明
%@Objective-Cのオブジェクトの文字列表現。descriptionWithLocale:メソッドがあればその戻り値、なければdescriptionメソッドの戻り値になります。CFTypeRefオブジェクトに対しても適用可能で、この場合、CFCopyDescription関数の戻り値を使います。
%%'%'という文字自身。
%d、%D符号付き32ビット整数(int)。
%u、%U符号なし32ビット整数(unsigned int)。
%x符号なし32ビット整数(unsigned int)の16進表示。数字(0-9)と英小文字(a-f)で表記。
%X符号なし32ビット整数(unsigned int)の16進表示。数字(0-9)と英小文字(A-F)で表記。
%o、%O符号なし32ビット整数(unsigned int)の8進表示。
%f64ビット浮動小数点数(double)。
%e64ビット浮動小数点数(double)の指数表記。指数部の先頭は小文字の「e」。
%E64ビット浮動小数点数(double)の指数表記。指数部の先頭は大文字の「E」。
%g64ビット浮動小数点数(double)。指数部が-4未満または精度以上であれば%eと同じ、そうでなければ%fと同じ。
%G64ビット浮動小数点数(double)。指数部が-4未満または精度以上であれば%Eと同じ、そうでなければ%fと同じ。
%c8ビット符号なし文字(unsigned char)。NSLog()でASCII文字として出力。非ASCII文字は8進形式(\\ddd)またはUnicodeの16進形式(\\udddd、但しdは数字)。
%C16ビットUnicode文字(unichar)。NSLog()でASCII文字として出力。非ASCII文字は8進形式(\\ddd)またはUnicodeの16進形式(\\udddd、但しdは数字)。
%s8ビット符号なし文字の配列(末尾はNull)。%s指定子があると、システムデフォルトのエンコーディングとして文字を解釈するので、特に書字方向が右から左(RTL、Right-To-Left)の言語の場合、結果が変わる可能性があります。たとえば強い方向付けがない文字の場合、書字方向マーカが挿入されるのです。したがって、%sの使用を避け、エンコーディングを明示的に指定する方がよいでしょう。
%S16ビットUnicode文字の配列(末尾はNull)。
%pvoidポインタ(void *)。先頭に「0x」を置いた16進表示。数字(0-9)および英小文字(a-f)で表記。
%a64ビット浮動小数点数(double)の指数表記。先頭に「0x」、小数点の前に16進数字を1つ、指数部の先頭は小文字の「p」。
%A64ビット浮動小数点数(double)の指数表記。先頭に「0x」、小数点の前に16進数字を1つ、指数部の先頭は大文字の「P」。
%F64ビット浮動小数点数(double)の10進表記。

表2 NSStringやCFStringの整形メソッド/関数で使える長さ修飾子

長さ修飾子説明
h変換指定子d、o、u、x、Xの前に置いて、引数がshortまたはunsigned shortであることを指定。
hh変換指定子d、o、u、x、Xの前に置いて、引数がsigned charまたはunsigned charであることを指定。
l変換指定子d、o、u、x、Xの前に置いて、引数がlongまたはunsigned longであることを指定。
ll、q変換指定子d、o、u、x、Xの前に置いて、引数がlong longまたはunsigned long longであることを指定。
L変換指定子a、A、e、E、f、F、g、Gの前に置いて、引数がlong doubleであることを指定。
z変換指定子d、o、u、x、Xの前に置いて、引数がsize_tまたはこれに類する符号つき整数であることを指定。
t変換指定子d、o、u、x、Xの前に置いて、引数がptrdiff_tまたはこれに類する符号なし整数であることを指定。
j変換指定子d、o、u、x、Xの前に置いて、引数がintmax_tまたはuintmax_tであることを指定。



●対処法

例として以下のコードを使用します。

NSInteger a=10, b=20, ans;
ans = a + b;
NSLog(@"ans = %d", ans);


1)NSInteger値をlongにキャストする

NSInteger a=10, b=20, ans;
ans = a + b;
NSLog(@"ans = %ld", (long)ans);

コンパイラが指示するもので、「文字列操作プログラミングガイド」の「プラットフォームによる違い(p.18)」にあるようにようにAppleが推奨する対処法です。

表3 データ型に応じた書式指定子

書式指定子考慮点
NSInteger%ldまたは%lx値をlongにキャスト。
NSUInteger%luまたは%lx値をunsigned longにキャスト。
CGFloat%fまたは%g整形の場合、%fはfloat、doubleの両方に有効。ただし、書式指定に従って文字列を解析するときは、下記(「文字列操作プログラミングガイド」のp.19)の注意点を参照。
CFIndex%ldまたは%lxNSIntegerと同様。
ポインタ%pまたは%zx%pならば先頭に「0x」と印字。これが不要であれば%zxを指定し、型のキャストはしない。

この対処法は確かに32/64bit両ランタイム環境で通じるのですが、同様にfloatからdoubleへとバイト長の変わったCGFloatなども含め、適切な書式指定子とキャストを施さなければならないのが少々面倒です。


2)長さ修飾子zを利用する

NSInteger a=10, b=20, ans;
ans = a + b;
NSLog(@"ans = %zd", ans);

size_tがNSIntegerと同じく32bit環境では4バイト、64bit環境では8バイトであることから、長さ修飾子zを利用するというものです。
(「Qiita/Objective-C/32bitと64bit、共通で使えるNSLogとかのフォーマット指定子」や「暇人のブログ/NSIntegerを32bit/64bit両環境で表示させるには」を参照)

この対処法は動作しては問題ないものの本来意味する長さ修飾子ではないので、急場凌ぎとして一時的な措置なら構わないと思いますが、複数人がコードを編集するような場合は混乱を招くので避けた方が良いでしょう。


3)NSNumberリテラルを利用する

NSInteger a=10, b=20, ans;
ans = a + b;
NSLog(@"ans = %@", @(ans));

Xcode 4.4から導入されたNSNumberリテラルを使用して、NSInteger値をNSNumberオブジェクトとする方法です。
(「[yashigani days]/最近はやってるNSLogの書き方」や「Natsu's note/[Xcode][Modern Objectice-C] NSNumberリテラルとBoxed Expression Literals」を参照)

この対処法であればランタイム環境による変数のバイト長の変化を気にする必要が無く、画一的かつ簡素に表現できる良い方法だと思います。



参考文献

Cocoa Touch 64ビット移行ガイド

文字列操作プログラミングガイド

Qiita/Objective-C/32bitと64bit、共通で使えるNSLogとかのフォーマット指定子

暇人のブログ/NSIntegerを32bit/64bit両環境で表示させるには

[yashigani days]/最近はやってるNSLogの書き方

Natsu's note/[Xcode][Modern Objectice-C] NSNumberリテラルとBoxed Expression Literals

詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応
(2013/11/02)
大重 美幸

商品詳細を見る






QuietControl 30 wireless headphones
Calendar
10 | 2017/11 | 12
Sun Mon Tue Wed Thu Fri Sat
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

QuietControl 30 wireless headphones
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR