文字列を数値に変換

文字列操作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関数の変換指定子をそれぞれ逆にしています。
一見同じ値でも、内部的には全く別の値が保存されていることがわかります。

ユーザーのキーボード入力やファイルからの文字の読み込みなどで得られるデータはすべて「文字」です。
ユーザーが「123」と入力しても、そのままでは「123」という数値を得ることはできず、単純な足し算すらできません。


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

atoi関数

数字を数値に変換するにはatoi関数を使用します。


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

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

    printf("%d", kazu);
    getchar();
}
int atoi(
 const char *str
);
strを数値に変換して返す。
変換に失敗した場合は0を返す。
(Visual Studioの場合)

atoi関数を使用するにはコードの先頭に「#include <stdlib.h>」を指定する必要があります。

atoi関数は引数の文字列が数値として読めるならば、数値にして返します。
数値に変換できない場合は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
0

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

atol、atof関数

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

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

strtol関数

文字列を数値に変換するにはstrtol関数を使用することもできます。
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);
	long num2 = strtol("123", NULL, 8);
	long num3 = strtol("123", NULL, 16);

	long num4 = strtol("123", NULL, 0);
	long num5 = strtol("0123", NULL, 0);
	long num6 = strtol("0x123", NULL, 0);

	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を利用すると空白で区切られた文字列から複数の値を連続して変換することができます。

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

atoi関数は変換に失敗した時に0を返しますが、実はこれはコンパイラに依存します。
多くのコンパイラでは0を返すようですが、C言語の仕様では決められていません。

また、0が返ってきた場合、それは「変換に失敗して0が返ってきた」のか「文字列の0を変換して0を返したのか(つまり正常終了)」を判断することができません。
このため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」は変換できていますが失敗と表示されています。

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


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型の数値に変換しますが、こよりも大きな値を解釈しようとすると変換に失敗します。
このとき、errnoという特殊な値にERANGEという定数をセットします。

errnoはいくつかの標準関数で使用される、何らかのエラーが発生した場合にエラーの情報を格納する特殊な値です。
ただしエラーが発生してもerrnoを書き換えない関数もあります。
(atoi関数は書き換えません)
内部的にはint型で、プログラムの実行直後は何もエラーが発生していない状態を表す0がセットされています。

long型の最大値を超える文字列を変換しようとすると、戻り値はLONG_MAXとなります。
long型の最小値よりも小さな文字列を変換しようとすると、戻り値はLONG_MINとなります。
これらはlong型で扱える最大値/最小値を表す定数です。

erronoを使用するには<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(errno == ERANGE){
		if (num >= 0)
			printf("long型の最大値を超えた変換を行おうとしました。\n");
		else
			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

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

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

関数名 データ型 最大値/最小値を超える場合 備考
strtol long LONG_MAX
LONG_MIN
 
strtoul unsigned long ULONG_MAXb
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が返され、errnoにERANGEがセットされます。

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