前回の記事で設計したToukaテストフレームワークを実際に作っていきたい。
まずは、ランダムな__m128を生成するモジュールを定義する。通常のシミュレーションのように「高速にそれっぽい分布を作る」のではなく、「コンパイラ最適化やハードウェアの挙動の違い(バグ)を炙り出す」ための戦略が必要である。イントリンジックの移植において、最もバグが混入しやすいのは「通常の範囲(0.0〜1.0など)」ではなく、ハードウェアの解釈が分かれやすい特殊値や境界値である。一様分布だけでは、これらのケースをテストできる確率が極めて低くなる。
非正規化数(Denormal number または Subnormal number)とは、IEEE 754 浮動小数点数規格において、「ゼロに極めて近い、極小のプラス/マイナスの実数」を表現するための特殊な状態のことである。非正規化数は、ポート作業においてバグの温床となる。
アーキテクチャが異なっても(Intel、PowerPC、ARM)、完全に同じビットパターンの浮動小数点数系列が生成されることなどが絶対条件である。よって、ビットパターンをファイルとして保存することになるし、比較するときもビットパターンとなる。
整数と浮動小数点数では乱数の生成戦略が異なってくる。
ランダム値生成戦略を入れ替えられるようにする。複数の戦略を準備する。例えば、ある範囲のランダム値を生成するものや、エッヂケースを入れるもの、ビットパターンの01からランダムにしたもの。あるいはそれらの混合。
ランダム値がある一定の割合でエッヂケースを入れ、ある一定の割合で一様分布の値を入れるようにする。
以下のようなC++コードが可能である。
+0.0、-0.0、無限大、無限小、qNuN、非正規化最小数、非正規化数付近は以下のようなコードで生成できる。pは0から1までの浮動小数点ランダム数である。
if (p < 0.10) return 0.0f;
if (p < 0.15) return -0.0f;
if (p < 0.25) return std::numeric_limits<float>::infinity();
if (p < 0.35) return -std::numeric_limits<float>::infinity();
if (p < 0.45) return std::numeric_limits<float>::quiet_NaN();
if (p < 0.50) return std::numeric_limits<float>::denorm_min(); // 非正規化数最小値
if (p < 0.55) return 1.175494e-38f; // 非正規化数付近
生ビットパターンからの生成も可能である。
// 4. 生ビットパターンからの生成
uint32_t bits = rng.next32();
float f;
std::memcpy(&f, &bits, sizeof(f));
return f;