回答編集履歴

2

おまけを追加

2020/05/17 12:01

投稿

raccy
raccy

スコア21735

test CHANGED
@@ -266,4 +266,86 @@
266
266
 
267
267
 
268
268
 
269
- Nが3程度ではそれほど違いは見えないでしょう(乱数の周期を越えるぐらいの試行回数を重ねないと、ランダム性から出る誤差の方が大きすぎて、有意義な結果が見えない)。ですが、「0から99までの数を乱数で取得(1D100をふるイメージ)し、50未満なら成功」みたいなゲームのプログラムを組んだとき、成功確率はわずかに50%を越えます(低い数値のほうが出やすいので、SANチェックもきっと乗り越えられるぜ!)。乱数の取り方とその利用方法によっては僅かな差がちょっとの差としてあられる可能性があると言うことです。といっても、その差は変わらずちょっとなのでゲーム程度では些細な問題です。しかし、数学的な厳密さが必要とされる統計やシミュレーション等では問題になる可能性があります。(そもそも、rand()の実装は環境依存であり、良い乱数ではないことが多いので、厳密さが求められるものに使用するのは避けるべきではありますが。)
269
+ Nが3程度ではそれほど違いは見えないでしょう(乱数の周期を越えるぐらいの試行回数を重ねないと、ランダム性から出る誤差の方が大きすぎて、有意義な結果が見えない)。ですが、「0から99までの数を乱数で取得(1D100をふるイメージ)し、50未満なら成功」みたいなゲームのプログラムを組んだとき、成功確率はわずかに50%を越えます(低い数値のほうが出やすいので、SANチェックもきっと乗り越えられるぜ!)。乱数の取り方とその利用方法によっては僅かな差がちょっとの差としてあられる可能性があると言うことです。といっても、その差は変わらずちょっとなのでゲーム程度では些細な問題です。しかし、数学的な厳密さが必要とされる統計やシミュレーション等では問題になる可能性があります。(そもそも、`rand()`の実装は環境依存であり、良い乱数ではないことが多いので、厳密さが求められるものに使用するのは避けるべきではありますが。)
270
+
271
+
272
+
273
+ ---
274
+
275
+ 【おまけ】
276
+
277
+
278
+
279
+ `rand() % N`はそれぞれが現れる確率が等しいとは言えないという話でした。では、どうすれば良いのかというと、はみ出てしまっている部分を切ればいいと言うことです。たとえば、RAND_MAXが32767の場合、0から順番に0,1,2と入れておくと、最後の方は、32766が0、32767が1となって、2にいれるものが一つだけ足りません。なので、乱数が32766や32767の場合は結果を切り捨てて、乱数取得からやり直しすれば、いいとなります。
280
+
281
+
282
+
283
+ では、早速、[GCCのlibstc++](https://github.com/gcc-mirror/gcc/blob/8d9254fc8aa32619f640efb01cfe87cc6cdc9ce1/libstdc%2B%2B-v3/include/bits/uniform_int_dist.h#L244-L253)を参考にしながら、100の場合のコードを書き換えて見ましょう。
284
+
285
+
286
+
287
+ ```C
288
+
289
+ #include <stdio.h>
290
+
291
+ #include <stdlib.h>
292
+
293
+
294
+
295
+ #define TRIAL_NUMS 100000000
296
+
297
+ #define N 100
298
+
299
+ #define N_SCALING (RAND_MAX / N)
300
+
301
+ #define N_PAST (N * N_SCALING)
302
+
303
+
304
+
305
+ int main(void)
306
+
307
+ {
308
+
309
+ srand(0);
310
+
311
+ long long count[N] = {0};
312
+
313
+ for (long long i = 0; i < TRIAL_NUMS; i++) {
314
+
315
+ int r;
316
+
317
+ do {
318
+
319
+ r = rand();
320
+
321
+ } while (r >= N_PAST);
322
+
323
+ count[r / N_SCALING]++;
324
+
325
+ }
326
+
327
+ printf("RAND_MAX: %d\n", RAND_MAX);
328
+
329
+ for (int i = 0; i < N; i++) {
330
+
331
+ printf("%d: %lld\n", i, count[i]);
332
+
333
+ }
334
+
335
+ return 0;
336
+
337
+ }
338
+
339
+ ```
340
+
341
+
342
+
343
+ 今度は少ない数の方に1000000越えが偏るということは起きなかったと思います。
344
+
345
+
346
+
347
+ さて、上のコードを見てみましょう。`do ~ while (...);`の部分は最初に言った切り捨て部分です。最後の切れ端みたいな乱数になった場合(`N_PAST`以上になった場合)はやり直ししています。そうして得られた乱数に対して、`N_SCALING`を割っています。そう、剰余ではなく商を使っています。
348
+
349
+
350
+
351
+ なぜ、剰余ではなく商なのか?`r % N`でもいいのでは無いのか?という疑問はありますが、GCCの実装がそうだったので、今回はそうしました。では、なぜ、GCCがそのようにしているのかについては、すいませんが、もっと詳しい人に聞いていただくようお願いします。

1

以下じゃない未満だ

2020/05/17 12:01

投稿

raccy
raccy

スコア21735

test CHANGED
@@ -266,4 +266,4 @@
266
266
 
267
267
 
268
268
 
269
- Nが3程度ではそれほど違いは見えないでしょう(乱数の周期を越えるぐらいの試行回数を重ねないと、ランダム性から出る誤差の方が大きすぎて、有意義な結果が見えない)。ですが、「0から99までの数を乱数で取得(1D100をふるイメージ)し、50以下なら成功」みたいなゲームのプログラムを組んだとき、成功確率はわずかに50%を越えます(低い数値のほうが出やすいので、SANチェックもきっと乗り越えられるぜ!)。乱数の取り方とその利用方法によっては僅かな差がちょっとの差としてあられる可能性があると言うことです。といっても、その差は変わらずちょっとなのでゲーム程度では些細な問題です。しかし、数学的な厳密さが必要とされる統計やシミュレーション等では問題になる可能性があります。(そもそも、rand()の実装は環境依存であり、良い乱数ではないことが多いので、厳密さが求められるものに使用するのは避けるべきではありますが。)
269
+ Nが3程度ではそれほど違いは見えないでしょう(乱数の周期を越えるぐらいの試行回数を重ねないと、ランダム性から出る誤差の方が大きすぎて、有意義な結果が見えない)。ですが、「0から99までの数を乱数で取得(1D100をふるイメージ)し、50未満なら成功」みたいなゲームのプログラムを組んだとき、成功確率はわずかに50%を越えます(低い数値のほうが出やすいので、SANチェックもきっと乗り越えられるぜ!)。乱数の取り方とその利用方法によっては僅かな差がちょっとの差としてあられる可能性があると言うことです。といっても、その差は変わらずちょっとなのでゲーム程度では些細な問題です。しかし、数学的な厳密さが必要とされる統計やシミュレーション等では問題になる可能性があります。(そもそも、rand()の実装は環境依存であり、良い乱数ではないことが多いので、厳密さが求められるものに使用するのは避けるべきではありますが。)