東方弾幕風の乱数について

東方弾幕風の乱数生成関数 rand( min, max ) は、 min 以上 max 以下の乱数を返す関数です:

let x = rand( 0, 1 )

例えばこう書いた場合、 x の値の範囲は 0 <= x <= 1 となり、 1 を含みます。


一方で、多くのプログラミング言語に用意されている一様な実数乱数の分布は、上端の値を含みません:

local x = math.random()  -- 代表して Lua

このように書いた場合、 x の範囲は 0 <= x < 1 となり、 1 になることはありません。


さて、通常の用途では、この「上端を含まない」性質はとても大事で、
例えば実数乱数から整数乱数を得たい場合には、上端を含まない乱数ならば、

local n = 6
local i = math.floor( math.random() * n ) + 1

などと綺麗に書き表すことができますが、上端を含む乱数ではこのように上手くは書けません。
また正規乱数を得たい時も、上端を含まない乱数ならば、ボックス=ミューラー法で

local r = math.sqrt( -2 * math.log( 1 - math.random() ) )
local theta = 2 * math.pi * math.random()
local a, b = r * math.cos(theta), r * math.sin(theta)

と書けますし、一定の確率 p で、ある処理を行ないたい場合なども、上端を含まない乱数ならば

local p = 〜
if rand() < p then
  -- 処理
end

と綺麗に書き表すことができます(上端を含むとダメな理由は p が 0 や 1 の時を考えれば分かります)。


ではなぜ、東方弾幕風の rand は、上端を含む範囲の乱数を返すのでしょうか?
これは rand( -1, 1 ) といった、「ゼロを中心とした」乱数を考えると納得できます:

let image = 〜;
let delay = 〜;

loop {
  let speed = 〜;
  let angle = GetAngleToPlayer() + rand(-60, 60);
  CreateShot01( GetX(), GetY(), speed, angle, image, delay );
  yield;
}

このコードが行う処理は、自機に対して扇形に弾幕をばらまくというものですが、
このような「何かを基準としてランダム要素を加える」場合には、両端を含んだ乱数のほうが相応しいのです。
こと弾幕を書く場合において、そのような処理は非常に多いので、
東方弾幕風の乱数が両端を含む分布なのは、ある程度 理にかなったものと言っていいでしょう。


とはいえ、やっぱり上端を含まない乱数も欲しいです:

let image = 〜;
let delay = 〜;

loop {
  let speed = 〜;
  let angle = rand( 0, 360 );
  CreateShot01( GetX(), GetY(), speed, angle, image, delay );
  yield;
}

このコードは全方位に弾幕をばらまく処理を行ないますが、
このような場合には、両端を含む乱数だと、 0° の方向に対して僅かに偏りが出てしまいます。
なので、東方弾幕風でも 上端を含まない乱数が使えたらいいなぁ、と少し思った次第。


まぁ、このへんの話は、あくまで気分的なもの。
プログラミングにおいて境界は大事ですが、境界が問題になる場合は その都度考えればいいし、
問題になる時だけ考えれば、実用上、境界を含むか否かは、大雑把に見た乱数の分布には殆ど関係ないわけですが、
それでも、気になるものは気になるので、
BarrageLL の乱数生成では、上端を含まない乱数と含む乱数、両方を用意しようと思いました。


// そもそも「何かを基準にランダム要素を加える」という場合は一様乱数より正規乱数の方がいい気もする