rand()+ rand()が負の数を生成するのはなぜですか?


304

私はその観察rand()、それがループ内で一度だけ呼ばれるライブラリ関数を、それはほとんど常に正の数を生成します。

for (i = 0; i < 100; i++) {
    printf("%d\n", rand());
}

しかし、2つのrand()呼び出しを追加すると、生成された番号の負の数が増えます。

for (i = 0; i < 100; i++) {
    printf("%d = %d\n", rand(), (rand() + rand()));
}

2番目のケースで負の数が表示される理由を誰かが説明できますか?

PS:ループの前にシードをとして初期化しますsrand(time(NULL))


11
rand()否定的であってはなりません...
20レモン

293
rand()+ rand()can owerflow
maskacovnik

13
RAND_MAXあなたのコンパイラは何ですか?通常はにありますstdlib.h。(面白い:チェックするとman 3 rand、「不良乱数ジェネレーター」という1行の説明が表示されます。)
usr2564301 '13

6
すべての正気なプログラマがすることをするabs(rand()+rand())。私はむしろ否定的なものよりも肯定的なUBを持ちたいです!;)
ヴィニシウス鎌倉

11
@hexa:すでに追加で発生しているため、これはUBには当てはまりません。UBを定義された動作にすることはできません。正気の progrtammerは地獄のようUBを避けるだろう。
このサイトには正直すぎます

回答:


542

rand()整数を返すように定義されている0RAND_MAX

rand() + rand()

オーバーフローする可能性があります。観察されるのは、おそらく整数オーバーフローによって引き起こされた未定義の動作の結果です。


4
@JakubArnold:オーバーフロー動作が各言語で異なるように指定されているのはどうしてですか?例えば、Pythonには何もありません(まあ、利用可能なメモリまで)。
このサイトには正直すぎる

2
@Olafこれは、言語が符号付き整数を表す方法を決定する方法に依存します。Javaには整数オーバーフローを検出するメカニズムがなく(Java 8まで)、ラップアラウンドするように定義されており、Goは2の補数表現のみを使用し、符号付き整数オーバーフローに合法であると定義しています。Cは明らかに2の補数以上をサポートします。
PP

2
@EvanCarslakeいいえ、それは普遍的な行動ではありません。あなたが言うことは2の補数表現についてです。しかし、C言語では他の表現も可能です。C言語の仕様では、符号付き整数のオーバーフローは定義されていません。したがって、一般に、プログラムはそのような動作に依存するべきではなく、符号付き整数オーバーフローを引き起こさないように注意深くコーディングする必要があります。しかし、これは、符号なし整数には適用できません。明確に定義された(2を法とする簡約)方法で「ラップアラウンド」するためです。[続く] ...
PP

12
これは、符号付き整数オーバーフローに関連するC標準からの引用です。式の評価中に例外条件が発生した場合(つまり、結果が数学的に定義されていないか、その型の表現可能な値の範囲にない場合)、動作未定義です。
PP

3
@EvanCarslakeは、Cコンパイラが標準を使用する問題から少し離れており、符号付き整数については、a + b > aそれを知っていると仮定できb > 0ます。また、後で実行されたステートメントがある場合a + 5、現在の値はそれよりも低いと想定することもできINT_MAX - 5ます。したがって、トラップのない2の補数プロセッサ/インタプリタでも、プログラムは、intsがトラップのない2の補数であるかのように動作しない場合があります。
Maciej Piechotka

90

問題は追加です。rand()int値を返します0...RAND_MAX。したがって、2つ追加すると、最大でになりRAND_MAX * 2ます。それを超える場合INT_MAX、加算の結果はan intが保持できる有効範囲をオーバーフローします。符号付き値のオーバーフローは未定義の動作であり、キーボードが外国語で話しかける可能性があります。

ここで2つのランダムな結果を追加しても利益はないので、単純なアイデアは単にそれを行わないことです。またはunsigned int、合計を保持できる場合は、各結果を加算前にキャストできます。または、より大きなタイプを使用します。longは必ずしもより大きいとは限らないことに注意してください。64ビット以上の場合intも同じです。long longint

結論:追加は避けてください。それはより多くの「ランダムさ」を提供しません。より多くのビットが必要な場合は、値を連結するsum = a + b * (RAND_MAX + 1)こともできますが、その場合は、よりも大きなデータ型が必要になる可能性がありintます。

あなたが述べた理由は結果がゼロになるのを避けることです:rand()両方ともゼロになる可能性があるので、2つの呼び出しの結果を追加してもそれは避けられません。代わりに、増分することができます。の場合RAND_MAX == INT_MAX、これはで実行できませんint。ただし、(unsigned int)rand() + 1非常に可能性が高いです。可能性が高い(決定的ではない)。UINT_MAX > INT_MAXこれは、が必要であるためです。これは、私が知っているすべての実装に当てはまります(これには、過去30年間の組み込みアーキテクチャ、DSP、およびデスクトップ、モバイル、サーバーのすべてのプラットフォームが含まれます)。

警告:

すでにここのコメントに振りかけますが、してください2つのランダムな値を追加することはないことに注意してくださいない均一な分布を得るが、2個のダイスを転がすような三角分布:取得する12(2個のダイスを)両方のダイスは示さなければなりません6。以下のための112つの可能な変形既に存在している6 + 55 + 6など、

したがって、この点からも加算は悪いです。

また、rand()生成される結果は、疑似乱数ジェネレータによって生成されるため、互いに独立していないことに注意してください。また、規格は計算値の品質や均一分布を指定していないことにも注意してください。


14
@badmad:では、両方の呼び出しが0を返すとどうなるでしょうか。
このサイトには正直すぎます

3
@badmad:UINT_MAX > INT_MAX != false規格で保証されているのかと思います。(おそらく音ですが、必要かどうかはわかりません)。その場合は、1つの結果をキャストして(その順序で)増分できます。
このサイトには正直すぎる

3
あなたは不均一な分布をしたい場合に、複数の乱数を追加することで、ゲインがあります:stackoverflow.com/questions/30492259/...は
・クール

6
0を回避するには、単純な「結果が0のとき、再ロール」しますか?
Olivier Dulac 2015年

2
それらを追加することは、0を回避するための悪い方法であるだけでなく、不均一な分布ももたらします。あなたは、圧延サイコロの結果のような分布を得る:7はおそらく2として、あるいは12として6回です
Barmar

36

これは、この回答に対するコメントで行われた質問の明確化に対する回答です

私が追加した理由は、コード内の乱数として「0」を回避するためでした。rand()+ rand()は、すぐに頭に浮かんだ簡単な解決策でした。

問題は0を回避することでした。提案されたソリューションには(少なくとも)2つの問題があります。1つは、他の回答が示すように、rand()+rand()未定義の動作を引き起こす可能性があることです。最善のアドバイスは、未定義の動作を呼び出さないことです。もう1つの問題はrand()、連続して0が2回生成されないという保証がないことです。

次の例では、ゼロを拒否し、未定義の動作を回避します。ほとんどの場合、2回の呼び出しよりも高速ですrand()

int rnum;
for (rnum = rand(); rnum == 0; rnum = rand()) {}
// or do rnum = rand(); while (rnum == 0);

9
どうrand() + 1ですか?
askvictor

3
@askvictorオーバーフローする可能性があります(そうではありません)。
gerrit 2015年

3
@gerrit-MAX_INTおよびRAND_MAXに依存
askvictor

3
@gerrit、彼らはしている場合、私は驚くだろうではない同じ、私はこれはpedantsのための場所であると仮定:)
askvictor

10
RAND_MAX == MAX_INTの場合、rand()+ 1は、rand()の値が0とまったく同じ確率でオーバーフローするため、このソリューションは完全に無意味になります。あなたはそれを危険にさらすと、オーバーフローの可能性を無視して喜んでいる場合は、同様のrand()などでを使用して、0を返すことの可能性を無視することができます
エミールJeřábek

3

基本的にはrand()間の番号を生成0してRAND_MAX、そして2 RAND_MAX > INT_MAXあなたのケースで。

オーバーフローを防ぐために、データ型の最大値でモジュレートできます。このコースは、乱数の分布を妨害しますが、乱数をrandすばやく取得するための方法にすぎません。

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

int main(void)
{
    int i=0;

    for (i=0; i<100; i++)
        printf(" %d : %d \n", rand(), ((rand() % (INT_MAX/2))+(rand() % (INT_MAX/2))));

    for (i=0; i<100; i++)
        printf(" %d : %ld \n", rand(), ((rand() % (LONG_MAX/2))+(rand() % (LONG_MAX/2))));

    return 0;
}

2

2つのrand()の合計によって返される値がRAND_MAXの値を超えないようにすることで、かなりトリッキーなアプローチを試すことができます。可能なアプローチは、sum = rand()/ 2 + rand()/ 2です。これにより、RAND_MAX値が32767の16ビットコンパイラの場合、両方のrandが偶然に32767を返したとしても(32767/2 = 16383)16383 + 16383 = 32766であるため、負の合計にはなりません。


1
OPは結果から0を除外したいと考えていました。また、ランダム値の均一な分布も提供されません。
このサイトには正直すぎる

@Olaf:2つの連続した呼び出しrand()が両方ともゼロを生成しないという保証はありません。そのため、ゼロを回避したいという願望は、2つの値を追加する理由にはなりません。一方、オーバーフローが発生しないことを保証する場合は、2つのランダムな値を追加する理由として、不均一な分布が望まれる場合があります。
スーパーキャット2018年

1

私が追加した理由は、コード内の乱数として「0」を回避するためでした。rand()+ rand()は、すぐに頭に浮かんだ簡単な解決策でした。

結果がゼロになることも、オーバーフローすることもない単純な解決策(大丈夫、それを「ハック」と呼びます)は次のとおりです。

x=(rand()/2)+1    // using divide  -or-
x=(rand()>>1)+1   // using shift which may be faster
                  // compiler optimization may use shift in both cases

これにより最大値が制限されますが、それを気にしない場合は、これで問題なく機能します。


1
補足:符号付き変数の右シフトに注意してください。これは非負の値に対してのみ明確に定義されており、負の値に対しては実装で定義されています。(幸いにも、rand()常に非負の値を返します)。ただし、最適化はコンパイラーに任せます。
このサイトには正直すぎます。2015

@Olaf:一般に、符号付き2による除算はシフトよりも効率が悪くなります。コンパイラーの作成者がrand負でないことをコンパイラーに伝える努力をしていなければ、シフトは符号付き整数による除算よりも効率的です。2.除算は機能する可能性2uがありますxが、その場合int、符号なしからの暗黙の変換に関する警告が生じる可能性があります署名する。
スーパーキャット2018年

@supercat:もう一度私のコメントを読んでください。/ 2とにかく、妥当なコンパイラーがシフトを使用することをよく知っているはずです(のようなもの-O0、つまり明示的に要求された最適化なしでも、これを見てきました)。これはおそらく、Cコードの最も簡単で確立された最適化です。ポイントは、除算が非負の値だけでなく、整数範囲全体の標準によって明確に定義されていることです。繰り返しますが、最適化はコンパイラに任せ、最初に正しい明確なコードを記述します。これは初心者にとってさらに重要です。
このサイトには正直すぎます

@Olaf:私がテストしたすべてのコンパイラは、を使用している場合でも、2で割るときよりもrand()1で右にシフトするとき、または割るときに、より効率的なコードを生成2u-O3ます。そのような最適化が問題になる可能性は低いと合理的に言うかもしれませんが、「そのような最適化をコンパイラーに任せる」と言うことは、コンパイラーがそれらを実行する可能性が高いことを意味します。あなたはを知っています任意の実際になることをコンパイラ?
スーパーキャット2018年

@supercat:その場合は、最新のコンパイラを使用する必要があります。生成されたアセンブラーを最後にチェックしたときに、gccが細かいコードを生成しました。それにもかかわらず、私がグルーピーをすることにかなり不満を抱いているので、私はあなたが前回提示する範囲にいらいらされたくないと思います。これらの投稿は古く、私のコメントは完全に有効です。ありがとうございました。
このサイトには正直すぎます

1

0を回避するには、これを試してください:

int rnumb = rand()%(INT_MAX-1)+1;

含める必要がありますlimits.h


4
それはそれは基本的に同じである(しかしpossiblly遅い)条件付きで1を追加するなど、1を取得する確率が2倍になりかのrand()利回り0
このサイトはあまりにも正直

はい、あなたは正しいオラフです。ランド()は0またはINT_MAXをIF = -1 rnumbは1であろう
ドニ

さらに考えてみると、さらに悪いことに。1and の確率は実際に2倍になります2(すべて想定されますRAND_MAX == INT_MAX)。私は忘れていました- 1
このサイトには正直すぎる

1
-1ここでは何の価値を提供していません。 rand()%INT_MAX+1; それでも、[1 ... INT_MAX]の範囲の値のみが生成されます。
chux-モニカを

-2

オーバーフローの可能性について他の誰もが言ったことは、符号なし整数を使用する場合でも、負の原因になる可能性があります。実際の問題は、実際には時刻/日付機能をシードとして使用することです。この機能に本当に慣れてきたら、私がこれを言う理由を正確に理解できます。それが実際に行うことは、特定の日付/時刻からの距離(経過時間)を与えることです。日付/時刻機能をrand()のシードとして使用することは非常に一般的な方法ですが、実際には最善の方法ではありません。トピックには多くの理論があり、私はそれらのすべてに入ることができなかったので、より良い代替案を検索する必要があります。この方程式にオーバーフローの可能性を追加すると、このアプローチは最初から運命づけられていました。

rand()+ 1をポストしたユーザーは、負の数を取得しないことを保証するためにほとんどが使用するソリューションを使用しています。しかし、そのアプローチも本当に最善の方法ではありません。

あなたができる最善のことは、適切な例外処理を記述して使用するために余分な時間をかけ、結果がゼロになる場合またはその両方の場合にのみrand()番号に追加することです。そして、負の数を適切に処理するため。rand()機能は完全ではないため、例外処理と組み合わせて使用​​して、目的の結果が確実に得られるようにする必要があります。

rand()機能の調査、調査、および適切な実装に余分な時間と労力を費やすことは、時間と労力に見合うだけの価値があります。ちょうど私の2セント。頑張って頑張ってください...


2
rand()使用するシードは指定しません。標準で、時間との関係ではなく、疑似乱数ジェネレータを使用するように指定してます。また、発電機の質についても述べていません。実際の問題は明らかにオーバーフローです。rand()+1を回避するために使用されることに注意してください0rand()負の値を返しません。申し訳ありませんが、ここで要点を逃しました。PRNGの品質についてではありません。...
このサイトには正直すぎる

... GNU / Linuxの下での良い習慣/dev/randomは、その後に良いPRNG をシードして使用し(rand()glibcからの品質については不明)、またはデバイスの使用を継続する-十分なエントロピーがない場合にアプリケーションがブロックされるリスクを負います。アプリケーションでエントロピーを得ようとすることは、おそらく攻撃しやすいため、脆弱性である可能性が非常に高いです。そして今、それは強化になりました-ここではありません
このサイトには正直すぎます
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.