回答編集履歴

2

バグがあることを明記

2018/01/27 13:21

投稿

Eki
Eki

スコア429

test CHANGED
@@ -218,6 +218,10 @@
218
218
 
219
219
 
220
220
 
221
+ さらなる追記: **バグがありました** 。例えば整数部・小数部とも省略されてしまった場合など。修正は容易ですが、今後修正していくことはできないため、鵜呑みにされることを避ける意味でも、バグがあるままにしておきます。これ以上の編集はしません。
222
+
223
+
224
+
221
225
  ```c
222
226
 
223
227
  #include <stdio.h>

1

より近い実装を追記

2018/01/27 13:21

投稿

Eki
Eki

スコア429

test CHANGED
@@ -203,3 +203,541 @@
203
203
  このようなとき、どの部分でつまっているかを確かめるために printf デバッグが有効です。
204
204
 
205
205
  私はまずソースの (==A==) の部分で n, i, o を表示させてみました。終了しない原因が integer() 関数にないことや、 integer() 関数の戻り値が正しいかどうかがここで確かめられます。次に入れるとしたら (==B==) の位置です。n と i を表示させながらループを観察します。そうすると `i == -1` のときにループから戻ってこないことが分かります。きっと pow() 関数の、負値のときにバグがあるのだろうと推測することができます。
206
+
207
+
208
+
209
+ ## 追記
210
+
211
+
212
+
213
+ もっと `strtod()` の挙動に近い `str_to_d()` を書いてみました。。。
214
+
215
+ コードが正しい保証・バグなく動く保証はまったくないです。見苦しいコードですが参考に...ならないか。
216
+
217
+ とりあえず追記しておきます。
218
+
219
+
220
+
221
+ ```c
222
+
223
+ #include <stdio.h>
224
+
225
+ #include <stdlib.h>
226
+
227
+ #include <math.h>
228
+
229
+ #include <stdbool.h>
230
+
231
+
232
+
233
+ #define WHITESPACE " \t\n\v\f\r"
234
+
235
+
236
+
237
+ // str の中から ch を探し、インデックスを返す。
238
+
239
+ // 見つからないときは -1
240
+
241
+ int find_ch(char const *str, char ch);
242
+
243
+
244
+
245
+ // ch が空白文字 WHITESPACE に含まれているかを返す
246
+
247
+ bool is_whitespace(char ch);
248
+
249
+
250
+
251
+ // 文字を十進整数にする
252
+
253
+ // 範囲外の場合は -1
254
+
255
+ int dec_to_digit(char ch);
256
+
257
+
258
+
259
+ // 文字を十六進整数にする
260
+
261
+ // 範囲外の場合は -1
262
+
263
+ int hex_to_digit(char ch);
264
+
265
+
266
+
267
+ // 符号を取得し、 nptr を進める
268
+
269
+ // 符号は、+なら1、-なら-1
270
+
271
+ int sign_and_eat(char const **nptr);
272
+
273
+
274
+
275
+ // ch が大文字だったら、小文字にする
276
+
277
+ char to_lower(char ch);
278
+
279
+
280
+
281
+ // s と t の先頭 n 文字が (大文字小文字の区別なしで) 等しいか返す。
282
+
283
+ bool is_same_n(char const *s, char const *t, size_t n);
284
+
285
+
286
+
287
+ // 基数 (0x) を取得し、 nptr を進める
288
+
289
+ int base_and_eat(char const **nptr);
290
+
291
+
292
+
293
+ // 整数部分 (小数点 or 指数表示の e, p まで) を取得し、
294
+
295
+ // nptr を進める。
296
+
297
+ int integer_part_and_eat(char const **nptr, int base);
298
+
299
+
300
+
301
+ // 小数部分 (小数点から指数表示の e, p か末尾まで) を取得し、
302
+
303
+ // nptr を進める。
304
+
305
+ double fraction_part_and_eat(char const **nptr, int base);
306
+
307
+
308
+
309
+ // base の n 乗を求める。
310
+
311
+ double pwr(double base, int n);
312
+
313
+
314
+
315
+ // 文字列を double に変換する。
316
+
317
+ double str_to_d(char const *nptr, char const **endptr);
318
+
319
+
320
+
321
+ int find_ch(char const *str, char ch) {
322
+
323
+ for (int i = 0; str[i]; i++) {
324
+
325
+ if (str[i] == ch) return i;
326
+
327
+ }
328
+
329
+ return -1;
330
+
331
+ }
332
+
333
+
334
+
335
+ bool is_whitespace(char ch) {
336
+
337
+ return find_ch(WHITESPACE, ch) != -1;
338
+
339
+ }
340
+
341
+
342
+
343
+ int dec_to_digit(char ch) {
344
+
345
+ if ('0' <= ch && ch <= '9') return ch - '0';
346
+
347
+ return -1;
348
+
349
+ }
350
+
351
+
352
+
353
+ int hex_to_digit(char ch) {
354
+
355
+ // まず10進の範囲での変換を試みる
356
+
357
+ int dg = dec_to_digit(ch);
358
+
359
+ if (dg != -1) return dg;
360
+
361
+
362
+
363
+ // ダメなら文字を変換する
364
+
365
+ if ('A' <= ch && ch <= 'F') return 10 + (ch - 'A');
366
+
367
+ if ('a' <= ch && ch <= 'f') return 10 + (ch - 'a');
368
+
369
+ return -1;
370
+
371
+ }
372
+
373
+
374
+
375
+ int sign_and_eat(char const **nptr) {
376
+
377
+ // 符号がなければ + となるため
378
+
379
+ int sign = 1;
380
+
381
+ if (**nptr == '+') {
382
+
383
+ (*nptr)++;
384
+
385
+ } else if (**nptr == '-') {
386
+
387
+ sign = -1;
388
+
389
+ (*nptr)++;
390
+
391
+ }
392
+
393
+ return sign;
394
+
395
+ }
396
+
397
+
398
+
399
+ char to_lower(char ch) {
400
+
401
+ if ('A' <= ch && ch <= 'Z') {
402
+
403
+ ch = ch - 'A' + 'a';
404
+
405
+ }
406
+
407
+ return ch;
408
+
409
+ }
410
+
411
+
412
+
413
+ bool is_same_n(char const *s, char const *t, size_t n) {
414
+
415
+ for (size_t i = 0; i < n; i++) {
416
+
417
+ if (!s[i] || !t[i]) return false;
418
+
419
+ if (to_lower(s[i]) != to_lower(t[i])) return false;
420
+
421
+ }
422
+
423
+ return true;
424
+
425
+ }
426
+
427
+
428
+
429
+ int base_and_eat(char const **nptr) {
430
+
431
+ if (is_same_n(*nptr, "0x", 2)) {
432
+
433
+ // 0x 始まり == 16進数
434
+
435
+ *nptr += 2;
436
+
437
+ return 16;
438
+
439
+ }
440
+
441
+ return 10;
442
+
443
+ }
444
+
445
+
446
+
447
+ int integer_part_and_eat(char const **nptr, int base, int (*to_digit)(char)) {
448
+
449
+ int ans = 0;
450
+
451
+ int dg;
452
+
453
+ // 数字である限りは変換
454
+
455
+ for ( ; **nptr && (dg = to_digit(**nptr)) != -1; (*nptr)++) {
456
+
457
+ ans *= base;
458
+
459
+ ans += dg;
460
+
461
+ }
462
+
463
+ return ans;
464
+
465
+ }
466
+
467
+
468
+
469
+ double fraction_part_and_eat(char const **nptr, int base, int (*to_digit)(char)) {
470
+
471
+ double ans = 0;
472
+
473
+ int dg;
474
+
475
+ double pwr = 1. / base;
476
+
477
+ // 数字である限りは変換
478
+
479
+ for ( ; **nptr && (dg = to_digit(**nptr)) != -1; (*nptr)++) {
480
+
481
+ ans += pwr * dg;
482
+
483
+ pwr /= base;
484
+
485
+ }
486
+
487
+ return ans;
488
+
489
+ }
490
+
491
+
492
+
493
+ double pwr(double base, int n) {
494
+
495
+ double ans = 1;
496
+
497
+ if (n > 0) {
498
+
499
+ for (int i = 0; i < n; i++)
500
+
501
+ ans *= base;
502
+
503
+ } else if (n < 0) {
504
+
505
+ for (int i = 0; i < -n; i++)
506
+
507
+ ans /= base;
508
+
509
+ }
510
+
511
+ return ans;
512
+
513
+ }
514
+
515
+
516
+
517
+ double str_to_d(char const *nptr, char const **endptr) {
518
+
519
+ #define FINISH_WHEN_ENDS() do { if (!*nptr) goto finish; } while (false)
520
+
521
+ // とりあえず NAN, INFINITY に対応する
522
+
523
+ if (is_same_n(nptr, "nan", 3)) {
524
+
525
+ *endptr = nptr + 3;
526
+
527
+ return NAN;
528
+
529
+ } else if (is_same_n(nptr, "infinity", 8)) {
530
+
531
+ *endptr = nptr + 8;
532
+
533
+ return INFINITY;
534
+
535
+ }
536
+
537
+
538
+
539
+ char const *oldnptr;
540
+
541
+ double ans = 0;
542
+
543
+ int sign = 1;
544
+
545
+ // whitespace の読み飛ばし
546
+
547
+ while (*nptr && is_whitespace(*nptr)) nptr++;
548
+
549
+
550
+
551
+ // 符号ビット
552
+
553
+ FINISH_WHEN_ENDS();
554
+
555
+ sign = sign_and_eat(&nptr);
556
+
557
+
558
+
559
+ // 基数
560
+
561
+ FINISH_WHEN_ENDS();
562
+
563
+ int base; base = base_and_eat(&nptr);
564
+
565
+ char const *expchars; expchars = base == 10 ? "eE" : "pP";
566
+
567
+ int (*to_digit)(char); to_digit = base == 10 ? dec_to_digit : hex_to_digit;
568
+
569
+
570
+
571
+ // 数字または小数点でないなら終了
572
+
573
+ FINISH_WHEN_ENDS();
574
+
575
+ if (to_digit(*nptr) == -1 && *nptr != '.') goto finish;
576
+
577
+
578
+
579
+ // 整数部分読み込み
580
+
581
+ oldnptr = nptr;
582
+
583
+ ans += integer_part_and_eat(&nptr, base, to_digit);
584
+
585
+ bool read_integer; read_integer = oldnptr != nptr;
586
+
587
+
588
+
589
+ // 小数点または expchars でなければ終了
590
+
591
+ FINISH_WHEN_ENDS();
592
+
593
+ bool read_dot; read_dot = false;
594
+
595
+ if (*nptr == '.') {
596
+
597
+ read_dot = true;
598
+
599
+ nptr++;
600
+
601
+ } else if (find_ch(expchars, *nptr) == -1) {
602
+
603
+ goto finish;
604
+
605
+ }
606
+
607
+
608
+
609
+ // 小数部分読み込み
610
+
611
+ oldnptr = nptr; // 変化したか確かめるため、とりあえず保存しておく。
612
+
613
+ ans += fraction_part_and_eat(&nptr, base, to_digit);
614
+
615
+ bool read_fraction; read_fraction = oldnptr != nptr;
616
+
617
+
618
+
619
+ // expchars でないか、 expchars であっても、
620
+
621
+ // 整数部省略小数であってかつ1つも進んでいない場合は終了
622
+
623
+ FINISH_WHEN_ENDS();
624
+
625
+ if (find_ch(expchars, *nptr) == -1
626
+
627
+ || (!read_integer && read_dot && !read_fraction)) {
628
+
629
+ if (!read_integer && read_dot && !read_fraction) {
630
+
631
+ // なぜか標準はドットから返すので、 nptr を一つ戻しておく。
632
+
633
+ nptr--;
634
+
635
+ }
636
+
637
+ goto finish;
638
+
639
+ }
640
+
641
+ nptr++;
642
+
643
+
644
+
645
+ // 符号
646
+
647
+ FINISH_WHEN_ENDS();
648
+
649
+ int exp_sign; exp_sign = sign_and_eat(&nptr);
650
+
651
+
652
+
653
+ // 値
654
+
655
+ FINISH_WHEN_ENDS();
656
+
657
+ int exp_num; exp_num = integer_part_and_eat(&nptr, base, to_digit);
658
+
659
+ // 16進数の指数表示は底が2なので。
660
+
661
+ double mulr; mulr = pwr(base == 10 ? 10 : 2, exp_num * exp_sign);
662
+
663
+ ans *= mulr;
664
+
665
+
666
+
667
+ if (*nptr) {
668
+
669
+ // なぜか標準は e を食べないので、戻す。
670
+
671
+ nptr--;
672
+
673
+ }
674
+
675
+
676
+
677
+ finish:
678
+
679
+ if (endptr) *endptr = nptr;
680
+
681
+ return sign * ans;
682
+
683
+ }
684
+
685
+
686
+
687
+ int main(void)
688
+
689
+ {
690
+
691
+ char const *tests[] = {
692
+
693
+ "134", "-542", "4234.342", "-543.435", ".5453", "-.2343", "45e2", "453.34E+4", "545.3e-2", ".e2", "4.e2",
694
+
695
+ "0x42", "-0x53", "0x32.42", "-0x543.53", "0x.423", "-0x.432", "0x234p2", "0x512P+4", "0x515p-2", ".p3", "2.p3",
696
+
697
+ "asdf", "23fab", "0xabgd", "28.eg", "4.3.3",
698
+
699
+ "naN", "InFiNiTy",
700
+
701
+ NULL,
702
+
703
+ };
704
+
705
+
706
+
707
+ double const EPS = 1e-8;
708
+
709
+ double f, f_std;
710
+
711
+ char const *endp;
712
+
713
+ char *endp_std;
714
+
715
+ for (int i = 0; tests[i] != NULL; i++) {
716
+
717
+ f = str_to_d(tests[i], &endp);
718
+
719
+ f_std = strtod(tests[i], &endp_std);
720
+
721
+ int num_equal = fabs(f - f_std) < EPS;
722
+
723
+ int rest_equal = endp == endp_std;
724
+
725
+
726
+
727
+ if (!num_equal || !rest_equal) {
728
+
729
+ printf("[%s] %f : %f (rest: %s : %s), (%d, %d)\n",
730
+
731
+ tests[i], f, f_std, endp, endp_std, num_equal, rest_equal);
732
+
733
+ }
734
+
735
+ }
736
+
737
+
738
+
739
+ return 0;
740
+
741
+ }
742
+
743
+ ```