調べて分かる道具箱

乱数の生成

いくつから
いくつまで
何個ほしいか個
同じ数が出てもよいか
乱数の作り方
出た数

引くと、ここに出ます。「引く」を押してください。

crypto.getRandomValues ── 暗号として強い乱数を返すと決められているもの。種を機器の外(OSの乱数)から取る。

よくある質問

Math.random を使ってはいけないのは、どんなときですか?

セキュリティに関わることすべてです。MDN の Math.random のページには「Math.random() は暗号として安全な乱数を返しません。セキュリティに関わることには使わないでください。代わりに Web Crypto API、正確には Crypto.getRandomValues() を使ってください」と書かれています。パスワード、合い言葉、当選番号、本人確認の番号などがこれにあたります。すごろくの目や、絵の色をばらけさせるといった用途なら Math.random でかまいません。

crypto.getRandomValues なら「本当の偶然」ですか?

いいえ、そこは同じです。MDN は getRandomValues について「速さを保つため、実装は本当の乱数発生器ではなく、じゅうぶんなエントロピーを持つ値を種にした擬似乱数発生器を使っている」と書いています。つまりこちらも計算で作っています。ちがうのは種の出どころと、次の値を当てにくく作ってあるかどうかです。種はブラウザの外、たとえば Unix の /dev/urandom のような機器側の仕組みから取ることが勧められています。

出た数はかたよっていませんか?

かたよらないように作っていますが、こちらの言葉を信じる必要はありません。この道具は、引いた32ビットの数と、捨てた値と、そこから答えを出した割り算を画面に出しています。紙と鉛筆で同じ計算をすれば、答えが合うことを自分で確かめられます。なお、乱数である以上、少ない回数では見た目がかたよって見えることがあります。それは道具の不具合ではありません。

重複なしで、範囲より多い個数を頼むとどうなりますか?

作らずに、何個までなら作れるかをお伝えします。たとえば1から10までは10個しかないので、重複なしで11個は取り出せません。このとき勝手に10個へ減らすと、11個頼んだつもりの人が10個を受け取って気づかないままになります。だから減らさず、範囲を広げるか重複ありに変えるかを選んでいただく形にしています。

くじ引きや席替えに使ってもいいですか?

使えますが、決め方を先にみんなで決めておいてください。この道具は「引き直し」をいくらでもできてしまうので、気に入らない結果が出るたびに引き直すと、道具がどれだけ公平でも結果はかたよります。だれが押すか、何回目を採用するかを先に決めておくのが確実です。なお、この道具が出す数について「絶対に当たらない」「必ず公平になる」といった言い切りはしません。

出した数はどこかに送られますか?

送っていません。数はお使いの機器の中だけで作られ、通信は一切していません。保存もしていないので、ページを閉じたり読み込み直したりすると消えます。控えておきたいときは、画面の数をご自分で書き写すか、コピーしておいてください。

いくつからいくつまでを、何個。作り方を選んで比べられます。引いた乱数と、そこから答えを出した計算も出すので、手元の紙で検算できます。

Math.random は、暗号に関わることに使ってはいけません

ここが、このページでいちばん持ち帰ってほしいところです。 JavaScript で乱数といえば Math.random() ですが、MDN の Math.random のページ(新しいタブで開きます)には、はっきりこう書かれています。

Math.random() does not provide cryptographically secure random numbers. Do not use them for anything related to security. Use the Web Crypto API instead, and more precisely the Crypto.getRandomValues() method.

(Math.random() は暗号として安全な乱数を返しません。セキュリティに関わることには使わないでください。 代わりに Web Crypto API、正確には Crypto.getRandomValues() を使ってください)

パスワード、合い言葉、当選番号、本人確認の番号 ── そういうものを Math.random() で作ってはいけない、ということです。 いっぽう、すごろくの目や、 絵の色をばらけさせるといった用途なら、これで足ります。 上のボタンで両方を選べるようにしてあるのは、 どちらか一方が正しいという話ではないからです。

2015年に、Math.random の中身が入れ替わりました

なぜ危ないのかは、実際に起きたことを見るのが早いです。Chrome の JavaScript を作っている V8 チームのブログ(新しいタブで開きます)(2015年12月)に、こう書かれています。

2015年の終わりまで、V8 の Math.random() は MWC1616 という作り方でした(原文いわく「64ビットの内部状態を使う」)。 ところがこれは乱数の質を測る検査 (TestU01)の多くに落ちていて、種の引きが悪いと、4,000万回たらずで同じ並びが繰り返し始めることがあった、と書かれています。 そこで xorshift128+ という別の作り方に置きかえられ、Chrome 49 から使われるようになりました。 こちらは原文で「128ビットの内部状態を使い、周期は2の128乗ひく1」と書かれています(この2つの数は、下の「出てこない並び」の節で効いてきます)。 Firefox と Safari も同じものに移った、とあります。

ここで大事なのは、置きかえられた、ということそのものです。 JavaScript の決まり(ECMA-262)は Math.random() の作り方を決めていません。だから中身は入れ替えられるし、どのブラウザが今どれを使っているかも約束されていません。 質がどれくらいかを、こちらから確かめるすべが無いのです。

そして同じブログは、置きかえたあとについても念を押しています ──xorshift128+ は MWC1616 より大きく良くなったが、それでも暗号として安全ではない。 ハッシュや署名、暗号化のような用途に、ふつうの擬似乱数は向かない、と。

じつは、どちらも「計算で作った数」です

では crypto.getRandomValues() は「本当の偶然」なのかというと、そうではありません。ここが面白いところです。MDN の getRandomValues のページ(新しいタブで開きます)は、こう説明しています。

To guarantee enough performance, implementations are not using a truly random number generator, but they are using a pseudo-random number generator seeded with a value with enough entropy.

(速さを保つため、実装は本当の乱数発生器を使ってはいません。 じゅうぶんなエントロピー(=でたらめさ)を持つ値を種にした、擬似乱数発生器を使っています)

つまりどちらも、種から順に計算しているだけです。 では何がちがうのか。同じページに答えがあります。 種をブラウザの外から取ってくること ── たとえば /dev/urandom のような、機器そのものが持っている でたらめさの供給口です。そして次の値を当てにくいように作られていること。

Math.random() のほうは、MDN が「種は実装が選ぶ。利用者が選ぶことも、戻すこともできない」と書いています。種を選べないのは良さそうに聞こえますが、その種がどこから来たのかも分からないということでもあります。

「本当の偶然」を使う道具もあります。放射線が飛んでくる時間の間隔や、 電子回路の雑音を測るような装置です。ブラウザからは使えませんし、速さと引きかえになります。 だから web の世界では 「外から取った種 + 当てにくい計算」という組み合わせが選ばれています。

範囲に押し込むときのかたよりは、まったく別の話です

乱数の出どころを良いものにしても、それだけでは足りません。 引いてきた数を「1から100まで」のような範囲に押し込むところで、新しいかたよりが生まれるからです。この2つは独立していて、 暗号用の乱数を使っていても押し込み方が雑ならかたよりますし、Math.random() でも押し込み方が正しければ範囲のかたよりは消えます (当てられることのほうは消えません)。

この道具が引いてくるのは32ビットの数、 つまり 0 から 4,294,967,295 までの4,294,967,296通りです。 これを「あまり」で範囲へ押し込むと、4,294,967,296が範囲の広さで割り切れないかぎり、 出やすい数と出にくい数ができます。

そして、範囲が広いほど差は大きくなります。4,294,967,296を範囲の広さで割った「商」が、1つの数に割り当たる取り分だからです。 商が4億もあれば1つぶんの差は誤差ですが、商が2しかなければ、1つの差は5割の差になります。

範囲の広さごとの、あまりだけで範囲に押し込んだときのかたより
範囲の広さ多い数の取り分少ない数の取り分出やすさの差捨てる値
10個429,496,730通り429,496,729通り0.00000023パーセント多い0.00000014パーセント
100個42,949,673通り42,949,672通り0.0000023パーセント多い0.0000022パーセント
1,000個4,294,968通り4,294,967通り0.000023パーセント多い0.0000069パーセント
1,000,000個4,295通り4,294通り0.023パーセント多い0.023パーセント
1,000,000,000個5通り4通り25パーセント多い6.9パーセント
2,000,000,001個3通り2通り50パーセント多い6.9パーセント

いちばん下の行 ── −10億から10億まで(2,000,000,001個)を「あまり」で作ると、 取り分は3通りと2通り。 つまり小さいほうから294,967,294個の数だけが、 残りより1.5倍出やすいことになります。 1回や2回では、まず気づけません。

1バイト(256通り)とサイコロの面の数で、この「あまりのかたより」を数え上げた話は 3Dサイコロをふるのページにあります。数が小さいぶん、全部を並べて確かめられます。

直し方: 範囲の外が出たら、捨ててもう一度引く

割り切れるところまでしか使わなければいいのです。 広さ10なら、4,294,967,296のうち 4,294,967,290 までは10で割り切れます。ここまでを使い、4,294,967,290以上が出たら捨ててもう一度引きます。 すると10個の数に429,496,729通りずつ、ぴったり同じだけ割り当たります。

捨てるのは 6個だけ、0.00000014パーセントです。 上の「この数はこう決めました」に、 引いた数と、捨てた分と、割り算がそのまま出ています。紙の上で同じ計算をして、合うかどうか確かめてみてください。

重複なしのとき、範囲より多くは作れません

1から10までの中から、同じ数を使わずに11個 ── これはどんな道具でも作れません。10個しか無いところから11個を 取り出すことはできないからです。あたりまえに見えますが、ここで黙って10個に減らす道具は珍しくありません。

この道具は減らしません。作らずに、何個までなら作れるかをお伝えします。黙って10個返すと、11個頼んだ人は足りないことに気づけないまま 次の作業に進んでしまいます。数が1つ足りない席替えの表を、あとから直すほうが大変です。 個数の上限(1,000個)を超えたときも同じで、勝手に1,000個へ丸めることはしません。

10億通りから5個選ぶのに、10億個の箱は作りません

重複なしのやり方でいちばん素直なのは、全部を並べてよく混ぜ、頭から必要な数だけ取る方法です (フィッシャー・イェーツのシャッフル)。 ところがこの道具の範囲は最大で2,000,000,001個あるので、そのまま並べると機械の記憶がまるごと足りません。

そこで動かしたところだけを覚えておく作りにしています。 混ぜるときに触っていない場所は「まだその番号のまま」とみなせるので、覚えておく量は取り出す個数のぶんだけで足ります。 5個ほしいなら5回だけ混ぜる ── 全部を混ぜ終える必要はありません。

画面の「この数はこう決めました」で、重複なしのときだけ割る数が1つずつ減っていくのはこのためです。 配り終えた数が候補から抜けるので、次はその1つぶん狭いところから選びます。

⭐ トランプを混ぜても、絶対に出てこない並びがあります

ここまでは「どれくらい良い乱数か」の話でした。 最後に、どんなに良い乱数でも越えられない壁の話をします。

トランプ52枚の並べ方は、全部で 68桁の数になります (8のうしろに0が67個ならぶくらい)。 一方 Math.random の内部にある 「いまどこまで進んだか」を覚えている場所は、上で見たとおり128ビットです。

混ぜ方が最初の内部状態だけで決まるなら、 出来上がる並びも内部状態1つにつき1つに決まります。 つまり作れる並びは、多くても2を128回かけた数だけ。 並べ方のほうがずっと多いので、どうやっても出てこない並びが必ず残ります(入れる箱より物が多ければ必ずあふれる、というのと同じ理屈です)。

どれくらい残るのか、割合で見るとこうなります ── 出せるのは全体の30桁ぶんの1より少ない、という桁です。ほとんど全部の並びが、一生出てきません。

内部状態の大きさと、全部の並びを出せる枚数
内部状態どこで使われたか全部出せる枚数
64ビット2015年までの Math.random(MWC1616)20枚まで
128ビットいまの Math.randomxorshift128+34枚まで
226ビット必要トランプ52枚を全部の並びにするには52枚

⭐ 128ビットで足りるのは 34枚までです。 トランプは52枚あるので、ジョーカーを抜いても届きません。

「だから危ない」とまでは言えません。ここで数えたのは「最初の内部状態だけから並びが決まる場合」です。 混ぜている途中で本物の種を継ぎ足す作りなら、この数え方はそのまま当てはまりません。crypto.getRandomValues は継ぎ足す側なので、 カードゲームのように「出てこない並びがあっては困る」用途では、そちらを使ってください。

それでも、この話には持ち帰るものがあります ──乱数が良いか悪いかとは別に、「取りうる場合の数」という上限があるということです。 上でみた「かたより」は使い方で消せましたが、こちらは箱の大きさの話なので、混ぜ方をいくら工夫しても消えません。

くじや席替えに使うときに、気をつけること

道具がかたよっていなくても、使い方でかたよります。 いちばん多いのは引き直しです。 気に入らない結果が出るたびに押し直せば、 出てくるのは「押した人が気に入った結果」であって、乱数の結果ではありません。

先に決めておくのが確実です。 だれが押すか、何回目を採用するか、 画面をみんなで見るか。 決めてから押せば、あとで揉めません。

それから、このページでは「絶対に当たらない」「必ず公平です」といった 言い切りをしません。 乱数は「次に何が出るか分からない」ことが値打ちなので、 少ない回数なら見た目がかたよることも当然あります。 3回続けて同じ数が出ることもあります。それは壊れているのではなく、そういうものです。

なお、ここで作った数はお使いの機器の中だけで生まれています。 通信は一切していません。入れた範囲も、出た数も、どこにも送っていません。

出どころ: Math.random() についての注意書きと、getRandomValues() が擬似乱数発生器であることは MDN の Math.random(新しいタブで開きます)と MDN の Crypto.getRandomValues(新しいタブで開きます)の原文、MWC1616 から xorshift128+ への置きかえは V8 チームのブログ(2015年12月17日)(新しいタブで開きます)です。 かたよりの通り数は4,294,967,296通りを数え上げた結果で、 単体テストに固定してあります(取り分の合計がちょうど4,294,967,296になることも 検算しています)。