teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

バグがあることを明記

2018/01/27 13:21

投稿

Eki
Eki

スコア429

answer CHANGED
@@ -108,6 +108,8 @@
108
108
  コードが正しい保証・バグなく動く保証はまったくないです。見苦しいコードですが参考に...ならないか。
109
109
  とりあえず追記しておきます。
110
110
 
111
+ さらなる追記: **バグがありました** 。例えば整数部・小数部とも省略されてしまった場合など。修正は容易ですが、今後修正していくことはできないため、鵜呑みにされることを避ける意味でも、バグがあるままにしておきます。これ以上の編集はしません。
112
+
111
113
  ```c
112
114
  #include <stdio.h>
113
115
  #include <stdlib.h>

1

より近い実装を追記

2018/01/27 13:21

投稿

Eki
Eki

スコア429

answer CHANGED
@@ -100,4 +100,273 @@
100
100
  ---
101
101
 
102
102
  このようなとき、どの部分でつまっているかを確かめるために printf デバッグが有効です。
103
- 私はまずソースの (==A==) の部分で n, i, o を表示させてみました。終了しない原因が integer() 関数にないことや、 integer() 関数の戻り値が正しいかどうかがここで確かめられます。次に入れるとしたら (==B==) の位置です。n と i を表示させながらループを観察します。そうすると `i == -1` のときにループから戻ってこないことが分かります。きっと pow() 関数の、負値のときにバグがあるのだろうと推測することができます。
103
+ 私はまずソースの (==A==) の部分で n, i, o を表示させてみました。終了しない原因が integer() 関数にないことや、 integer() 関数の戻り値が正しいかどうかがここで確かめられます。次に入れるとしたら (==B==) の位置です。n と i を表示させながらループを観察します。そうすると `i == -1` のときにループから戻ってこないことが分かります。きっと pow() 関数の、負値のときにバグがあるのだろうと推測することができます。
104
+
105
+ ## 追記
106
+
107
+ もっと `strtod()` の挙動に近い `str_to_d()` を書いてみました。。。
108
+ コードが正しい保証・バグなく動く保証はまったくないです。見苦しいコードですが参考に...ならないか。
109
+ とりあえず追記しておきます。
110
+
111
+ ```c
112
+ #include <stdio.h>
113
+ #include <stdlib.h>
114
+ #include <math.h>
115
+ #include <stdbool.h>
116
+
117
+ #define WHITESPACE " \t\n\v\f\r"
118
+
119
+ // str の中から ch を探し、インデックスを返す。
120
+ // 見つからないときは -1
121
+ int find_ch(char const *str, char ch);
122
+
123
+ // ch が空白文字 WHITESPACE に含まれているかを返す
124
+ bool is_whitespace(char ch);
125
+
126
+ // 文字を十進整数にする
127
+ // 範囲外の場合は -1
128
+ int dec_to_digit(char ch);
129
+
130
+ // 文字を十六進整数にする
131
+ // 範囲外の場合は -1
132
+ int hex_to_digit(char ch);
133
+
134
+ // 符号を取得し、 nptr を進める
135
+ // 符号は、+なら1、-なら-1
136
+ int sign_and_eat(char const **nptr);
137
+
138
+ // ch が大文字だったら、小文字にする
139
+ char to_lower(char ch);
140
+
141
+ // s と t の先頭 n 文字が (大文字小文字の区別なしで) 等しいか返す。
142
+ bool is_same_n(char const *s, char const *t, size_t n);
143
+
144
+ // 基数 (0x) を取得し、 nptr を進める
145
+ int base_and_eat(char const **nptr);
146
+
147
+ // 整数部分 (小数点 or 指数表示の e, p まで) を取得し、
148
+ // nptr を進める。
149
+ int integer_part_and_eat(char const **nptr, int base);
150
+
151
+ // 小数部分 (小数点から指数表示の e, p か末尾まで) を取得し、
152
+ // nptr を進める。
153
+ double fraction_part_and_eat(char const **nptr, int base);
154
+
155
+ // base の n 乗を求める。
156
+ double pwr(double base, int n);
157
+
158
+ // 文字列を double に変換する。
159
+ double str_to_d(char const *nptr, char const **endptr);
160
+
161
+ int find_ch(char const *str, char ch) {
162
+ for (int i = 0; str[i]; i++) {
163
+ if (str[i] == ch) return i;
164
+ }
165
+ return -1;
166
+ }
167
+
168
+ bool is_whitespace(char ch) {
169
+ return find_ch(WHITESPACE, ch) != -1;
170
+ }
171
+
172
+ int dec_to_digit(char ch) {
173
+ if ('0' <= ch && ch <= '9') return ch - '0';
174
+ return -1;
175
+ }
176
+
177
+ int hex_to_digit(char ch) {
178
+ // まず10進の範囲での変換を試みる
179
+ int dg = dec_to_digit(ch);
180
+ if (dg != -1) return dg;
181
+
182
+ // ダメなら文字を変換する
183
+ if ('A' <= ch && ch <= 'F') return 10 + (ch - 'A');
184
+ if ('a' <= ch && ch <= 'f') return 10 + (ch - 'a');
185
+ return -1;
186
+ }
187
+
188
+ int sign_and_eat(char const **nptr) {
189
+ // 符号がなければ + となるため
190
+ int sign = 1;
191
+ if (**nptr == '+') {
192
+ (*nptr)++;
193
+ } else if (**nptr == '-') {
194
+ sign = -1;
195
+ (*nptr)++;
196
+ }
197
+ return sign;
198
+ }
199
+
200
+ char to_lower(char ch) {
201
+ if ('A' <= ch && ch <= 'Z') {
202
+ ch = ch - 'A' + 'a';
203
+ }
204
+ return ch;
205
+ }
206
+
207
+ bool is_same_n(char const *s, char const *t, size_t n) {
208
+ for (size_t i = 0; i < n; i++) {
209
+ if (!s[i] || !t[i]) return false;
210
+ if (to_lower(s[i]) != to_lower(t[i])) return false;
211
+ }
212
+ return true;
213
+ }
214
+
215
+ int base_and_eat(char const **nptr) {
216
+ if (is_same_n(*nptr, "0x", 2)) {
217
+ // 0x 始まり == 16進数
218
+ *nptr += 2;
219
+ return 16;
220
+ }
221
+ return 10;
222
+ }
223
+
224
+ int integer_part_and_eat(char const **nptr, int base, int (*to_digit)(char)) {
225
+ int ans = 0;
226
+ int dg;
227
+ // 数字である限りは変換
228
+ for ( ; **nptr && (dg = to_digit(**nptr)) != -1; (*nptr)++) {
229
+ ans *= base;
230
+ ans += dg;
231
+ }
232
+ return ans;
233
+ }
234
+
235
+ double fraction_part_and_eat(char const **nptr, int base, int (*to_digit)(char)) {
236
+ double ans = 0;
237
+ int dg;
238
+ double pwr = 1. / base;
239
+ // 数字である限りは変換
240
+ for ( ; **nptr && (dg = to_digit(**nptr)) != -1; (*nptr)++) {
241
+ ans += pwr * dg;
242
+ pwr /= base;
243
+ }
244
+ return ans;
245
+ }
246
+
247
+ double pwr(double base, int n) {
248
+ double ans = 1;
249
+ if (n > 0) {
250
+ for (int i = 0; i < n; i++)
251
+ ans *= base;
252
+ } else if (n < 0) {
253
+ for (int i = 0; i < -n; i++)
254
+ ans /= base;
255
+ }
256
+ return ans;
257
+ }
258
+
259
+ double str_to_d(char const *nptr, char const **endptr) {
260
+ #define FINISH_WHEN_ENDS() do { if (!*nptr) goto finish; } while (false)
261
+ // とりあえず NAN, INFINITY に対応する
262
+ if (is_same_n(nptr, "nan", 3)) {
263
+ *endptr = nptr + 3;
264
+ return NAN;
265
+ } else if (is_same_n(nptr, "infinity", 8)) {
266
+ *endptr = nptr + 8;
267
+ return INFINITY;
268
+ }
269
+
270
+ char const *oldnptr;
271
+ double ans = 0;
272
+ int sign = 1;
273
+ // whitespace の読み飛ばし
274
+ while (*nptr && is_whitespace(*nptr)) nptr++;
275
+
276
+ // 符号ビット
277
+ FINISH_WHEN_ENDS();
278
+ sign = sign_and_eat(&nptr);
279
+
280
+ // 基数
281
+ FINISH_WHEN_ENDS();
282
+ int base; base = base_and_eat(&nptr);
283
+ char const *expchars; expchars = base == 10 ? "eE" : "pP";
284
+ int (*to_digit)(char); to_digit = base == 10 ? dec_to_digit : hex_to_digit;
285
+
286
+ // 数字または小数点でないなら終了
287
+ FINISH_WHEN_ENDS();
288
+ if (to_digit(*nptr) == -1 && *nptr != '.') goto finish;
289
+
290
+ // 整数部分読み込み
291
+ oldnptr = nptr;
292
+ ans += integer_part_and_eat(&nptr, base, to_digit);
293
+ bool read_integer; read_integer = oldnptr != nptr;
294
+
295
+ // 小数点または expchars でなければ終了
296
+ FINISH_WHEN_ENDS();
297
+ bool read_dot; read_dot = false;
298
+ if (*nptr == '.') {
299
+ read_dot = true;
300
+ nptr++;
301
+ } else if (find_ch(expchars, *nptr) == -1) {
302
+ goto finish;
303
+ }
304
+
305
+ // 小数部分読み込み
306
+ oldnptr = nptr; // 変化したか確かめるため、とりあえず保存しておく。
307
+ ans += fraction_part_and_eat(&nptr, base, to_digit);
308
+ bool read_fraction; read_fraction = oldnptr != nptr;
309
+
310
+ // expchars でないか、 expchars であっても、
311
+ // 整数部省略小数であってかつ1つも進んでいない場合は終了
312
+ FINISH_WHEN_ENDS();
313
+ if (find_ch(expchars, *nptr) == -1
314
+ || (!read_integer && read_dot && !read_fraction)) {
315
+ if (!read_integer && read_dot && !read_fraction) {
316
+ // なぜか標準はドットから返すので、 nptr を一つ戻しておく。
317
+ nptr--;
318
+ }
319
+ goto finish;
320
+ }
321
+ nptr++;
322
+
323
+ // 符号
324
+ FINISH_WHEN_ENDS();
325
+ int exp_sign; exp_sign = sign_and_eat(&nptr);
326
+
327
+ // 値
328
+ FINISH_WHEN_ENDS();
329
+ int exp_num; exp_num = integer_part_and_eat(&nptr, base, to_digit);
330
+ // 16進数の指数表示は底が2なので。
331
+ double mulr; mulr = pwr(base == 10 ? 10 : 2, exp_num * exp_sign);
332
+ ans *= mulr;
333
+
334
+ if (*nptr) {
335
+ // なぜか標準は e を食べないので、戻す。
336
+ nptr--;
337
+ }
338
+
339
+ finish:
340
+ if (endptr) *endptr = nptr;
341
+ return sign * ans;
342
+ }
343
+
344
+ int main(void)
345
+ {
346
+ char const *tests[] = {
347
+ "134", "-542", "4234.342", "-543.435", ".5453", "-.2343", "45e2", "453.34E+4", "545.3e-2", ".e2", "4.e2",
348
+ "0x42", "-0x53", "0x32.42", "-0x543.53", "0x.423", "-0x.432", "0x234p2", "0x512P+4", "0x515p-2", ".p3", "2.p3",
349
+ "asdf", "23fab", "0xabgd", "28.eg", "4.3.3",
350
+ "naN", "InFiNiTy",
351
+ NULL,
352
+ };
353
+
354
+ double const EPS = 1e-8;
355
+ double f, f_std;
356
+ char const *endp;
357
+ char *endp_std;
358
+ for (int i = 0; tests[i] != NULL; i++) {
359
+ f = str_to_d(tests[i], &endp);
360
+ f_std = strtod(tests[i], &endp_std);
361
+ int num_equal = fabs(f - f_std) < EPS;
362
+ int rest_equal = endp == endp_std;
363
+
364
+ if (!num_equal || !rest_equal) {
365
+ printf("[%s] %f : %f (rest: %s : %s), (%d, %d)\n",
366
+ tests[i], f, f_std, endp, endp_std, num_equal, rest_equal);
367
+ }
368
+ }
369
+
370
+ return 0;
371
+ }
372
+ ```