プログラム: number001.c,
#include<stdio.h>
int main(void)
{
FILE *fp;
int x[ ] = {1,2,-1,4,5,65535,7,65536,65537,10};
if ((fp = fopen("test.dat", "w+")) == NULL) {
printf("file open error!!\n");
exit(-1);
}
fwrite(x, sizeof(int), 10, fp); /* write 10 numbers */
fclose(fp);
}
コンパイルして走らせてみる.
% gcc number001.c % ./a.out % ls -l -rw-r--r-- 1 date date 40 May 20 11:41 test.dattest.dat というファイルができているか確認する. この40 という数字は,ファイルの大きさが 40バイトという意味. 10個の整数(int)型のデータを書き込んだ結果が40バイト. 1個の数字あたり4バイトが費やされていることが分かる.
% od -t x1z -A x test.dat 000000 01 00 00 00 02 00 00 00 ff ff ff ff 04 00 00 00 >................< 000010 05 00 00 00 ff ff 00 00 07 00 00 00 00 00 01 00 >................< 000020 01 00 01 00 0a 00 00 00 >........< 0000281 は 01 00 00 00 と表現されており, -1 は ff ff ff ff と(2の補数)表現されており, 10 は 0a 00 00 00 と表現されていることが分かる. 同様に 65535 は ff ff 00 00 と表現されており, 65536 は 00 00 01 00, 65537 は 01 00 01 00 と表現されていることが分かる.
これを見て,数の並び方がおかしいと感じるかもしれない. 本来 AB CD EF GH という数が GH EF CD AB の順に格納されている (ここでは4ビットの数を A,B,C 等とした). これは使用している CPU に依存して決まっている. インテルの CPU では「リトルエンディアン」と呼ばれる 方式が採用されている. (エンディアン)
なお, 32bit で一つの整数を表現しているが,正の数,負の数をともにまんべんなく 表現しようとすると 32 bit では ± 231 = ± 2,197,483,648 までしか区別できない.
int x[] = {123,-299,65535,4,5,6,7,8,9,10};
#include<stdio.h>
int main(void)
{
printf("sizeof char= %d\n", sizeof(char));
printf("sizeof short int = %d\n", sizeof(short int));
printf("sizeof long int = %d\n", sizeof(long int));
printf("sizeof int = %d\n", sizeof(int));
printf("sizeof double= %d\n", sizeof(double));
printf("sizeof long double= %d\n", sizeof(long double));
}
% gcc number002.c % ./a.out sizeof char= 1 sizeof short int = 2 sizeof long int = 4 sizeof int = 4 sizeof double= 8 sizeof long double= 12
man コマンドで調べると
% man fwrite size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); .... The function fwrite writes nmemb elements of data, each size bytes long,ということなので,
fwrite(x, sizeof(double), 1, fp); /* write a number x[0] */この場合, x という配列に入っている 1個あたり sizeof(double) バイトの データを1個,ファイルに書き込む,ということ.
fwrite(x, sizeof(double), 2, fp); /* write a number x[0] */1を2にすれば x[0], x[1] が書き込まれる.
(0.5)10 = 0.1
(0.25)10 = 0.01
(0.125)10 = 0.001
(0.75)10 = 0.11
(0.1)10 = 0.0001100110011 = 0.1100 1100 1100 x 2 -3
= 1.1001 1001 1001 x 2 -4
プログラム: number003.c,
#include<stdio.h>
int main(void)
{
FILE *fp;
double x[] = {0.0, 1.0, -1.0, 0.5, -0.5, 0.75, -0.75, 0.1};
if ((fp = fopen("test.dat", "w+")) == NULL) {
printf("file open error!!\n");
exit(-1);
}
fwrite(x, sizeof(double), 5, fp); /* write a number x[0] */
fclose(fp);
}
%od -t x1z -A x test.dat 000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0 3f >...............?< 000010 00 00 00 00 00 00 f0 bf 00 00 00 00 00 00 e0 3f >...............?< 000020 00 00 00 00 00 00 e0 bf 00 00 00 00 00 00 e8 3f >...............?< 000030 00 00 00 00 00 00 e8 bf 9a 99 99 99 99 99 b9 3f >...............?< 000040
+1.0 は 00 00 00 00 00 00 f0 3f と表現されている.
-1.0 は 00 00 00 00 00 00 f0 bf と表現されている.
f0 3f = 1111 0000 0011 1111
f0 bf = 1111 0000 1011 1111
1.0, -1.0 は符号部以外は同じ表現のはず. これで,符号を表現するビットがどれか分かった.
整数の場合と同じで AB CD EF GH と数があった場合に, GH EF CD AB という順で格納されているようだ. この解釈をすれば, 1.0 の指数部 e の 11 bit は 011 1111 1111 = 1023 で. 仮数部は 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 である. 仮数部 f はすべて 0 である. (1.0)10 = 1.0 x 20 であるので, 仮数部 f が 0 で指数部 e は 1023 になっていた.
別の数を見てみよう.
+0.5 は 00 00 00 00 00 00 e0 3f と表現されている.
+1.0 は 00 00 00 00 00 00 f0 3f と表現されている.
0.5 =2-1 , 1.0= 20 は仮数部以外は同じ表現のはず.
e0 3f = 1110 0000 0011 1111
f0 3f = 1111 0000 0011 1111
なるほど! では最後に 0.1 の2進数表現はどうなっているか見てみよう.
(0.1)10 は bf 9a 99 99 99 99 99 b9 3f と表現されている.
しようがないので全部かきおこす.
9a 99 99 99 99 99 b9 3f = 1001 1010 1001 1001 1001 1001 1001 1001 1001 1001 1011 1001 0 011 1111
(0.1)10 = 1.1001 1001 1001 x 2 -4
であるので,
仮数部 f の 52 bit は 1001 1001 1001 1001 1001 ... のはず...
最後だけ 9a になっている.
指数部 e は e-1023 = -4 つまり e = 1019 が 11 bit で記述され
ているはず. (1019)10 =
1023- 4 = 011 1111 1011
緑の部分が指数部! 赤の部分が符号.
自分の調べている数の符号を変えると,符号ビットだけ 反転するので,違いを調べれば,符号ビットがどこかは分かります. 同様に自分の調べている数字を 2 倍するとか,1だけ小さい数が どのように表現されているか,もとの数字の場合と 比較すれば,少なくとも,どこが指数部の一部を担当しているか, どこが仮数部の一部を担当しているかは分かります.