ユーザー入力の検証、ログ出力、ファイル保存やネットワーク越しのやり取りなど、C++では文字列と数値の行き来が頻繁に発生します。
本記事では、C++での数値→文字列、文字列→数値の主要な方法を網羅的に整理し、エラー処理やロケール、精度などの落とし穴まで丁寧に解説します。
C言語からの移行組にも読みやすいよう、段階を踏んで説明します。
数値→文字列の変換方法
std::to_stringの基本
std::to_string
はもっとも手軽に使える変換関数です。
整数も浮動小数点数も簡単に文字列化できますが、浮動小数点では桁数や表記形式を細かく制御できない点に注意が必要です。
サンプルコード
#include <iostream>
#include <string>
int main() {
int i = 42;
long long big = 123456789012345LL;
double d = 3.141592653589793;
// 最も簡単な数値→文字列
std::string si = std::to_string(i);
std::string sb = std::to_string(big);
std::string sd = std::to_string(d); // 多くの実装で小数点以下は6桁程度の固定桁
std::cout << "i: " << si << '\n';
std::cout << "big: " << sb << '\n';
std::cout << "d: " << sd << " (桁数や指数表記の制御はできません)\n";
}
i: 42
big: 123456789012345
d: 3.141593 (桁数や指数表記の制御はできません)
制御したい場合は後述のstd::format
やstd::ostringstream
、std::to_chars
を使います。
std::formatの書式指定 C++20
C++20のstd::format
は、高速で扱いやすい書式指定APIです。
幅やゼロ埋め、符号、基数、精度などをPythonのf-string風の文法で指定できます。
デフォルトではロケール非依存です。
主な書式指定の例
- 整数:
{:08d}
で幅8ゼロ埋め、{:+d}
で符号常時表示、{:#x}
で0x
付き16進 - 浮動小数点:
{:.3f}
で小数点以下3桁固定、{:.2e}
で指数表記、{:.17g}
で有効桁数 - 2進・8進・16進:
{:b}
{:o}
{:x}
、#
でプレフィックス付与
サンプルコード
#include <iostream>
#include <format> // C++20
#include <string>
int main() {
int n = 42;
double pi = 3.141592653589793;
std::string s1 = std::format("n={:08d}, sign={:+d}", n, n);
std::string s2 = std::format("hex={:#x}, HEX={:#X}, bin={:#b}, oct={:#o}", n, n, n, n);
std::string s3 = std::format("pi(fixed)={:.3f}, pi(exp)={:.2e}, pi(17g)={:.17g}", pi, pi, pi);
std::cout << s1 << '\n' << s2 << '\n' << s3 << '\n';
}
n=00000042, sign=+42
hex=0x2a, HEX=0X2A, bin=0b101010, oct=052
pi(fixed)=3.142, pi(exp)=3.14e+00, pi(17g)=3.1415926535897931
コンパイラと標準ライブラリのバージョン(GCCなら13以降、Clangならlibc++の対応版、MSVCはVS 2019 16.10+など)を確認してください。
std::ostringstreamとiomanipの使い方
std::ostringstream
はC++98から使える伝統的な方法で、<iomanip>
のマニピュレータを併用すると柔軟に整形できます。
ストリームはロケールの影響を受けます。
サンプルコード
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
int main() {
int n = 42;
double pi = 3.141592653589793;
std::ostringstream oss;
// 浮動小数点の桁数と表記方法を指定
oss << std::fixed << std::setprecision(4) << "pi=" << pi << ", ";
// 幅とゼロ埋め、基数(16進)と接頭辞
oss << "hex=" << std::showbase << std::hex << std::uppercase
<< std::setw(6) << std::setfill('0') << n;
std::string out = oss.str();
std::cout << out << '\n';
}
pi=3.1416, hex=0X002A
ストリームはホワイトスペースの扱いやロケールの指定など入出力の一貫化がしやすいのが利点です。
std::to_charsで高速変換 C++17
std::to_chars
はアロケーションなしで指定バッファに直接書き込む高速APIです。
整数用はC++17、浮動小数点用はC++17で規定され実装はC++20以降で安定しています。
ロケール非依存です。
サンプルコード
#include <iostream>
#include <charconv> // to_chars
#include <array>
#include <string>
int main() {
std::array<char, 64> buf{};
// 整数を10進
int value = 123456789;
auto [ptr1, ec1] = std::to_chars(buf.data(), buf.data() + buf.size(), value);
std::string s1(buf.data(), ptr1);
std::cout << "int: " << s1 << '\n';
// 整数を16進(プレフィックスは自分で付ける)
auto [ptr2, ec2] = std::to_chars(buf.data(), buf.data() + buf.size(), value, 16);
std::string s2(buf.data(), ptr2);
std::cout << "hex: 0x" << s2 << '\n';
// 浮動小数点を有効桁17で一般形式
double d = 3.141592653589793;
auto [ptr3, ec3] = std::to_chars(buf.data(), buf.data() + buf.size(), d, std::chars_format::general, 17);
std::string s3(buf.data(), ptr3);
std::cout << "double(17g): " << s3 << '\n';
// 無限大やNaNは "inf"/"nan" の表記が書き出されます
auto [ptr4, ec4] = std::to_chars(buf.data(), buf.data() + buf.size(),
std::numeric_limits<double>::infinity(),
std::chars_format::general);
std::string s4(buf.data(), ptr4);
std::cout << "infinity: " << s4 << '\n';
}
int: 123456789
hex: 0x75bcd15
double(17g): 3.1415926535897931
infinity: inf
to_chars
は成功可否をエラーコードで返すため、例外を投げません。
失敗時はec != std::errc{}
になります。
16進 2進 8進への変換
基数変換は複数の方法があります。
状況に応じて選択できます。
std::format
:{:x}
{:X}
{:o}
{:b}
、#
で接頭辞付std::ostringstream
:std::hex
std::oct
、std::showbase
で接頭辞std::to_chars
: 第4引数に基数(2〜36)を指定- 2進は
std::bitset<N>
も便利です
サンプルコード
#include <iostream>
#include <bitset>
#include <format>
#include <charconv>
#include <array>
#include <string>
int main() {
unsigned v = 255;
// format
std::cout << std::format("format => hex={:#x}, oct={:#o}, bin={:#b}\n", v, v, v);
// iostream
std::cout << std::showbase << std::hex << "iostream => " << v << '\n';
std::cout << std::showbase << std::oct << "iostream => " << v << '\n';
std::cout << "bitset => 0b" << std::bitset<16>(v) << '\n';
// to_chars
std::array<char, 64> buf{};
auto [p, ec] = std::to_chars(buf.data(), buf.data() + buf.size(), v, 2);
std::cout << "to_chars => 0b" << std::string(buf.data(), p) << '\n';
}
format => hex=0xff, oct=0377, bin=0b11111111
iostream => 0xff
iostream => 0377
bitset => 0b0000000011111111
to_chars => 0b11111111
文字列→数値の変換方法
std::stoi stol stollの基本
std::stoi
std::stol
std::stoll
は整数値をパースします。
オプションのbase
引数で基数を指定できます。
base
が0のときはCと同様に自動判定(先頭0x
→16進、先頭0
→8進、それ以外→10進)です。
サンプルコード
#include <iostream>
#include <string>
int main() {
std::cout << std::stoi("42") << '\n'; // 10進
std::cout << std::stoi("2a", nullptr, 16) << '\n';// 16進(=42)
std::cout << std::stoi("101010", nullptr, 2) << '\n'; // 2進(=42)
std::cout << std::stoi("0x2a", nullptr, 0) << '\n'; // 自動判定で16進
std::cout << std::stoll("1234567890123") << '\n'; // 64bit程度
}
42
42
42
42
1234567890123
std::stoi
系は、先頭に空白があれば無視し、最初の非数値文字までを数値とみなします。
完全一致を要求しない点に注意してください(未読文字は後述のpos
で取得可能)。
std::stof stodの浮動小数点
std::stof
std::stod
std::stold
は浮動小数点文字列をパースします。
指数e/E
表記も扱えます。
サンプルコード
#include <iostream>
#include <string>
int main() {
std::cout << std::stod("3.14") << '\n';
std::cout << std::stod("1.5e2") << '\n'; // 150
std::cout << std::stof(" -0.125 ") << '\n'; // 先頭空白は許可
std::cout << std::stod("123.45xyz") << '\n'; // 途中まで読んで止まる
}
3.14
150
-0.125
123.45
多くの実装で"inf"
や"nan"
も受け付けます(後述)。
失敗や桁あふれは例外で通知されます。
posで未読文字を検出する
stoi/stol/stoll/stof/stod
は第2引数にstd::size_t* pos
を渡すと、どこまで読んだかを教えてくれます。
単位や余計な文字の検出に便利です。
サンプルコード
#include <iostream>
#include <string>
int main() {
std::string s = "42kg";
std::size_t pos = 0;
int n = std::stoi(s, &pos); // base省略は10進
std::string unit = s.substr(pos); // 未読部分を取り出す
std::cout << "value=" << n << ", unit=\"" << unit << "\" (pos=" << pos << ")\n";
}
value=42, unit="kg" (pos=2)
std::from_charsで高速パース C++17
std::from_chars
は例外を使わない高速パーサです。
ロケール非依存で、空白のスキップもしません。
整数はC++17、浮動小数点はC++17規定(C++20以降で広く提供)です。
サンプルコード
#include <cctype>
#include <charconv>
#include <iostream>
#include <string>
#include <system_error> // errc
int main() {
std::string s1 = "123abc";
int v1 = 0;
auto r1 = std::from_chars(s1.data(), s1.data() + s1.size(), v1);
std::cout << "v1=" << v1 << ", next='"
<< std::string(r1.ptr, s1.c_str() + s1.size()) << "'\n";
// 先頭空白は無視しないので、そのままだと失敗
std::string s2 = " 456";
int v2 = 0;
auto r2 = std::from_chars(s2.data(), s2.data() + s2.size(), v2);
std::cout << "s2 ec=" << (r2.ec == std::errc{} ? "ok" : "invalid") << '\n';
// 自力でトリムしてからパース
std::string_view sv = s2;
while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.front())))
sv.remove_prefix(1);
int v3 = 0;
auto r3 = std::from_chars(sv.data(), sv.data() + sv.size(), v3);
std::cout << "v3=" << v3 << ", ec=" << (r3.ec == std::errc{} ? "ok" : "err")
<< '\n';
// 16進。注意: "0x"接頭辞は許可されません(自分でスキップ)
std::string s4 = "0x2a";
int v4 = 0;
const char* p = s4.data();
if (s4.rfind("0x", 0) == 0 || s4.rfind("0X", 0) == 0) p += 2;
auto r4 = std::from_chars(p, s4.data() + s4.size(), v4, 16);
std::cout << "v4=" << v4 << ", rest='"
<< std::string(r4.ptr, s4.c_str() + s4.size()) << "'\n";
}
v1=123, next='abc'
s2 ec=invalid
v3=456, ec=ok
v4=42, rest=''
from_chars
はbase
の自動判定をしない点と、0x
接頭辞を受け付けない点がstoi
との大きな違いです。
std::stringstreamでのパース
ストリーム抽出演算子>>
は、空白スキップやロケール適用、失敗時の状態確認がしやすいのが特徴です。
複数値の順次パースにも向きます。
サンプルコード
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::istringstream iss(" 42 3.14 0x2a done");
int a = 0;
double b = 0.0;
int c = 0;
iss >> a; // 先頭空白は自動スキップ
iss >> b; // 浮動小数点
iss >> std::hex >> c; // 16進で読み取る指定(0x接頭辞ありでもOK)
std::string rest;
iss >> rest; // 残ったトークンを文字列として取得
std::cout << "a=" << a << ", b=" << b << ", c=" << c << ", rest='" << rest << "'\n";
}
a=42, b=3.14, c=42, rest='done'
Cのstrtol strtodとの違い
Cライブラリのstrtol
やstrtod
は例外ではなくerrno
とendptr
で結果を返します。
Cとのインターフェースが必要な場面や、きめ細かいエラー制御が必要な低レベル層で有用です。
サンプルコード
#include <iostream>
#include <cerrno>
#include <cstdlib> // strtol, strtod
#include <cstring>
int main() {
const char* s1 = "0x2a tail";
char* end1 = nullptr;
errno = 0;
long v1 = std::strtol(s1, &end1, 0); // base=0で自動判定
std::cout << "v1=" << v1 << ", rest='" << end1 << "', errno=" << errno << '\n';
const char* s2 = "123.45";
char* end2 = nullptr;
errno = 0;
double v2 = std::strtod(s2, &end2);
std::cout << "v2=" << v2 << ", rest='" << end2 << "', errno=" << errno << '\n';
}
v1=42, rest=' tail', errno=0
v2=123.45, rest='', errno=0
std::stoi/std::stod
はこれらをラップしてC++スタイルの例外を投げる関数です。
一方std::from_chars
は完全に独立したAPIで、ロケール非依存かつ例外非使用です。
変換のエラー処理と注意点
例外 invalid_argument out_of_rangeの対処
std::stoi/stod
は「数字が1つも読めない」場合にstd::invalid_argument
、桁あふれ時にstd::out_of_range
を投げます。
例外で明確に分岐しましょう。
サンプルコード
#include <iostream>
#include <string>
int main() {
try {
std::cout << std::stoi("abc") << '\n'; // 数字がない → invalid_argument
} catch (const std::invalid_argument& e) {
std::cout << "invalid_argument: " << e.what() << '\n';
}
try {
// 非常に大きい数 → out_of_range
std::cout << std::stoi("999999999999999999999") << '\n';
} catch (const std::out_of_range& e) {
std::cout << "out_of_range: " << e.what() << '\n';
}
}
invalid_argument: stoi
out_of_range: stoi
from_charsの戻り値と範囲判定
from_chars
は{ptr, ec}
を返します。
ec==std::errc{}
なら成功、ec==std::errc::invalid_argument
は1桁も読めず、ec==std::errc::result_out_of_range
は桁あふれです。
サンプルコード
#include <iostream>
#include <charconv>
#include <string>
#include <system_error>
#include <cstdint>
int main() {
std::string big = "9999999999999"; // int32_tには大きすぎる
int32_t x = 0;
auto r = std::from_chars(big.data(), big.data() + big.size(), x);
if (r.ec == std::errc::result_out_of_range) {
std::cout << "out of range for int32_t\n";
}
}
out of range for int32_t
ロケールと小数点 iostream stoi to_charsの違い
数値と小数点の扱いはAPIごとに異なります。
特に小数点記号(日本では通常.
ですが、ロケールによっては,
になる)に注意します。
下表は主な違いの整理です。
API | ロケールの影響 | 小数点記号 | 空白スキップ | 備考 |
---|---|---|---|---|
std::format | なし(C++20既定) | ‘.’固定 | 該当なし(出力専用) | C++23以降でロケール対応版あり |
ostringstream/stringstream | あり(ストリームロケール) | ロケール依存 | あり(抽出時) | 柔軟だがやや遅い |
std::to_string | 実装依存だが制御不可 | 実装依存 | 該当なし | 表示簡便、精度制御不可 |
std::stoi/std::stod | Cロケールに依存(グローバルなsetlocale) | 多くは’.’ | あり | 部分読みOK、例外で通知 |
std::to_chars/std::from_chars | なし | ‘.’固定 | なし | 高速、バイナリに近い |
ロケールに影響されない表現やシリアライズにはto_chars/from_chars
やstd::format
がおすすめです。
ユーザー入力の「,」小数点を受けたいなら、ストリームのロケールを設定して抽出するのが簡単です。
精度と丸め to_stringの落とし穴
std::to_string(double)
は多くの実装で小数点以下6桁程度の固定表記で丸めます。
ラウンドトリップ(文字列化→再パースで元の値に戻す)用途には不適です。
サンプルコード
#include <iostream>
#include <string>
#include <format>
#include <charconv>
#include <array>
int main() {
double x = 0.1 * 3; // 2進表現の都合で厳密には0.3ではない
std::string s_to_string = std::to_string(x); // 例: "0.300000"
std::string s_format = std::format("{:.17g}", x); // 17桁なら通常ラウンドトリップ可能
// to_charsで有効桁17の文字列化
std::array<char, 64> buf{};
auto [p, ec] = std::to_chars(buf.data(), buf.data() + buf.size(),
x, std::chars_format::general, 17);
std::string s_to_chars(buf.data(), p);
std::cout << "to_string: " << s_to_string << '\n';
std::cout << "format(17g): " << s_format << '\n';
std::cout << "to_chars(17g): " << s_to_chars << '\n';
}
to_string: 0.300000
format(17g): 0.30000000000000004
to_chars(17g): 0.30000000000000004
精度や表記を制御したい場合はstd::format
やstd::to_chars
を使いましょう。
空白 符号 トレーリング文字の扱い
同じ入力でもAPIごとに扱いが異なります。
代表例を比較します。
サンプルコード
#include <charconv>
#include <iostream>
#include <sstream>
#include <string>
#include <system_error>
int main() {
std::string s = " -42abc";
// stoi: 先頭空白OK、符号OK、未読文字は無視(例外なし)
std::size_t pos = 0;
int a = std::stoi(s, &pos);
std::cout << "stoi => a=" << a << ", pos=" << pos << '\n';
// from_chars: 空白は自動スキップしないので失敗
int b = 0;
auto r = std::from_chars(s.data(), s.data() + s.size(), b);
std::cout << "from_chars => ec=" << (r.ec == std::errc{} ? "ok" : "invalid")
<< '\n';
// 自分で空白を取り除けばOK
std::string s2 = s.substr(2); // 先頭の" "を除去
int c = 0;
auto r2 = std::from_chars(s2.data(), s2.data() + s2.size(), c);
std::cout << "from_chars(trimmed) => c=" << c << ", next='"
<< std::string(r2.ptr, s2.c_str() + s2.size()) << "'\n";
// stringstream: 先頭空白はスキップ、符号OK、途中で停止
std::istringstream iss(s);
int d = 0;
iss >> d;
std::string rest;
iss >> rest;
std::cout << "stringstream => d=" << d << ", rest='" << rest << "'\n";
}
stoi => a=-42, pos=4
from_chars => ec=invalid
from_chars(trimmed) => c=-42, next='abc'
stringstream => d=-42, rest='abc'
基数と0x 0プレフィックス
std::stoi
はbase=0
時に0x
や先頭0
の自動判定をします。
一方from_chars
は自動判定なし、かつ0x
を含む文字列を直接は受け付けません。
サンプルコード
#include <iostream>
#include <string>
#include <charconv>
int main() {
std::cout << "stoi base=0 => " << std::stoi("0x2a", nullptr, 0) << '\n';
std::cout << "stoi base=0 => " << std::stoi("075", nullptr, 0) << '\n'; // 8進(=61)
// from_charsは0xを自前処理
std::string s = "0xFF";
int v = 0;
const char* p = s.data();
if (s.rfind("0x", 0) == 0 || s.rfind("0X", 0) == 0) p += 2;
auto r = std::from_chars(p, s.data() + s.size(), v, 16);
std::cout << "from_chars(16) => " << v << '\n';
}
stoi base=0 => 42
stoi base=0 => 61
from_chars(16) => 255
NaN Infの扱い
浮動小数点の特別値はAPIごとに挙動が違います。
std::to_chars(double)
は非有限値を"nan"
"inf"
として出力します。std::stod
は多くの実装で"nan"
"inf"
"infinity"
を大文字小文字無視で受け付けますが、環境によりinvalid_argument
の可能性があります。std::from_chars
(浮動小数点)は有限値のみを想定し、"nan"
"inf"
はパース失敗になる実装が一般的です。
サンプルコード
#include <iostream>
#include <string>
#include <cmath>
int main() {
try {
double a = std::stod("nan");
double b = std::stod("inf");
std::cout << "stod('nan'): isnan=" << std::isnan(a) << '\n';
std::cout << "stod('inf'): isinf=" << std::isinf(b) << '\n';
} catch (...) {
std::cout << "stod: この環境では 'nan' または 'inf' が受け付けられません\n";
}
}
stod('nan'): isnan=1
stod('inf'): isinf=1
環境によっては上記の代わりに例外メッセージが表示されることがあります。
wstringとto_wstringの注意点
ワイド文字列を扱う場合は以下に注意します。
std::to_wstring
はstd::wstring
を返します。std::stoi
やstd::stod
はstd::wstring
版の引数も受け付けます。std::to_chars
/std::from_chars
はchar
バッファ専用で、wchar_t
には非対応です。ワイド文字列にしたい場合は変換するか、std::wostringstream
やワイド版std::format
を使います。
サンプルコード
#include <iostream>
#include <string>
#include <sstream>
#include <format> // C++20
int main() {
int n = 255;
// 数値→wstring
std::wstring ws1 = std::to_wstring(n);
std::wstring ws2 = std::format(L"hex={:#x}", n); // ワイド版format(フォーマット文字列がL"")
std::wcout << L"to_wstring: " << ws1 << L'\n';
std::wcout << L"format: " << ws2 << L'\n';
// wstring→数値
std::size_t pos = 0;
int m = std::stoi(std::wstring(L"42"), &pos);
std::wcout << L"stoi(L\"42\") => " << m << L" (pos=" << pos << L")\n";
}
to_wstring: 255
format: hex=0xff
stoi(L"42") => 42 (pos=2)
std::wcout
の出力はロケール設定の影響を受ける点に注意してください。
まとめ
手軽さならstd::to_string
ですが、精度や表記の制御はできません。表示やログの簡易用途に留めるのが安全です。
柔軟な整形と読みやすさならstd::format
(C++20)。幅、精度、基数、符号などを簡潔に指定できます。ロケール非依存で、シリアライズにも向きます。
従来からの方法やロケール依存の入出力が必要ならstd::ostringstream
/std::stringstream
と<iomanip>
。複数値の逐次パースにも便利です。
高速でアロケーションなし、例外なしを求めるならstd::to_chars
/std::from_chars
。ロケール非依存で、入力の空白スキップや0x
接頭辞の扱いは自前で実装します。
文字列→数値の例外処理は、invalid_argument
(一桁も読めず)とout_of_range
(桁あふれ)を使い分けましょう。from_chars
ではerrc
で分岐します。
ロケール、小数点記号、空白や符号、基数の自動判定などはAPIごとに挙動が異なります。要件(ユーザー入力かシリアライズか、速度重視か、ロケール依存か)に合わせて選択しましょう。
NaN/Infの扱いはAPIと実装によって差が出ます。必要なら明示的にチェックし、テストで挙動を確認してください。
ワイド文字列が必要な場合はto_wstring
やワイド版std::format
、std::wostringstream
を使い、to_chars/from_chars
はchar
専用である点に注意します。
本記事のサンプルを土台に、要件に最適な変換APIを選べるようになれば、入力検証、ログ、保存形式、通信プロトコルなどがぐっと堅牢になります。