今日も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を返す。
多分大体こんな感じの処理であっていると思う・・・。
うーむ。
ややこしいー。
おしまい。
.