実際にどのような動作をするのか SPARC ( あるしおーね )で確かめる。 人のプログラムを読むよりは、自分で書いた方が納得する。
特に、フォーマットの変換、例外フラグの立て方、Inf や NaN 等の IEEE 754 まわりのきわどい部分について調べていく。 また、明らかに規格書に載っているものでも、できるだけやってみることにする。
Q1 : 整数から単精度への変換時、精度が落ちる可能性があるが、そのときの
振舞いは?
A1 : Integer から Single にキャストする形でやってみたところ、精度が落
ちるときには丸めモードに従って丸めを行うが、不正確の例外は出さない
信じ難いことに、整数から単精度のとき、桁落ちがあるにも関わらず例外が出 ない。
なんと今使っている SPARC では不正確の例外を正しく実装していないらしい。 例えば
double x; x = max_normal() + min_normal(); |
とやったところで、例外が起きない。不正確の例外が起きるのは
double x; x = max_normal() + max_normal(); |
のように、オーバーフローが起きたときだけ。あとアンダーフローも。
但し、decimal_to_single() , decimal_to_double() , decimal_to_extended() という命令も実装されており、floatingpoint.h の中に定義されている
typedef struct { enum fp_class_type fpclass; int sign; int exponent; decimal_string ds; /* Significand - each char contains an ascii * digit, except the string-terminating ascii * null. */ int more; /* On conversion from decimal to binary, != 0 * indicates more non-zero digits following * ds. */ int ndigits;/* On fixed_form conversion from binary to * decimal, contains number of digits * required for ds. */ } decimal_record; |
という構造体を用いて 10 進数を指定すると適切なフォーマットに直してくれる。 これは例外も出してくれるとは書いてある。 未確認だが嘘だろう。 結局 int から直接変換するのはキャスト以外無いらしい。
ASCA プロセッサでは DLX 命令を基礎としており、その中にコンバートの命令 が存在する。それは例外を出すことにする。この仕様の確定は規格書を読んで から。
プログラム ( 一部省略、結果を見て類推すべし )
#include < stdio.h > #include < math.h > #include < sys/ieeefp.h > #include < floatingpoint.h > void print_bit_float(float x); int main(){ float x; char *out; unsigned int a; a = 0x00000001; printf("a= %08x\n",a); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); a = 0x00ffffff; printf("a= %08x\n",a); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); a = 0x01000000; printf("a= %08x\n",a); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); a = 0x01fffffd; printf("a= %08x\n",a); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); a = 0x01fffffd; printf("a= %08x",a); ieee_flags("set","direction","positive",&out); printf(" ( ROUNDMODE = POSITIVE )\n"); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); ieee_flags("set","direction","nearest",&out); a = 0xc0000000; printf("a= %08x\n",a); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); a = 0xffffff00; printf("a= %08x\n",a); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); a = 0xffffffff; printf("a= %08x\n",a); x = (float) a; print_bit_float(x); ieee_retrospective(stderr); return 0; } void print_bit_float(float x){ int i; int *up; up = (int*)&x; for(i=0;i<1;i++) printf("%d",((*up)>>(31-i))%2); printf("_"); for(i=1;i<9;i++) printf("%d",((*up)>>(31-i))%2); printf("_"); for(i=9;i<32;i++) printf("%d",((*up)>>(31-i))%2); printf("( %08x : %g )\n",*up,x); } |
結果
a= 00000001 x= 0_01111111_00000000000000000000000( 3f800000 : 1 ) a= 00000003 x= 0_10000000_10000000000000000000000( 40400000 : 3 ) a= 00000007 x= 0_10000001_11000000000000000000000( 40e00000 : 7 ) a= 0000000f x= 0_10000010_11100000000000000000000( 41700000 : 15 ) a= 00ffffff x= 0_10010110_11111111111111111111111( 4b7fffff : 1.67772e+07 ) a= 01000000 x= 0_10010111_00000000000000000000000( 4b800000 : 1.67772e+07 ) a= 01000001 x= 0_10010111_00000000000000000000000( 4b800000 : 1.67772e+07 ) a= 01000010 x= 0_10010111_00000000000000000001000( 4b800008 : 1.67772e+07 ) a= 01000100 x= 0_10010111_00000000000000010000000( 4b800080 : 1.67775e+07 ) a= 01800000 x= 0_10010111_10000000000000000000000( 4bc00000 : 2.51658e+07 ) a= 01fffffd x= 0_10010111_11111111111111111111110( 4bfffffe : 3.35544e+07 ) a= 01fffffd ( ROUNDMODE = POSITIVE ) x= 0_10010111_11111111111111111111111( 4bffffff : 3.35545e+07 ) a= 01fffffe x= 0_10010111_11111111111111111111111( 4bffffff : 3.35544e+07 ) a= 01ffffff x= 0_10011000_00000000000000000000000( 4c000000 : 3.35544e+07 ) a= 01ffffff ( ROUNDMODE = TOZERO ) x= 0_10010111_11111111111111111111111( 4bffffff : 3.35544e+07 ) a= c0000000 x= 0_10011110_10000000000000000000000( 4f400000 : 3.22123e+09 ) a= 80000000 x= 0_10011110_00000000000000000000000( 4f000000 : 2.14748e+09 ) a= ffffff00 x= 0_10011110_11111111111111111111111( 4f7fffff : 4.29497e+09 ) a= ffffffff x= 0_10011111_00000000000000000000000( 4f800000 : 4.29497e+09 ) |
Q2 : 浮動小数点数から整数への変換時のオーバーフロー・アンダーフローの
振る舞いは?
A2 : 整数に対する命令だが、ちゃんと例外が発生する。
確認した例外は、Invalid Operation である。表示では、Invalid Operand と
なっているが、規格書では Operand ではなく Operation となっているので、
このページでは Operation で名前を統一する。オーバーフロー、アンダーフ
ローは発生しない。不正確例外は発生しなかったが、これもこの SPRAC の仕
様落ちだろう。規格書の pp.14 の Invalid Operation の第 7 項で
Conversion of a binary floating-point number to an integer or decimal format when overflow, infinity, or NaN precludes a faithful representation in that format and this cannot otherwise be signaled.
と明記されており、整数への変換だからといって、浮動小数点の例外が発生し ないわけではないようだ。
プログラム
/* conv01.c */ #include |
result conv01 x = 4/3 = 3ff55555 55555555 ( 1.33333 ) y = (int) x = 1 x = int_max = 41dfffff ffc00000 ( 2.14748e+09 ) x = int_max + 1.0 = 41e00000 00000000 ( 2.14748e+09 ) y = (int) x = 7fffffff Warning: the following IEEE floating-point arithmetic exceptions occurred in this program and were never cleared: Invalid Operand; x = max_normal = 7fefffff ffffffff ( 1.79769e+308 ) y = (int) x = 7fffffff Warning: the following IEEE floating-point arithmetic exceptions occurred in this program and were never cleared: Invalid Operand; x = min_normal = 00100000 00000000 ( 2.22507e-308 ) y = (int) x = 0 |
Q1 : Inf をメモリにロードしただけでオーバーフローは起きるのか?
A1 : やってみたら起きない。
Q2 : Inf について四則演算を行ったときにオーバーフローは起きるのか?
A2 :
について実際にやってみたところ、Inf - Inf と Inf * 0 と Inf / Inf は Invalid Operation の例外を起こして NaN を返す。 それ以外は Inf を「完全な Inf」として考えた場合の妥当な答えを返し、例 外は発生させない。
Q3 : Inf / 0 はどんな例外が起きる?
A3 :
どの例外も起きない
前問の延長上にある問題で、Inf を「完全な Inf 」として計算しているので、
オーバーフローは起こらないことが想像できる、が、
注目すべきは、 Inf / 0 のときに 0 除算例外を起こさないことである。
これは、規格書 IEEE 754 pp.14 にも書かれていて、FINITE NONZERO NUMBER が被除
数のときにオーバーフローと明記されている。
If the divisor is zero and the dividend is a finite nonzero number, then the division by zero exception shall be signaled. The result, when no trap occurs, shall be a correctly signed INF.
オーバーフローについては、これが全文なんだけど … これだけで本当にきっ ちり規定できるのかしら?
プログラムの一部 ( 似たような文が延々と書かれているので、省略してある )
/* int01.c */ #include < stdio.h > #include < math.h > #include < sys/ieeefp.h > #include < floatingpoint.h > void print_bit_double(double x); int main(){ double x; char *out; sigfpe_handler_type handler; /* dummy */ if (ieee_handler("clear", "all", handler) != 0) printf("exception handler error\n"); x = infinity(); printf("infinity = "); print_bit_double(x); ieee_retrospective(stderr); x = infinity(); x = x + 0.00; printf("infinity + 0.00 = "); print_bit_double(x); ieee_retrospective(stderr); x = infinity(); x = x - 0.00; printf("infinity - 0.00 = "); print_bit_double(x); ieee_retrospective(stderr); x = infinity(); x = x - x; printf("infinity - infinity = "); print_bit_double(x); ieee_retrospective(stderr); ieee_flags("clear", "exception", "all", &out); x = infinity(); x = x * 0.00; printf("infinity * 0.00 = "); print_bit_double(x); ieee_retrospective(stderr); ieee_flags("clear", "exception", "all", &out); x = infinity(); x = 1 / x; printf("1 / infinity = "); print_bit_double(x); ieee_retrospective(stderr); x = infinity(); x = x / x; printf("infinity / infinity = "); print_bit_double(x); ieee_retrospective(stderr); return 0; } void print_bit_double(double x){ int *up,*lp; up = (int*)&x; lp = (int*)(((int)(&x))+4); printf("%08x %08x ( %g ) \n",*up,*lp,x); } |
結果
result of int01 infinity = 7ff00000 00000000 ( Inf ) infinity + 0.00 = 7ff00000 00000000 ( Inf ) infinity + 2.00 = 7ff00000 00000000 ( Inf ) infinity + infinity = 7ff00000 00000000 ( Inf ) infinity - 0.00 = 7ff00000 00000000 ( Inf ) infinity - 1.00 = 7ff00000 00000000 ( Inf ) infinity - infinity = 7fffffff ffffffff ( NaN ) Warning: the following IEEE floating-point arithmetic exceptions occurred in this program and were never cleared: Invalid Operand; infinity * 1.00 = 7ff00000 00000000 ( Inf ) infinity * 0.00 = 7fffffff ffffffff ( NaN ) Warning: the following IEEE floating-point arithmetic exceptions occurred in this program and were never cleared: Invalid Operand; infinity * infinity = 7ff00000 00000000 ( Inf ) infinity / 1 = 7ff00000 00000000 ( Inf ) infinity / 2 = 7ff00000 00000000 ( Inf ) infinity / 0 = 7ff00000 00000000 ( Inf ) 0 / infinity = 00000000 00000000 ( 0 ) 1 / infinity = 00000000 00000000 ( 0 ) infinity / infinity = 7fffffff ffffffff ( NaN ) Warning: the following IEEE floating-point arithmetic exceptions occurred in this program and were never cleared: Invalid Operand; |
当初思ったより、オーバーフローフラグは立ちにくいということがわかった。 結局、オペランドそのものが Inf のときは NaN 以外の例外は起こらない。
フラグを立てるだけのときは、値が正規化数で表現できない程小さく ( Tininess )、かつ精度が落ちている ( Loss of Accuracy ) ときのみとしてい るが、トラップを発生させるときは、Tininess の条件だけクリアしていれば よく、Loss of Accuracy は関係ない。
ちなみに、Tininess かどうか、 Loss of Accuracy かどうかの基準は、各々 2 つのやり方がある。規格書参照。
プログラム ( しばしまたれい )
/* */ |
Q2 : 規格書に書かれている「between ±2Emin」
に ±2Emin は含まれるのか?
A2 :
「以下」なのか「未満」なのかという悩みに似ている。しかしもはやこれは英
語の問題。本来の英語の意味は between は±
2Emin を含むようだが実際やってみると、当然と
いうかなんというか、含まれていなかった。
Q3 : デノーマル同士の足し算で結果がデノーマルになったらどうなる?
A3 :
Q1 : Signaling NaN ( s ) と Quiet NaN ( s ) は、関数 quiet_nan(long n) と
signaling_nan(long n) で具体的にどのようなビット列になっているのか?
A1 : 以下の結果の通りのビット列。このようにした理由を推測すると
だろう。なお、引数 long n は将来の拡張のためにあるもので、現在は使 用されていない。
プログラム
/* nan01.c */ #include < stdio.h > #include < math.h > #include < sys/ieeefp.h > #include < floatingpoint.h > void print_bit_double(double x); int main(){ double x; char *out; x = quiet_nan(0); printf("quiet_nan = "); print_bit_double(x); ieee_retrospective(stderr); x = signaling_nan(0); printf("signaling_nan = "); print_bit_double(x); ieee_retrospective(stderr); return 0; } void print_bit_double(double x){ int *up,*lp; up = (int*)&x; lp = (int*)(((int)(&x))+4); printf("%08x %08x ( %g ) \n",*up,*lp,x); } |
結果
result nan01 quiet_nan = 7fffffff ffffffff ( NaN ) signaling_nan = 7ff00000 00000001 ( NaN ) |
Q2 : NaN の符号については、規格で規定されていないが、実際にはどうしているのか?
A2 : 0 * Inf を符号つきでやってみたところから推測して、符号は常に 0 に
なっているっぽい
プログラム
/* nan02.c */ #include < stdio.h > #include < math.h > #include < sys/ieeefp.h > #include < floatingpoint.h > void print_bit_double(double x); int main(){ double x,y; char *out; x = -0.0; printf("-0.0 = "); print_bit_double(x); ieee_retrospective(stderr); y = +infinity(); printf("+infinity = "); print_bit_double(y); ieee_retrospective(stderr); x = x * y; printf("-0.0 * +infinity = "); print_bit_double(x); ieee_retrospective(stderr); ieee_flags("clear","exception","all",&out); x = +0.0; printf("+0.0 = "); print_bit_double(x); ieee_retrospective(stderr); y = +infinity(); printf("+infinity = "); print_bit_double(y); ieee_retrospective(stderr); x = x * y; printf("+0.0 * +infinity = "); print_bit_double(x); ieee_retrospective(stderr); return 0; } void print_bit_double(double x){ int *up,*lp; up = (int*)&x; lp = (int*)(((int)(&x))+4); printf("%08x %08x ( %g ) \n",*up,*lp,x); } |
結果
result nan02 -0.0 = 80000000 00000000 ( -0 ) +infinity = 7ff00000 00000000 ( Inf ) -0.0 * +infinity = 7fffffff ffffffff ( NaN ) Warning: the following IEEE floating-point arithmetic exceptions occurred in this program and were never cleared: Invalid Operand; +0.0 = 00000000 00000000 ( 0 ) +infinity = 7ff00000 00000000 ( Inf ) +0.0 * +infinity = 7fffffff ffffffff ( NaN ) Warning: the following IEEE floating-point arithmetic exceptions occurred in this program and were never cleared: Invalid Operand; |