CASE Study : SPARC

浮動小数点プログラムを組んでみよう

ファイヤー書きかけ。

概要

実際にどのような動作をするのか 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 
#include 
#include 
#include 
#include 

void print_bit_double(double x);
void sample_handler(int sig, int code, struct sigcontext *scp, char *addr);

void sample_handler(int sig,int code,struct sigcontext *scp,char *addr){
	printf("ieee exception code %x occurred at pc %X \n", code, scp->sc_pc);
}

int main(){

	double x;
	int y;
	char *out;

	sigfpe_handler_type hdl;
	hdl = (sigfpe_handler_type) sample_handler;
	if(ieee_handler("set","underflow",hdl)!=0)
		printf("exception handler error\n");

	x = 4.0/3.0;
	printf("x = 4/3 = ");
	print_bit_double(x);
	y = 1;
	y = (int) x;
	printf("y = (int) x = %x\n",y);
    ieee_retrospective(stderr);
	ieee_flags("clear","exception","all",&out);
	
	printf("\n");

	y = 0x7fffffff;
	x = (double) y;
	printf("x = int_max = ");
	print_bit_double(x);

	x = x + 1.0;
	printf("x = int_max + 1.0 = ");
	print_bit_double(x);
    ieee_retrospective(stderr);
	y = 1; /* blank */
	y = (int) x;
	printf("y = (int) x = %x\n",y);
    ieee_retrospective(stderr);
	ieee_flags("clear","exception","all",&out);

	printf("\n");

	x = max_normal();
	printf("x = max_normal = ");
	print_bit_double(x);
	y = 1;
	y = (int) x;
	printf("y = (int) x = %x\n",y);
    ieee_retrospective(stderr);
	ieee_flags("clear","exception","all",&out);

	printf("\n");

	x = min_normal();
	printf("x = min_normal = ");
	print_bit_double(x);
	y = 1;
	y = (int) x;
	printf("y = (int) x = %x\n",y);
    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 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


Inf について

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 以外の例外は起こらない。


アンダーフローについて

Q1 : 規格書では、トラップがある時とない時では、アンダーフローの出し方 が違うと書いてあるが、SPARC でも本当に実装されているのか?
A1 : 実装されている。規格書では、割り込みがかかる場合と、ただフラグを 立てるだけの場合とで、アンダーフローの発生の仕方を変えている。

フラグを立てるだけのときは、値が正規化数で表現できない程小さく ( Tininess )、かつ精度が落ちている ( Loss of Accuracy ) ときのみとしてい るが、トラップを発生させるときは、Tininess の条件だけクリアしていれば よく、Loss of Accuracy は関係ない。

ちなみに、Tininess かどうか、 Loss of Accuracy かどうかの基準は、各々 2 つのやり方がある。規格書参照。

プログラム ( しばしまたれい )
/* */

Q2 : 規格書に書かれている「between ±2Emin」 に ±2Emin は含まれるのか?
A2 : 「以下」なのか「未満」なのかという悩みに似ている。しかしもはやこれは英 語の問題。本来の英語の意味は between は± 2Emin を含むようだが実際やってみると、当然と いうかなんというか、含まれていなかった。

Q3 : デノーマル同士の足し算で結果がデノーマルになったらどうなる?
A3 :


NaN について

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; 


一歩前へ[Back]
僕のホームへ[Home]
研究室のページへ[Amano Lab.]
Takahiro Kawaguchi kawaguti@aa.cs.keio.ac.jp
Last modified: Dec. 20, 1997