K&Rを読もう(2) 1.2 - 1.3 温度変換
K&Rを見たとき、何処かで見た事のあるプログラムが多数存在する。温度変換もそのひとつ。正規表現のバイブル「ふくろう本」の例題でも使われている(読んでないけど)
由緒正しき温度変換をボクノスなりに読み解こうと思う。
整数の割り算
温度変換のポイントは整数の割り算だと思う。
1/2をやろう。
printf("%d\n", 1 / 2); /* 0 */
結果は0。整数演算では結果も整数になる。なぜなら、「整数と浮動少数では扱うレジスタが別だから」である。
0.5を表示させるときは、
printf("%f\n", 1.0 / 2.0); /* 0.50000 */
とする。
バグバグなprintf
変なの出てきた。
printf("%f\n", 1 / 2); /* -0.234262 */
うは、ひでぇ。出てくる数値はバラバラになる。
「整数と浮動少数では扱うレジスタが別だから」というのを確認しよう。
原因を探る
何故正しく表示されないのか原因を探る。
printfで単純に1を表示させる。
printf("%d\n", 1); printf("%f\n", 1.0);
gcc -g付きでコンパイルして、objdump -dSで逆アセンブルする。
printf("%d\n", 1); 8048395: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp) 804839c: 00 804839d: c7 04 24 c8 84 04 08 movl $0x80484c8,(%esp) 80483a4: e8 0f ff ff ff call 80482b8 <printf@plt> printf("%f\n", 1.0); 80483a9: dd 05 d0 84 04 08 fldl 0x80484d0 80483af: dd 5c 24 04 fstpl 0x4(%esp) 80483b3: c7 04 24 cc 84 04 08 movl $0x80484cc,(%esp) 80483ba: e8 f9 fe ff ff call 80482b8 <printf@plt>
fldlは定数のロード、fstpでpush。
出てくるバイナリは全く別。
浮動少数の場合、扱うレジスタが別であることが確認できる。
温度変換
では、温度変換プログラムしよう。まずは結果。
fahr celsius 0 -17 100 37 200 93 300 148 300 148 200 93 100 37 0 -17 0 -17.8 100 37.8 200 93.3 300 148.9 celsius fahr 0 32.0 100 212.0 200 392.0 300 572.0
SICP中なので、関数ポインタを多用する。
#include <stdio.h> #include <stdlib.h> typedef void (*proc_t)(int); static void display_columun(char *a, char *b); static void iter(int start, int end, int step, proc_t proc); static void reverse(int start, int end, int step, proc_t proc); static void fahr_to_int_celsius(int fahr); static void fahr_to_float_celsius(int fahr); static void celsius_to_float_fahr(int celsius); int main(void) { int start = 0; int end = 300; int step = 100; display_columun("fahr", "celsius"); /* F to int C */ iter(start, end, step, (proc_t) fahr_to_int_celsius); puts(""); /* F to int C (reverse)*/ reverse(end, start, step, (proc_t) fahr_to_int_celsius); puts(""); /* F to float C */ iter(start, end, step, (proc_t) fahr_to_float_celsius); puts(""); display_columun("celsius", "fahr"); /* C to float F */ iter(start, end, step, (proc_t) celsius_to_float_fahr); exit(EXIT_SUCCESS); } static void display_columun(char *a, char *b) { printf("%8s%8s\n", a, b); } static void iter(int start, int end, int step, proc_t proc) { int i; for (i = start; i <= end; i += step) proc(i); } static void reverse(int start, int end, int step, proc_t proc) { int i; for (i = start; i >= end; i -= step) proc(i); } static void fahr_to_int_celsius(int fahr) { printf("%8d%8d\n", fahr, 5 * (fahr - 32) / 9); } static void fahr_to_float_celsius(int fahr) { printf("%8d%8.1f\n", fahr, 5 * ((float) fahr - 32) / 9); } static void celsius_to_float_fahr(int celsius) { printf("%8d%8.1f\n", celsius, (float) celsius * 9 / 5 + 32); }
もっと抽象化出来る気がする。
まとめと課題
- レジスタを意識して、キャストしよう。
- 関数ポインタプログラミングにはまだまだ修行が必要そうだ。
- FPU命令を覚えたいと思う。