nostdlibでmruby/c
ch32funでは、コンパイル時にnostdlibと指定されている。 これは、サイズが大きな標準ライブラリを使わずに、マイコンでも動くような小さなバイナリにするためである。 その代わりに、同様の役割をする自前の関数を作っている。 しかしmruby/cでは、nostdlibの状態では使えない関数をいくつか使っており、その中でch32funが代替の関数を用意していないものを三つ使っているようである。 その三つとは、atol, atof, strpbrkである。
atolは、文字列を整数に変換するものだが、to_i用に定義されているmrbc_atoiで底を10にしたらほぼ同じ挙動をするので、これで置き換えることができる。 残りの二つの関数は、Floatを使う時に必要になるものである。 atofは文字列をFloatに変換するc_string_to_fで使われている。 これは、新たに自分で書かないといけない。
strpbrkはto_fで使われている。 1e1や10.0はFloatだけど、10は整数と判断されてしまうので、 Floatを文字列に変換したときに10では無く10.0になるようにするために、 strpbrk(buf,".e")として、’.‘または’e’を検索している。 それなら、strpbrkを使わずに、 以下のようにしても良いので、value.cの中身を書き換えた。
strchr(buf, '.') || strchr(buf, 'e')
つまり、あとはatofだけを何とかすれば、nostdlibでもコンパイルできるようになる。 通常のC言語のatofはかなり大きくなるらしいので、できるだけコンパクトになるようにプログラムをしてみた。
double atof(const char *s) {
int digit=0, sign=1;
unsigned count=0, point=0;
double res=0, factor=1;
for(;*s == ' ';s++);
if (*s == '-' || *s == '+') sign = (*s++ & 2)-1; //+:0x2b, -:0x2d
for (;*s;s++) {
unsigned v = *s-'0';
if (v <= 9) {
if (point) digit--;
if(count>>4){
digit++;
}else{
res = res * 10 + v;
if(count || v) count++;
}
} else {
if (*s == '.' && !point) {
point = 1;
continue;
}
if ((*s | 0x20)=='e') digit+=atol(s+1);
break;
}
}
int negative = (digit<0);
if(negative) digit=-digit;
for(;digit--;) factor*=10;
if(negative) factor=1/factor;
return factor * res * sign;
}
バグ探しにgoogleに助言を求めたりしたが、適切なコメントと、誤ったコメントの両方があるので、それをうまく取捨選択して取り入れた。 最初のスペースは飛ばしたが、tabはmrbc_atoiでも処理していなかったので、tabは考慮しないことにした。 主な工夫は以下の通りである。 符号の判定は、bit操作を用いることで、2バイトぐらい小さくなっているそうだ。 0から9までを判断させるのに、符号無しで'0’を引いて、9以下かどうかを見ることによって、条件判断を減らしている。 doubleの限界として、有効数字が16桁ぐらいなので、最初の0を飛ばして有効数字を数えて、15を超えたら桁のみを変化させることによって、誤差が大きくなるのを防いでいる。 最後に桁を処理する際には、何度も10で割ると誤差が大きくなるので、割り算は一度だけになるようにした。 コードゴルフっぽくて、面白いところもある。 このコードを誰か使ってくれないかな。 似たようなコードはすでにありそうだけど。
上記の三つの関数の問題をクリアして、nostdlibでmruby/cをコンパイルできるようになった。 これで、ch32funでFloatを扱う準備が整った。 しかし、問題が二つある。 一つはch32x035やch32v203では、浮動小数点がハードウェアで対応していないので、ソフトが大きくなってしまうことである。 もう一つは、ch32funのprintfは小数を扱えるようになっていないので、そのままでは表示が出来無いということである。 マイコンで浮動小数点を扱うのは大変なようだ。 いっそのこと、mruby/cでは固定小数点にしてしまうというのはどうなのだろう。 でも、Floatという名前も変えないと変かな。