たった一つの冴えた NAN の作り方

あたりの話題。

で、いきなりタイトル否定すると、「たった一つ」はたぶんありませんな。どこで聞いたか忘れたけど、「ポータブルなコードとは、環境の数だけ #ifdef でがんばったコードのことである」とか。

DECLSPEC_SELECTANY extern const float FLOAT_POSITIVE_INFINITY = ((float)(1e308 * 10));
DECLSPEC_SELECTANY extern const float FLOAT_NEGATIVE_INFINITY = ((float)(-1e308 * 10));
DECLSPEC_SELECTANY extern const float FLOAT_NaN = ((float)((1e308 * 10)*0.));

http://blogs.msdn.com/oldnewthing/archive/2010/03/05/9973225.aspx

これは知らんかった。

まぁこれ 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