文字列を数値に変換

文字列操作5

ほとんどのプログラミング言語では、文字と数値は別物として扱われます。
同じ「1」という値でも、文字としての「1」と数値としての「1」は別物なのです。


#include <stdio.h>

int main()
{
    char kazu = 1;
    char moji = '1';
    
    printf("kazu: %d\n", kazu);
    printf("moji: %c\n", moji);

    printf("\n");

    printf("kazu: %c\n", kazu);
    printf("moji: %d\n", moji);

    getchar();
}
kazu: 1
moji: 1

kazu: ・
moji: 49

このコードでは同じchar型の変数に、数値としての「1」と文字としての「1」を代入しています。
8、9行目はそれぞれ正しく出力されますが、13、14行目ではprintf関数の変換指定子をそれぞれ逆にしています。
一見同じ値でも、内部的には全く別の値が保存されていることがわかります。

getchar関数などによるキーボード入力やファイルからの文字の読み込みなどで得られるデータはすべて「文字」です。
ユーザーが「123」と入力しても、そのままでは「123」という数値を得ることはできず、単純な足し算すらできません。
(直接数値に変換して取得する方法も存在しますが)


//こういうことはできない
char moji[] = "123" + "456";
int kazu = "123" + "456";

atoi関数

数字を数値に変換するにはatoi関数を使用します。
この関数を使用するにはコード先頭に#include <stdlib.h>を追加する必要があります。


#include <stdio.h>
#include <stdlib.h>

int main()
{
    const char moji[] = "123";
    int kazu = atoi(moji);

    printf("%d", kazu);
    getchar();
}
int atoi(
 const char *str
);
文字列strを数値に変換して返す。
数値と評価できない文字列が渡された場合は0を返す。
int型の範囲を超える数値表現の文字列が渡された場合の動作は未定義。

atoi関数は引数の文字列が数値として読めるならば、数値にして返します。
「abc」や「123」(全角の数字)などは数値に変換できず、0を返します。

数字と数字以外の文字が混在する場合は、先頭から数字と評価できる分だけを数値に変換して返します。
先頭に空白文字(半角スペースや改行文字など)がある場合は何らかの文字が出現するまで読み飛ばされます。


#include <stdio.h>
#include <stdlib.h>

int main()
{
    char moji1[] = "12a3b";
    char moji2[] = "-123ab";
    char moji3[] = "    \n\t123ab";
    char moji4[] = "a123b";
    int kazu1 = atoi(moji1);
    int kazu2 = atoi(moji2);
    int kazu3 = atoi(moji3);
    int kazu4 = atoi(moji4);

    printf("%d\n", kazu1);
    printf("%d\n", kazu2);
    printf("%d\n", kazu3);
    printf("%d\n", kazu4);

    getchar();
}
12
-123
123
0

このコードのmoji1は先頭から2文字が数字なのでkazu1には「12」が代入されます。
moji2は先頭がハイフンですが、これはマイナス符号と解釈されます。
moji3は先頭に半角スペースや改行文字、タブ文字がありますがこれらは読み飛ばされます。
moji4は途中に数字がありますが、先頭が数字ではないので変換できず、kazu4には「0」が代入されます。

なお、int型の範囲を超える数字の文字列を指定した場合の動作は未定義です。

atol、atof関数

atoi関数の類似関数にatol関数、atof関数があります。
atol関数は文字列をlong型に変換します。
atof関数は文字列をdouble型に変換します。

使い方はatoi関数と同じなのでサンプルコードは省略します。

strtol関数

文字列を数値に変換するにはstrtol関数を使用することもできます。
(string to long)
atoi関数に比べて使い方がやや難しいものの、より安全かつ柔軟な変換が可能です。

long strtol(
 const char *strSource,
 char **endptr,
 int base
);
文字列strSourceをbase進数の文字列と解釈した値をlong型で返す。
ポインタendptrには読み取った次の文字への位置を格納する。

#include <stdio.h>
#include <stdlib.h>

int main()
{
	const char str[] = "123 456 7ab";
	char *end1, *end2, *end3;

	long num1 = strtol(str, &end1, 10);
	long num2 = strtol(end1, &end2, 10);
	long num3 = strtol(end2, &end3, 10);

	printf("%d\n", num1);
	printf("%d\n", num2);
	printf("%d\n", num3);

	printf("%s\n", end3);

	getchar();
}
123
456
7
ab

strtol関数の第一引数は変換対象となる文字列です。
先頭から文字列を読み取り、数値と解釈できない文字が現れるまでの文字列を数値に変換します。
先頭に空白文字がある場合は無視されます。
(何らかの文字が現れるまで読み飛ばされる)

第二引数はポインタという機能を学習しないと意味が分からないと思います。
とりあえず第二引数に渡す変数は、char型変数の変数名の前に*記号を付けて宣言し、実引数には&記号を指定する、と覚えてください。
必要がない場合はNULLを渡すことも可能です。
この引数については後述します。

第三引数は文字列の基数を指定します。
「10」を指定すれば文字列中の数値は10進数であると解釈され、「16」を指定すれば16進数と解釈されます。
2~36の数値が指定できます。
「0」を指定すると、文字列の形式から自動的に判別します。
文字列の先頭が「0」ならば8進数、「0x」「0X」の場合は16進数、それ以外の場合は10進数と解釈されます。


#include <stdio.h>
#include <stdlib.h>

int main()
{
	long num1 = strtol("123", NULL, 10);	//10進数と解釈
	long num2 = strtol("123", NULL, 8);		//8進数と解釈
	long num3 = strtol("123", NULL, 16);	//16進数と解釈

	long num4 = strtol("123", NULL, 0);		//自動判別(10進数)
	long num5 = strtol("0123", NULL, 0);	//自動判別(8進数)
	long num6 = strtol("0x123", NULL, 0);	//自動判別(16進数)

	printf("%d\n", num1);
	printf("%d\n", num2);
	printf("%d\n", num3);

	printf("\n");

	printf("%d\n", num4);
	printf("%d\n", num5);
	printf("%d\n", num6);

	getchar();
}
123
83
291

123
83
291

※この実行結果はすべて10進数表記です。

第二引数endptr

第二引数endptrは「ポインタ」への「ポインタ」を指定します。
ポインタはまだ説明していないので、できるだけ省いて説明します。


char *end;

long num = strtol("123", &end, 10);

上記の形式でchar型変数を宣言して第二引数に渡すと、この変数には「数値と解釈できない文字が表われた位置」の情報が格納されます。
文字列をすべて数値と解釈できた場合は、終端のNULL文字の位置の情報が格納された状態となります。

この変数から文字を取り出すには変数名の前に*記号を付けます。


char *end1, *end2;

long num1 = strtol("abc", &end1, 10);
long num2 = strtol("123def", &end2, 10);

printf("%c\n", *end1);
printf("%c\n", *end2);
a
d

「abc」は数値(10進数)とは解釈できないので文字列の先頭である「a」の位置の情報が変数end1に格納されます。
「123def」は「123」までは数値と解釈でき、以降は数値とは解釈できないため「d」の位置の情報が変数end2に格納されます。

strtol関数は先頭の空白文字は読み飛ばすため、最初のサンプルコードのように、第二引数endptrを利用すると空白で区切られた文字列から複数の値を連続して変換することができます。


const char str[] = "123 456 789";
char *end1, *end2, *end3;

long num1 = strtol(str, &end1, 10);		//123
long num2 = strtol(end1, &end2, 10);	//456
long num3 = strtol(end2, &end3, 10);	//789

変換エラーへの対処その1

atoi関数は数値に変換できない文字列を指定した場合に0を返しますが、正常に変換された場合にも0を返す可能性があります。
また、int型の範囲を超える値を渡した時の動作は未定義です。
つまり、関数が成功したのか失敗したのかを戻り値からは判別することができません。
このためatoi関数の使用は推奨されていません。

strtol関数は第二引数のendptrを使用することで正常終了か否かを判断することができます。


#include <stdio.h>
#include <stdlib.h>

int main()
{
	const char str1[] = "0";
	const char str2[] = "a";

	char *end1, *end2;

	long num1 = strtol(str1, &end1, 10);
	long num2 = strtol(str2, &end2, 10);

	printf("%d\n", num1);
	printf("%d\n", num2);

	printf("\n");

	if(*end1 == '\0')
		printf("str1の変換は成功\n");
	else
		printf("str1の変換は失敗\n");

	if (*end2 == '\0')
		printf("str2の変換は成功\n");
	else
		printf("str2の変換は失敗\n");

	getchar();
}
0
0

str1の変換は成功
str2の変換は失敗

第二引数endptrは文字列の終端まで変換に成功すると終端のNULL文字の位置を指した状態となります。
つまりendptrとNULL文字が等しいかを比較することで、変換成功か失敗かを判別できます。

ただしこの方法では文字列の途中まで読み取れた場合に正しく判定できません。


char *end;

long num = strtol("0a", &end, 10);

printf("%d\n", num);

if (*end == '\0')
    printf("変換成功\n");
else
    printf("変換失敗\n");
0
変換失敗

実際には「0a」の先頭の「0」は変換できていますが失敗と表示されています。
(全部を変換できなかったと考えれば失敗と言えますが)

以下のように、endptrstrの「メモリ上の位置」同士を比較することで成功か失敗かを判定することは可能です。
メモリ上の位置が異なる場合、少なくとも一文字以上は読み取れたことになります。
(これの意味の理解にはポインタの知識が必要です)


const char str[] = "0a";
char *end;

long num = strtol(str, &end, 10);

printf("%d\n", num);

if (end != str)
    printf("変換成功\n");
else
    printf("変換失敗\n");
0
変換成功

変換エラーへの対処2

strtol関数は文字列をlong型の数値に変換しますが、これよりも大きな値を解釈しようとすると変換に失敗します。
long型の最大値を超える値の文字列を変換しようとすると戻り値はlong型で扱える最大値に、最小値よりも小さな値の文字列を変換しようとすると戻り値はlong型で扱える最小値になります。
long型の最大値はLONG_MAX、最小値はLONG_MINという定数が使用できます。

そしてこのとき、errnoという特殊な変数にERANGEという定数がセットされます。
errnoはいくつかの標準関数で使用される、何らかのエラーが発生した場合にエラーの情報を格納する特殊な値です。
この値はグローバル変数(のようなもの)で、プログラム中のどこからでもアクセス可能です。

errnoを正しく使用するには、エラーチェックしたい関数を呼び出す直前errnoに0をセットします。
そして、エラーチェックしたい関数の呼び出し直後に、errnoの値を調べてエラーの有無を判断します。
ちなみにプログラムの起動時は0がセットされています。
なお、C言語標準関数の全てがエラー発生時にerrnoを書き換えるわけではないので注意が必要です。
(atoi関数は書き換えません)

errnoを使用するには<errno.h>をインクルードします。
定数LONG_MAX/LONG_MIN<limits.h>をインクルードすると使用できます。
(インクルードしなくても戻り値自体は受け取れます)


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

long MyStrToL(const char* str, char** ptr, int radix)
{
    //他の関数で書き換わっている可能性があるので
    //事前にerrnoを0にセットしておく
    errno = 0;

    long num = strtol(str, ptr, radix);
    if(num == LONG_MAX && errno == ERANGE)
        printf("long型の最大値を超えた変換を行おうとしました。\n");
    else if (num == LONG_MIN && errno == ERANGE)
        printf("long型の最小値を超えた変換を行おうとしました。\n");

    //errnoを0に戻しておく
    errno = 0;

    return num;
}

int main()
{
	//LONG_MAX/LONG_MIN
	printf("LONG_MAX: %d\n", LONG_MAX);
	printf("LONG_MIX: %d\n", LONG_MIN);
	printf("\n");

	//long型の最大値より1大きい値
	const char strMax[] = "2147483648";
	//long型の最小値より1小さい値
	const char strMin[] = "-2147483649";

	printf("%d\n", MyStrToL(strMax, NULL, 10));
	printf("%d\n", MyStrToL(strMin, NULL, 10));

	getchar();
}
LONG_MAX: 2147483647
LONG_MIX: -2147483648

long型の最大値を超えた変換を行おうとしました。
2147483647
long型の最小値を超えた変換を行おうとしました。
-2147483648

なお、細かい点ですが、関数がエラーの可能性を示す値を返すようになっている場合はまずその値が返されたことをチェックしてからerrnoの値を調べるのが良いそうです。
strtol関数ならばLONG_MAXまたはLONG_MINが返されたことを先にチェックします。
C言語の仕様上、関数でエラーが発生したときにerrnoを書き換えることになっていますが、エラーが発生していないときに書き換えることは禁止されていないためです。

その他のデータ型への変換

strtol関数はlong型への変換ですが、他のデータ型へ変換するための関数も存在します。

関数名 データ型 最大値/最小値を超える場合 備考
strtol long LONG_MAX
LONG_MIN
 
strtoul unsigned long ULONG_MAX
ULONG_MIN
 
strtoll long long LLONG_MAX
LLONG_MIN
 
strtoull unsigned long long ULLONG_MAX
ULLONG_MIN
 
strtof float HUGE_VALF
-HUGE_VALF
第三引数(基数の指定)はない
定数の使用にはmath.hをインクルードする
strtod double HUGE_VAL
-HUGE_VAL
第三引数(基数の指定)はない
定数の使用にはmath.hをインクルードする
strtold long double HUGE_VALL
-HUGE_VALL
第三引数(基数の指定)はない
定数の使用にはmath.hをインクルードする

使い方は基本的にstrtol関数と同じです。

ただし小数型への変換は第三引数の基数の指定はありません。
また、最大値を超える変換を行った場合の戻り値はHUGE_VALという定数となります。
(float型はHUGE_VALF、long double型はHUGE_VALL)
これは「巨大な正の値」を表す定数で、具体的な値は処理系によりますがおおむね無限大を表すものです。
最小値を下回る場合は-HUGE_VALで、「巨大な負の値」となります。
この定数はmath.hをインクルードすることで使用できます。

さらに小数型は、限りなく0に近い値となる文字列を数値に変換しようとしたとき、アンダーフローが発生します。
アンダーフローが発生した場合は0が返されます。
errnoERANGEをセットするかどうかはコンパイラにより異なります。

ここに無い型への変換はキャストが必要です。
その際にはオーバーフローに注意してください。