たった一つの冴えた NAN の作り方
- http://www.kt.rim.or.jp/~kbk/zakkicho/10/zakkicho1003b.html#D20100311-2
- http://www.hi-matic.org/diary/index.cgi?20100312#12-1
あたりの話題。
で、いきなりタイトル否定すると、「たった一つ」はたぶんありませんな。どこで聞いたか忘れたけど、「ポータブルなコードとは、環境の数だけ #ifdef でがんばったコードのことである」とか。
DECLSPEC_SELECTANY extern const float FLOAT_POSITIVE_INFINITY = ((float)(1e308 * 10));
http://blogs.msdn.com/oldnewthing/archive/2010/03/05/9973225.aspx
DECLSPEC_SELECTANY extern const float FLOAT_NEGATIVE_INFINITY = ((float)(-1e308 * 10));
DECLSPEC_SELECTANY extern const float FLOAT_NaN = ((float)((1e308 * 10)*0.));
これは知らんかった。
まぁこれ fpgetmask(3) で FP_X_DZ たってる場合ははずす必要あるのと(FreeBSD ってまだそうだっけ?)
http://www.hi-matic.org/diary/index.cgi?20100312#12-1
FreeBSD 8.0 だとデフォルトではたってませんね。また、明示的に fpsetmask(FP_X_DZ) しても、0.0/0.0 は例外があがらないようです。1.0/0.0 だとあがるので、INFINITY はだめですな。
VisualC++ の場合は C2124なので、分母の0.0を定数で書けませんのでいちど変数に入れる必要が。
http://www.hi-matic.org/diary/index.cgi?20100312#12-1
こっちは Ruby でも懸案になりましたね。
で、現在 Ruby でどうしているかは以前の記事を。これは「どうせほとんどの環境では double = IEEE 754 binary64 なんだから、とりあえずエンディアンでだけ分岐。これで動かなかったら報告が来てから考える」と割り切っています。
で、ここで終わるとただのまとめなので -O3 でコンパイルして逆アセンブルしてみると、以下のようになりました。自力でバイナリ作った方が高速ですな。まぁ、NAN や INFINITY の演算を高速にやるニーズなんて皆無だろうのでどうでもいい気もする。
#define N1 (*(double *)rb_nan) const unsigned char rb_nan[] = "\x00\x00\x00\x00\x00\x00\xf8\x7f"; 40057f: f2 0f 59 05 91 00 00 mulsd 145(%rip),%xmm0 # 400618 <rb_nan> 400586: 00 #define N2 (0.0/0.0); 40057b: 66 0f 57 c9 xorpd %xmm1,%xmm1 400587: f2 0f 5e c9 divsd %xmm1,%xmm1 40058b: f2 0f 59 c1 mulsd %xmm1,%xmm0