メモ代わり。てきとーに。 いや、ですからてきとーですって。 2年前ぐらいにPythonあたりでメールくれた方、ごめんなさい。メール紛失してしまい無視した形になってしまいました。。。

2008年3月1日土曜日

[Apache][CodeReading] Apache2.2.8コードリーディング30日目

今日もApache2.2.8コードリーディング。

今日はちょっとだけ。

今日やったところは、

  • apr_cvt()/Apache2.2.8
らへん。

apr版printfの不動小数点数を文字列に変換するところ。

apr_cvt()
この関数は少々難しい。いかに自分が力不足か確認できた。。。

何をする関数かというと、与えられた浮動小数点数値を
与えられた精度で、文字列化するのだが、ここで小数点は文字列化しない。
小数点の位置は引数で関数に渡すパラメータにセットされ、呼び出し元に返される。
正負も引数で関数に渡すパラメータにセットされ、呼び出し元に返される。

たとえば、1234.5678という数値があったとする。
そして、eflags == 0、ndigits == 10でapr_cvt()をコールしたとする。
このときの関数の呼び出し結果は、

123456780000000
 

という文字列と、

4
 

という小数点の位置と、

0
 

という正負フラグ
になる。

あと、たとえば、0.9999という数値があったとして、
精度が4以下の場合、1に丸められる。


とりあえず、以下コード。

96 static char *apr_cvt(double arg, int ndigits, int *decpt, int *sign,
97 int eflag, char *buf)
98 {
99 register int r2;
100 double fi, fj;
101 register char *p, *p1;
102
103 if (ndigits >= NDIG - 1)
104 ndigits = NDIG - 2;
105 r2 = 0;
106 *sign = 0;
107 p = &buf[0];
108 if (arg < 0) {
109 *sign = 1;
110 arg = -arg;
111 }
112 arg = modf(arg, &fi);
113 p1 = &buf[NDIG];
114 /*
115 * Do integer part
116 */
117 if (fi != 0) {
118 p1 = &buf[NDIG];
119 while (p1 > &buf[0] && fi != 0) {
120 fj = modf(fi / 10, &fi);
121 *--p1 = (int) ((fj + .03) * 10) + '0';
122 r2++;
123 }
124 while (p1 < &buf[NDIG])
125 *p++ = *p1++;
126 }
127 else if (arg > 0) {
128 while ((fj = arg * 10) < 1) {
129 arg = fj;
130 r2--;
131 }
132 }
133 p1 = &buf[ndigits];
134 if (eflag == 0)
135 p1 += r2;
136 if (p1 < &buf[0]) {
137 *decpt = -ndigits;
138 buf[0] = '\0';
139 return (buf);
140 }
141 *decpt = r2;
142 while (p <= p1 && p < &buf[NDIG]) {
143 arg *= 10;
144 arg = modf(arg, &fj);
145 *p++ = (int) fj + '0';
146 }
147 if (p1 >= &buf[NDIG]) {
148 buf[NDIG - 1] = '\0';
149 return (buf);
150 }
151 p = p1;
152 *p1 += 5;
153 while (*p1 > '9') {
154 *p1 = '0';
155 if (p1 > buf)
156 ++ * --p1;
157 else {
158 *p1 = '1';
159 (*decpt)++;
160 if (eflag == 0) {
161 if (p > buf)
162 *p = '0';
163 p++;
164 }
165 }
166 }
167 *p = '\0';
168 return (buf);
169 }
 

引数は、
  • arg 変換対象の浮動小数点数値
  • ndigits 精度
  • decpt 出力パラメータ。小数点の位置
  • sign 出力パラメータ。正負。
  • eflag フォーマットが'e'か'E'の場合は、0以外の値を指定する。
  • buf 出力パラメータ。変換後文字列格納領域。
となっている。
さて、ややこしいので1つづつ見ていく。

99行目の

register int r2;
 

は、小数点の位置の算出に利用される。

103     if (ndigits >= NDIG - 1)
104 ndigits = NDIG - 2;
105 r2 = 0;
106 *sign = 0;
107 p = &buf[0];
 
 

あたりは特に難しくない。精度が、上限値(NDIG==80)を超えていたらセットしなおしている。
r2(小数点の位置)は0クリア。
sign(正負フラグ)も0クリア。
pを出力領域の先頭アドレスにセット。

108     if (arg < 0) {
109 *sign = 1;
110 arg = -arg;
111 }
  

ここは、変換対象の浮動小数点数が負の値で合った場合の処理。
signに1をセットし、argは正の値にセットしなおす。



112 arg = modf(arg, &fi);
 
 
ここで整数部と小数部を分離。
fiには整数部、argには小数部が入る。

113     p1 = &buf[NDIG];
 

ここで、p1に出力領域(buf)の最後尾のアドレスをセットしている。


117     if (fi != 0) {
118 p1 = &buf[NDIG];
119 while (p1 > &buf[0] && fi != 0) {
120 fj = modf(fi / 10, &fi);
121 *--p1 = (int) ((fj + .03) * 10) + '0';
122 r2++;
123 }
124 while (p1 < &buf[NDIG])
125 *p++ = *p1++;
126 }
 

ここで、整数部を文字列に変換する。
118行目は必要なコードでは無いと思う。
whileループは整数部が無くなるまで繰り返す。
120行目で10分の1にすることで、整数部の1の位を取り出している。
1の位はfjに10分の1の値で設定される。

そして、面白いのがここ。
121             *--p1 = (int) ((fj + .03) * 10) + '0';
 
 
10分の1にして取り出した1の位の値に0.03を加算後、10倍しているのだが、
この0.03が無いと結果が変わってしまう。
たとえば、1234.0という値を変換しようとした場合、0.03を加算しないと
p1には1134という値がセットされてしまう。
0.03を加算することで、doubleやfloatの問題を回避している。

124行目からは、121行目で作成した文字列をbufの先頭領域にコピーしている。


127     else if (arg > 0) {
128 while ((fj = arg * 10) < 1) {
129 arg = fj;
130 r2--;
131 }
132 }
 


は、整数部が無かった場合の処理で、
小数点以下第一位の位に0以外の数値を持ってきている。
位を移動した分だけr2も更新。

133     p1 = &buf[ndigits];
134 if (eflag == 0)
135 p1 += r2;
136 if (p1 < &buf[0]) {
137 *decpt = -ndigits;
138 buf[0] = '\0';
139 return (buf);
140 }
 
 
p1に指定精度になるようにbuf[ndigits]へのアドレスをセット。
135行目で、整数部の桁数分p1を後ろにずらしている。

141     *decpt = r2;
142 while (p <= p1 && p < &buf[NDIG]) {
143 arg *= 10;
144 arg = modf(arg, &fj);
145 *p++ = (int) fj + '0';
146 }
 

ここで、decptをセット。r2には小数点の位置(整数部の桁数)がセットされているので
その値をそのままdecptの指す先へセットしている。

142から146行目の処理では小数点以下の値を文字列化している。

仮にndigitsの値分の精度が必要なかった場合、あまった領域には'0'がセットされる。

147     if (p1 >= &buf[NDIG]) {
148 buf[NDIG - 1] = '\0';
149 return (buf);
150 }
 

で、領域を壊して突き進んでいたら、出力領域の最後尾にヌル文字をセットして
呼び出し元へ返る。


151     p = p1;
152 *p1 += 5;
153 while (*p1 > '9') {
154 *p1 = '0';
155 if (p1 > buf)
156 ++ * --p1;
157 else {
158 *p1 = '1';
159 (*decpt)++;
160 if (eflag == 0) {
161 if (p > buf)
162 *p = '0';
163 p++;
164 }
165 }
166 }
 

ここは、簡単に言うと、丸め処理をしている部分。
出力精度が4桁で、渡された値が99999であった場合、
このループに入るときのp1は、最後の'9'を指している。
そして、その'9'に5を加算する。('>'になる)
'>'は'9'より大きいので、このループが実行される。
このループの実行が終わると
99999であった値は100000に変わる。
それに伴ってdepctの値も更新。

最後にbuf[ndigits]にヌル文字をセットし、
bufを返す。



多分大体こんな感じの処理であっていると思う・・・。
うーむ。
ややこしいー。


おしまい。
.

0 コメント: