質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

5回答

3088閲覧

文字列を扱うライブラリ関数について

kamecha

総合スコア41

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2018/01/24 12:15

###前提
書籍で勉強している学生です。
書籍の解答がないため問題のヒントや解説をしていただけると嬉しいです。

###問題
ライブラリ関数atoi, atol, atofと同じ動作を行う関数を作成せよ。

lang

1int strtoi(const char *nptr){/* ... */} 2long strtol(const char *nptr){/* ... */} 3double strtof(const char *nptr){/* ... */}

###該当のソースコード

lang

1#include <stdio.h> 2#include <string.h> 3 4/*類乗*/ 5double power (double n){ 6 double o = 1; 7 if(n > 0){ 8 while(n--){ 9 o *= 10; 10 } 11 }else if(n == 0){ 12 o *= 1; 13 }else{ 14 while(n--){ 15 o /= 10; 16 } 17 } 18 return o; 19} 20 21/*整数部分の桁数*/ 22int integer (const char *nptr){ 23 int o = 0; 24 while(*nptr){ 25 o++; 26 nptr++; 27 if(*nptr == '.') break; 28 } 29 return o; 30} 31 32int strtoi (const char *nptr){ 33 int n = strlen(nptr); 34 int o = 0; 35 while(n--){ 36 o += (*nptr - '0') * (int)power(n); 37 nptr++; 38 } 39 40 return o; 41} 42 43//strtolはstrtoiとの違いが分からず省略 44 45double strtof(const char *nptr){ 46 int n = strlen(nptr); //文字列の長さ 47 double i = integer(nptr); //整数部分 48 double o = 0; 49 50 while(n--){ 51 if(i > 0){ 52 o += (*nptr - '0') * (int)power(i); 53 }else if(i < 0){ 54 o += (*nptr - '0') * (double)power(i); 55 } 56 nptr++; 57 i--; 58 } 59 60 return o; 61 62}

###疑問点
strtoi関数は何とか動作するのですが、strtof関数は動作さえしてくれません。
strtol関数は、strtoi関数との違いさえ分かりません。
どこが原因で動作していないのか、より良い方法がある、等があれば教えて頂けると嬉しいです。

また、自分は英語力があるほうではないため、atoi,atol等の単語の由来が分かりません。
出来れば、由来も教えて頂きたいです。
###補足情報
書籍 : 新明解c言語 入門編
演習 11-10

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

コメントを一旦削除させてもらってから、修正箇所をコメントアウトして修正してみました。
できるだけもとの形で、一部を修正する形にしてあります。

c

1#include <stdio.h> 2#include <string.h> 3 4double power (double n){ 5 double o = 1; 6 if(n > 0){ 7 while(n--){ 8 o *= 10; 9 } 10 }else if(n == 0){ 11 o *= 1; 12 }else{ 13 // (1) 14 // while(n--){ 15 while(n++){ 16 o /= 10; 17 } 18 } 19 return o; 20} 21 22int integer (const char *nptr){ 23 int o = 0; 24 while(*nptr){ 25 if(*nptr == '.') break; 26 o++; 27 nptr++; 28 // (2) 29 // if(*nptr == '.') break; 30 } 31 return o; 32} 33 34double strtof(const char *nptr){ 35 int n = strlen(nptr); 36 // (3) 37 // double i = integer(nptr); 38 int i = integer(nptr); 39 double o = 0; 40 41 // (==A==) 42 43 while(n--){ 44 // (==B==) 45 if(i > 0){ 46 // (4) 47 // o += (*nptr - '0') * (int)power(i); 48 o += (*nptr - '0') * (int)power(i - 1); 49 }else if(i < 0){ 50 o += (*nptr - '0') * (double)power(i); 51 } 52 nptr++; 53 i--; 54 } 55 56 return o; 57 58} 59 60int main(void) { 61 printf("%f\n", strtof(".24123")); 62 printf("%f\n", strtof("43")); 63 printf("%f\n", strtof("43.2")); 64 printf("%f\n", strtof("543.24123")); 65 return 0; 66}

実行結果

0.241230 43.000000 43.200000 543.241230

といっても、修正箇所はたったの4箇所だけです。

(1) デクリメントではなくインクリメント

もともと n < 0 なので、デクリメントしていっても n == 0 には到達せず、ループの条件がずっと真です。おそらく凡ミスだろうと思いますが。
動作すらしない、というのはここの無限ループのせいだと思われます。

(2) integer() 関数

これは「まったく動作しない」ことには直接関係はありませんが、小数点が最初のケタにあった場合に対処できないので、if文を上にもっていきました。普通の小数ならば小数点は少なくとも2文字目以降ですが、0.XXXX のような小数で最初の 0 を省略して .XXXX と入力することも、個人的にはよくするので...

(3) 型?

integer() の戻り値、桁数は int なので、それにあわせて直してます。凡ミスでしょう。

(4) 累乗の指数ズレ

累乗の指数が1ずれています。よく考えると分かりますが、iは「残りの整数部分のケタ数」を表しているわけです。なので、たとえばi == 3のときは 100 〜 999 なわけです。一番上のケタは 10^2 の位ですよね。なので、一番上のケタは 10^(i-1) の位なのです。

ちなみにstrtoi() では n-1 としていないのに、なぜこちらでは必要なのでしょうか?strtoi()では、while(n--) {のデクリメントによって、while 文の条件を判定するときに既に-1されているからです。


このようなとき、どの部分でつまっているかを確かめるために printf デバッグが有効です。
私はまずソースの (==A==) の部分で n, i, o を表示させてみました。終了しない原因が integer() 関数にないことや、 integer() 関数の戻り値が正しいかどうかがここで確かめられます。次に入れるとしたら (==B==) の位置です。n と i を表示させながらループを観察します。そうすると i == -1 のときにループから戻ってこないことが分かります。きっと pow() 関数の、負値のときにバグがあるのだろうと推測することができます。

追記

もっと strtod() の挙動に近い str_to_d() を書いてみました。。。
コードが正しい保証・バグなく動く保証はまったくないです。見苦しいコードですが参考に...ならないか。
とりあえず追記しておきます。

さらなる追記: バグがありました 。例えば整数部・小数部とも省略されてしまった場合など。修正は容易ですが、今後修正していくことはできないため、鵜呑みにされることを避ける意味でも、バグがあるままにしておきます。これ以上の編集はしません。

c

1#include <stdio.h> 2#include <stdlib.h> 3#include <math.h> 4#include <stdbool.h> 5 6#define WHITESPACE " \t\n\v\f\r" 7 8// str の中から ch を探し、インデックスを返す。 9// 見つからないときは -1 10int find_ch(char const *str, char ch); 11 12// ch が空白文字 WHITESPACE に含まれているかを返す 13bool is_whitespace(char ch); 14 15// 文字を十進整数にする 16// 範囲外の場合は -1 17int dec_to_digit(char ch); 18 19// 文字を十六進整数にする 20// 範囲外の場合は -1 21int hex_to_digit(char ch); 22 23// 符号を取得し、 nptr を進める 24// 符号は、+なら1、-なら-1 25int sign_and_eat(char const **nptr); 26 27// ch が大文字だったら、小文字にする 28char to_lower(char ch); 29 30// s と t の先頭 n 文字が (大文字小文字の区別なしで) 等しいか返す。 31bool is_same_n(char const *s, char const *t, size_t n); 32 33// 基数 (0x) を取得し、 nptr を進める 34int base_and_eat(char const **nptr); 35 36// 整数部分 (小数点 or 指数表示の e, p まで) を取得し、 37// nptr を進める。 38int integer_part_and_eat(char const **nptr, int base); 39 40// 小数部分 (小数点から指数表示の e, p か末尾まで) を取得し、 41// nptr を進める。 42double fraction_part_and_eat(char const **nptr, int base); 43 44// base の n 乗を求める。 45double pwr(double base, int n); 46 47// 文字列を double に変換する。 48double str_to_d(char const *nptr, char const **endptr); 49 50int find_ch(char const *str, char ch) { 51 for (int i = 0; str[i]; i++) { 52 if (str[i] == ch) return i; 53 } 54 return -1; 55} 56 57bool is_whitespace(char ch) { 58 return find_ch(WHITESPACE, ch) != -1; 59} 60 61int dec_to_digit(char ch) { 62 if ('0' <= ch && ch <= '9') return ch - '0'; 63 return -1; 64} 65 66int hex_to_digit(char ch) { 67 // まず10進の範囲での変換を試みる 68 int dg = dec_to_digit(ch); 69 if (dg != -1) return dg; 70 71 // ダメなら文字を変換する 72 if ('A' <= ch && ch <= 'F') return 10 + (ch - 'A'); 73 if ('a' <= ch && ch <= 'f') return 10 + (ch - 'a'); 74 return -1; 75} 76 77int sign_and_eat(char const **nptr) { 78 // 符号がなければ + となるため 79 int sign = 1; 80 if (**nptr == '+') { 81 (*nptr)++; 82 } else if (**nptr == '-') { 83 sign = -1; 84 (*nptr)++; 85 } 86 return sign; 87} 88 89char to_lower(char ch) { 90 if ('A' <= ch && ch <= 'Z') { 91 ch = ch - 'A' + 'a'; 92 } 93 return ch; 94} 95 96bool is_same_n(char const *s, char const *t, size_t n) { 97 for (size_t i = 0; i < n; i++) { 98 if (!s[i] || !t[i]) return false; 99 if (to_lower(s[i]) != to_lower(t[i])) return false; 100 } 101 return true; 102} 103 104int base_and_eat(char const **nptr) { 105 if (is_same_n(*nptr, "0x", 2)) { 106 // 0x 始まり == 16進数 107 *nptr += 2; 108 return 16; 109 } 110 return 10; 111} 112 113int integer_part_and_eat(char const **nptr, int base, int (*to_digit)(char)) { 114 int ans = 0; 115 int dg; 116 // 数字である限りは変換 117 for ( ; **nptr && (dg = to_digit(**nptr)) != -1; (*nptr)++) { 118 ans *= base; 119 ans += dg; 120 } 121 return ans; 122} 123 124double fraction_part_and_eat(char const **nptr, int base, int (*to_digit)(char)) { 125 double ans = 0; 126 int dg; 127 double pwr = 1. / base; 128 // 数字である限りは変換 129 for ( ; **nptr && (dg = to_digit(**nptr)) != -1; (*nptr)++) { 130 ans += pwr * dg; 131 pwr /= base; 132 } 133 return ans; 134} 135 136double pwr(double base, int n) { 137 double ans = 1; 138 if (n > 0) { 139 for (int i = 0; i < n; i++) 140 ans *= base; 141 } else if (n < 0) { 142 for (int i = 0; i < -n; i++) 143 ans /= base; 144 } 145 return ans; 146} 147 148double str_to_d(char const *nptr, char const **endptr) { 149#define FINISH_WHEN_ENDS() do { if (!*nptr) goto finish; } while (false) 150 // とりあえず NAN, INFINITY に対応する 151 if (is_same_n(nptr, "nan", 3)) { 152 *endptr = nptr + 3; 153 return NAN; 154 } else if (is_same_n(nptr, "infinity", 8)) { 155 *endptr = nptr + 8; 156 return INFINITY; 157 } 158 159 char const *oldnptr; 160 double ans = 0; 161 int sign = 1; 162 // whitespace の読み飛ばし 163 while (*nptr && is_whitespace(*nptr)) nptr++; 164 165 // 符号ビット 166 FINISH_WHEN_ENDS(); 167 sign = sign_and_eat(&nptr); 168 169 // 基数 170 FINISH_WHEN_ENDS(); 171 int base; base = base_and_eat(&nptr); 172 char const *expchars; expchars = base == 10 ? "eE" : "pP"; 173 int (*to_digit)(char); to_digit = base == 10 ? dec_to_digit : hex_to_digit; 174 175 // 数字または小数点でないなら終了 176 FINISH_WHEN_ENDS(); 177 if (to_digit(*nptr) == -1 && *nptr != '.') goto finish; 178 179 // 整数部分読み込み 180 oldnptr = nptr; 181 ans += integer_part_and_eat(&nptr, base, to_digit); 182 bool read_integer; read_integer = oldnptr != nptr; 183 184 // 小数点または expchars でなければ終了 185 FINISH_WHEN_ENDS(); 186 bool read_dot; read_dot = false; 187 if (*nptr == '.') { 188 read_dot = true; 189 nptr++; 190 } else if (find_ch(expchars, *nptr) == -1) { 191 goto finish; 192 } 193 194 // 小数部分読み込み 195 oldnptr = nptr; // 変化したか確かめるため、とりあえず保存しておく。 196 ans += fraction_part_and_eat(&nptr, base, to_digit); 197 bool read_fraction; read_fraction = oldnptr != nptr; 198 199 // expchars でないか、 expchars であっても、 200 // 整数部省略小数であってかつ1つも進んでいない場合は終了 201 FINISH_WHEN_ENDS(); 202 if (find_ch(expchars, *nptr) == -1 203 || (!read_integer && read_dot && !read_fraction)) { 204 if (!read_integer && read_dot && !read_fraction) { 205 // なぜか標準はドットから返すので、 nptr を一つ戻しておく。 206 nptr--; 207 } 208 goto finish; 209 } 210 nptr++; 211 212 // 符号 213 FINISH_WHEN_ENDS(); 214 int exp_sign; exp_sign = sign_and_eat(&nptr); 215 216 // 値 217 FINISH_WHEN_ENDS(); 218 int exp_num; exp_num = integer_part_and_eat(&nptr, base, to_digit); 219 // 16進数の指数表示は底が2なので。 220 double mulr; mulr = pwr(base == 10 ? 10 : 2, exp_num * exp_sign); 221 ans *= mulr; 222 223 if (*nptr) { 224 // なぜか標準は e を食べないので、戻す。 225 nptr--; 226 } 227 228finish: 229 if (endptr) *endptr = nptr; 230 return sign * ans; 231} 232 233int main(void) 234{ 235 char const *tests[] = { 236 "134", "-542", "4234.342", "-543.435", ".5453", "-.2343", "45e2", "453.34E+4", "545.3e-2", ".e2", "4.e2", 237 "0x42", "-0x53", "0x32.42", "-0x543.53", "0x.423", "-0x.432", "0x234p2", "0x512P+4", "0x515p-2", ".p3", "2.p3", 238 "asdf", "23fab", "0xabgd", "28.eg", "4.3.3", 239 "naN", "InFiNiTy", 240 NULL, 241 }; 242 243 double const EPS = 1e-8; 244 double f, f_std; 245 char const *endp; 246 char *endp_std; 247 for (int i = 0; tests[i] != NULL; i++) { 248 f = str_to_d(tests[i], &endp); 249 f_std = strtod(tests[i], &endp_std); 250 int num_equal = fabs(f - f_std) < EPS; 251 int rest_equal = endp == endp_std; 252 253 if (!num_equal || !rest_equal) { 254 printf("[%s] %f : %f (rest: %s : %s), (%d, %d)\n", 255 tests[i], f, f_std, endp, endp_std, num_equal, rest_equal); 256 } 257 } 258 259 return 0; 260}

投稿2018/01/24 14:06

編集2018/01/27 13:21
Eki

総合スコア429

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamecha

2018/01/24 14:42

有り難うございます。 少しのミスが積み重なり、エラー多発になってしまうと言うことを身をもって実感しました。 とても分かりやすく為になりました! printfデバッグ...これから使っていきます。
guest

0

strtol関数は、strtoi関数との違いさえ分かりません。

long版とint版の違いですね。整数型といってもlongとintは明確に違う型として認識されるので、分ける必要があります。処理系によっては、intは32bitでlongは64bit、のように扱える数値が変わってきます。

書籍の問題のルールは、他の標準関数を使わないで書くことでしょうか。だとしても、ソースコードはちょっと処理が大げさすぎですね。おそらく出題者はもっと簡単に書くことを意図していると思います。

ヒントです。

  • "12345"

(((((0) * 10 + 1) * 10 + 2) * 10 + 3) * 10 + 4) * 10 + 5

  • "123.45"

((((((0) * 10 + 1) * 10 + 2) * 10 + 3) * 10 + 4) * 10 + 5) / 10 / 10

  • "1.23e4"

(((((0) * 10 + 1) * 10 + 2) * 10 + 3) / 10 / 10 * 10 * 10 * 10 * 10

power関数など作らずとも、一つ一つ文字を取り出してあーしてこーすればできそうな気がしませんか?


補足です。
「ライブラリ関数と同じ動作」をするには、符号(+-)の処理も必要ですね。一手間かけて符号の処理も入れましょう。

投稿2018/01/24 14:18

編集2018/01/24 14:23
catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamecha

2018/01/25 13:56

有り難うございます。 本の3周目の際に、そのような細かな所も視野にいれていきます。
guest

0

とりあえず由来は、「man 関数名」で出てきます。
atoi -- convert ASCII string to integer
atol -- convert ASCII string to long
atof -- convert ASCII string to double(float)

投稿2018/01/24 12:27

編集2018/01/24 12:30
hichon

総合スコア5737

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamecha

2018/01/24 13:03

有り難うございます! これから分からなくなった時、調べます。
guest

0

下記サイトが、ライブラリについては詳しく説明してあります。
例えば、atoi()はどのような文字列を受け付けるか?・・・とか
ライブラリー関数 IBM
「ヒント」

c

1long stol(const char * str) 2{ 3 long ret= 0; 4 5 while(*str){ 6 ret *= 10; 7 ret += *str - '0'; 8 str++; 9 } 10 11 return ret; 12}

投稿2018/01/24 13:37

編集2018/01/24 14:39
cateye

総合スコア6851

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamecha

2018/01/24 14:29

有り難うございます。
cateye

2018/01/24 14:37

皆さんの回答に符号(+、ー)について記述が無いのですが・・・? 符号については、情報として持っておいて、すべて自然数で算出し負の時は符号を反転するのが確実です。
kamecha

2018/01/24 14:50

なるほど、符号は考えていませんでした。 long型に対応するためには、関数内の変数をlong型で宣言してやれば良いのですね!
guest

0

文字列を数値に変換する方法としては、ato*()やstrto*()関数以外に、sscanf()関数を用いる方法があります。

C

1int strtoi(const char *nptr) 2{ 3 int n; 4 5 if (sscanf(nptr, "%d", &n) != 1){ 6 return 0; 7 } 8 9 return n; 10}

投稿2018/01/24 13:14

shsh_

総合スコア113

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamecha

2018/01/24 14:28

有り難うございます。 sscanf()関数...知らなかったので、しっかりと抑えていきます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問