閉じる

C++で数値を文字列に(逆も)変換する方法まとめと注意点

ユーザー入力の検証、ログ出力、ファイル保存やネットワーク越しのやり取りなど、C++では文字列と数値の行き来が頻繁に発生します。

本記事では、C++での数値→文字列、文字列→数値の主要な方法を網羅的に整理し、エラー処理やロケール、精度などの落とし穴まで丁寧に解説します。

C言語からの移行組にも読みやすいよう、段階を踏んで説明します。

数値→文字列の変換方法

std::to_stringの基本

std::to_stringはもっとも手軽に使える変換関数です。

整数も浮動小数点数も簡単に文字列化できますが、浮動小数点では桁数や表記形式を細かく制御できない点に注意が必要です。

サンプルコード

C++
#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::formatstd::ostringstreamstd::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}#でプレフィックス付与

サンプルコード

C++
#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

注意: std::formatはC++20です。コンパイルエラーになる場合は、各コンパイラごとのバージョン指定が必要です。

コンパイラと標準ライブラリのバージョン(GCCなら13以降、Clangならlibc++の対応版、MSVCはVS 2019 16.10+など)を確認してください。

std::ostringstreamとiomanipの使い方

std::ostringstreamはC++98から使える伝統的な方法で、<iomanip>のマニピュレータを併用すると柔軟に整形できます。

ストリームはロケールの影響を受けます。

サンプルコード

C++
#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以降で安定しています。

ロケール非依存です。

サンプルコード

C++
#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::octstd::showbaseで接頭辞
  • std::to_chars: 第4引数に基数(2〜36)を指定
  • 2進はstd::bitset<N>も便利です

サンプルコード

C++
#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進)です。

サンプルコード

C++
#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表記も扱えます。

サンプルコード

C++
#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を渡すと、どこまで読んだかを教えてくれます。

単位や余計な文字の検出に便利です。

サンプルコード

C++
#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以降で広く提供)です。

サンプルコード

C++
#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_charsbaseの自動判定をしない点と、0x接頭辞を受け付けない点がstoiとの大きな違いです。

std::stringstreamでのパース

ストリーム抽出演算子>>は、空白スキップやロケール適用、失敗時の状態確認がしやすいのが特徴です。

複数値の順次パースにも向きます。

サンプルコード

C++
#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ライブラリのstrtolstrtodは例外ではなくerrnoendptrで結果を返します。

Cとのインターフェースが必要な場面や、きめ細かいエラー制御が必要な低レベル層で有用です。

サンプルコード

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を投げます。

例外で明確に分岐しましょう。

サンプルコード

C++
#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は桁あふれです。

サンプルコード

C++
#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::stodCロケールに依存(グローバルなsetlocale)多くは’.’あり部分読みOK、例外で通知
std::to_chars/std::from_charsなし‘.’固定なし高速、バイナリに近い

ロケールに影響されない表現やシリアライズにはto_chars/from_charsstd::formatがおすすめです。

ユーザー入力の「,」小数点を受けたいなら、ストリームのロケールを設定して抽出するのが簡単です。

精度と丸め to_stringの落とし穴

std::to_string(double)は多くの実装で小数点以下6桁程度の固定表記で丸めます。

ラウンドトリップ(文字列化→再パースで元の値に戻す)用途には不適です。

サンプルコード

C++
#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::formatstd::to_charsを使いましょう。

空白 符号 トレーリング文字の扱い

同じ入力でもAPIごとに扱いが異なります。

代表例を比較します。

サンプルコード

C++
#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::stoibase=0時に0xや先頭0の自動判定をします。

一方from_charsは自動判定なし、かつ0xを含む文字列を直接は受け付けません。

サンプルコード

C++
#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"はパース失敗になる実装が一般的です。

サンプルコード

C++
#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_wstringstd::wstringを返します。
  • std::stoistd::stodstd::wstring版の引数も受け付けます。
  • std::to_chars/std::from_charscharバッファ専用で、wchar_tには非対応です。ワイド文字列にしたい場合は変換するか、std::wostringstreamやワイド版std::formatを使います。

サンプルコード

C++
#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::formatstd::wostringstreamを使い、to_chars/from_charschar専用である点に注意します。

本記事のサンプルを土台に、要件に最適な変換APIを選べるようになれば、入力検証、ログ、保存形式、通信プロトコルなどがぐっと堅牢になります。

この記事を書いた人
エーテリア編集部
エーテリア編集部

C++をこれから学ぶ方に向けて、基礎的な文法や標準ライブラリの使い方を紹介します。モダンな書き方も初心者に合わせてやさしく説明しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!