FPUのフラグをいじろう
あまりよくわかってませんが、浮動小数点数の規格であるところの IEEE754 には、無効操作ってものが定義されてるようです。例えば Inf - Inf だとか NaN > 0 だとか sqrt(-1) だとかですね。
で、 x87 の FPU ではなんか、これはたいていの場合ほっとくと QNaN を返す状態になってるみたいです。たいていの場合ってのはうちの Linux および Windows の場合、というわけなのですが。で、これ嬉しいかというとあんまりゲームとかでは嬉しくなくて、ゲームで QNaN なんて出てきたらそれはもうものすごい勢いで即日落ちて欲しいんです。
ところが D 言語では float だの double の初期値は QNaN でして、 OpenGL は QNaN の座標とかを示されても特に騒ぐこともなく単に描画してくれない。だから変数の初期化忘れるとなんか画面に色々出ないという悲しいことになるわけです。これは、僕自身も何度も何度も悲しいことになってますし、isshikiさんは実際色々苦労されていたようですし、 ABA さんも float とかには assert 入れまくってるようですし、特に D & OpenGL のハマリポイントになってるみたいです。
で、対策は色々あるんですが、とにかく落ちてくれるのが一番嬉しい。で、たぶん FPU に NaN の演算を落とすようなフラグがあるんじゃないかな、と思ったので調べたところ、あったみたいです。 fldcw と fnstcw or fstcw 。これで 0bit 目のマスクを落としてやればいいみたいです。 GCC でのサンプルコード。普段は落ちないで nan と表示されるはずですが、これをセットしておくと落ちてくれるはずです。
#include <stdio.h> #include <math.h> int main() { short cw; __asm__ volatile("fnstcw %0":"=m"(cw)); cw &= ~1; __asm__ volatile("fldcw %0"::"m"(cw)); printf("%f\n", sqrt(-1)); return 0; }
D 言語でのサンプル。 version(X86) の部分が無い場合、 NaN の比較ができてしまいます。
void main() { double d; version (X86) { short cw; asm { fnstcw cw; } cw &= ~1; asm { fldcw cw; } } if (!(d > 0)) printf("NaN can be compared\n"); }
実際に上記のコードを isshiki さんの ReflectBall 冒頭に仕込んだところ、きちんと落ちてくれました。 D でゲーム書いてるような人はみんなこれやっておくといいんじゃないかなーと思います。
あと最後に。なんか NaN には二種類あって、 QNaN と SNaN があるらしく、 QNaN ってのは quiet NaN の略で、 QNaN + 3 = QNaN となるようなものでして、つまりこう、 QNaN がどっかにあるとあちこちに伝播していくような物体です。一方 SNaN の方は signaling NaN の略で、 SNaN との演算は一番最初に言った無効操作ってヤツになるみたいです。で、普通 NaN というと QNaN のことらしく、 D の double.init とかも QNaN です。つまり、残念ながら、
double d; d += d;
なんてしただけでは、今まで言ってきた浮動小数点例外は飛んでくれません。もし SNaN なら飛んでくれたと思われるのですが。まぁ、「NaN に対しての比較」か「NaN を整数に変換」のどちらかが起きれば、 QNaN であっても例外が飛んでくれるので、まぁいずれは落ちてくれるように思います。
あと参考文献は例のごとく「IA-32 インテル・アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル」(このクソ長い名前なんとかならんのか)で、上巻に FPU 例外の話があって、中巻A に fldcw とかについて載ってました。
http://www.intel.co.jp/jp/developer/download/index.htm
追記: どうせなら SSE2 の例外もひろっておいた方がいいだろう、という指摘を頂きました。全くその通りだと思います。