-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Math:rnd(min, max)
等の分布が偏っている The Math:rnd(min, max)
and its seeded variant have slight biases in distribution
#489
Comments
ユースケースによってはシード値の保存とかをしている可能性がある以上、乱数生成・調整アルゴリズムの変更は破壊的変更ということになりそうですかね? |
論文を読むのがかなり苦手なのであまり参加できないかも… |
Misskey Pages/Playとかでランダム生成なのに何故かパターンがあるような生成がされるなと3年くらい前から思ってるけどそういうことだったのかしら |
Rejection Samplingの方は既に手元環境で試してみました。実装例をほぼそのまま書き込みましたがうまく動いてくれます。
欲しいパターン数nに対して 内部の乱数生成器の品質(Off-topic?)また、昨夜に解析していたのですが、 シード生成(暗号目的には決して使えないアルゴリズム)
|
うーん、話を聞く限りよほど大きい数を扱わなければ分布の偏りは無視できる程度に収まりそうですかね? |
この先更に精度が良かったり安全だったりなアルゴリズムを実装する時の対応を考えたいですね… 例えば、
というようにエイリアスを用意することで、多くのユーザーにはより改善されたアルゴリズムを提供でき、アルゴリズム変更が気になるユーザーにも以前のアルゴリズムを使い続ける選択肢がある、というのはどうでしょうか? |
残念ながら僅かな偏りに噛みつく人はどうしても存在します。 |
(私は乱数生成器とそれを扱うアルゴリズムを一括りにして語っていましたが、)
概ね同意です。 |
This comment was marked as off-topic.
This comment was marked as off-topic.
発生している乱数の偏りがどの程度問題になるのか分かりません。 例えば、その偏りが実用上あまり影響がないのであれば、修正しても良いとは思いますが必ずしも修正する必要はありません。AiScriptとしてその方法を採用するかを選択できると思います。 |
AiScriptはサードパーティライブラリのような仕組みがないので、やるなら公式で実装するしか無い、という点で私は採用したい寄りですね |
仮に、実用上の影響があまりなくてできるだけ本来の乱数に近づけたいという目的であれば、方法があるならどこまでも近づけるのかという話になりますし、実装の複雑さに影響を与えます。AiScriptとしてこれを採用するかどうかの判断ができるという意味です。 |
もちろんその通りですし、その上で
です。それが際限のない探求になるとしても、例えば
のような仕組みを用意することで対処できると考えています。 |
メンテナンスできる人は限られているのでそこも考える必要があります。 |
This comment was marked as resolved.
This comment was marked as resolved.
現在のアルゴリズムによる偏りの話です。
|
用途にもよりますが、過去に以下のようにご回答した通り、修正は必要であると考えています。
また、.NET Runtimeでは「偏りが計測不可能なほど小さい」ことは「僅かに偏っている」のと同義であると結論付け、Lemire's algorithmを実装する際にループの実行回数に制限を設けませんでした。 |
実装に取り掛かっているのでどなたかAssignして頂けませんか? |
後で気づきましたが、現行の実装では範囲外の値が帰ってきた結果Play等が意図せずクラッシュする可能性がありますね。 極稀に範囲外の値が生成される可能性がある(off-topic?)
aiscript/src/interpreter/lib/std.ts Line 410 in 537fa96
となり、指定した return NUM(Math.floor(Math.random() * (Math.floor(max.value) - Math.ceil(min.value) + 1)) + Math.ceil(min.value)); と書き直せば解決しますが、乱数生成器が だったのが、この変更により、 へと変わってしまいます。 |
乱数の最大値付近が出た時、丸め誤差が掛け算により増幅され、結果が最大値を超す場合がある、みたいな感じですかね? |
どちらかというと、掛け算まではぎりぎり切り上げられなかった結果を足し算後の丸めが切り上げさせてしまう感じですね。
私もそう思います。(とりあえず現在は新しい関数として実装してますが...) |
概要
aiscript/src/interpreter/lib/std.ts
Lines 408 to 427 in 537fa96
現行の実装では、$[0, 1)$ の乱数を生成し、範囲を $[min, max+1)$ に整形してからfloorするという方法で生成されています。
しかし、この方法では偏りが生じます。
現行実装の問題点
浮動小数点数演算の丸め誤差による偏り
まず、$[0, 1)$ 内の有効な倍精度浮動小数点数は $4,607,182,418,800,017,408$ ( $2^{52} * 3 * 11 * 31$ )パターン存在します。
Math.random()
はこれらの数の中から何らかの分布(選定されたパターンを倍精度浮動小数点数として解釈し観測した場合に一様分布となるような分布)に従ってランダムに値を選定して返します。これを範囲変換する際に浮動小数点数の乗算や加算を行っています。
浮動小数点数では、四則演算を行う度に丸めが発生します。ここでの丸めは切り捨てや切り上げではなく、Rounding half to even(日本語版)と呼ばれる方法に基づいて行われることが多いです。
また、浮動小数点数はそもそも無限桁の演算を行うことが出来ない為、四則演算は殆どの場合単射ではありません。
ここで偏りが発生します。
内部的に生成されうる全パターンを等分出来ないことによる偏り
まず、$[0, 1)$ 内の有効な倍精度浮動小数点数 $4,607,182,418,800,017,408$ パターンの内、その約99.8%は $[0, 0.5)$ の範囲内にあり、 $[0.5, 1)$ の範囲内にあるパターン数は $4,503,599,627,370,496$ ( $2^{52}$ )パターンしか存在しません。
$[0, 0.5)$ と $[0.5, 1)$ の範囲がそれぞれ50%の確率で生成されます。つまり、50%もの確率で $2^{52}$ パターンの中から抽選されることになります。
$2^{52}$ パターンの中から選ばれた $[0.5, 1)$ の範囲内にある数を2の冪ではない整数(range)で乗算し、$[\lfloor {\frac{range}{2}} \rfloor, range)$ 内の整数のいずれかにたどり着きますが、この範囲の整数はそもそも $2^{52}$ パターンを等分できず、偏りが発生します。
Math.random()
ではMath.floor()
により丸めを行うと、他の範囲についても同様のことが言えます。
提案手法
偏りを除去するためのアルゴリズムは複数存在します。
Rejection Sampling系アルゴリズム
最もシンプルな方法として、"Rejection Sampling"と呼ばれる方法が広く知られています。
以下に示すアルゴリズムはそのうちの一種類です。
TypeScriptでの実装例:
min以上max以下の整数を生成する例(max-minは2^32 以下である必要があります):
この方法は最も簡潔ですが、
maxInclusive
が二の冪(=パターン数が二の冪+1)であるときに約50%もの確率で生成をやり直すことになり、殆どの乱数列が無駄になります。Lemire's algorithm 及びその変種
.NET RuntimeやOpenSSL等で採用された方法です。
詳細な解説は以下にあるのでそれをご確認ください。
どちらも固定小数点数をスケールして範囲に収めていますが、偏りの原因となるパターンが生成されたことを検知して精度を向上した上で乗算する処理が入っており、ループ回数を制限しなければ偏りを取り除くことが出来ます。
The text was updated successfully, but these errors were encountered: