プログラマー向け:C++ std::stoull の応用と代替テクニック

2025-05-01

std::stoull は、C++ の標準ライブラリ(<string> ヘッダーで定義されています)にある関数の一つで、文字列を符号なしの 64 ビット整数型 (unsigned long long) の値に変換する 役割を持っています。

具体的には、以下のような処理を行います。

  1. 入力
    std::stoull は、変換したい文字列を受け取ります。
  2. 解析
    受け取った文字列の先頭から、符号なしの整数として解釈できる部分を解析します。空白文字は読み飛ばされます。
  3. 変換
    解析された部分を unsigned long long 型の値に変換します。
  4. 戻り値
    変換された unsigned long long 型の値を返します。

さらに、std::stoull にはオプションの引数として、以下のものがあります。

  • int base = 10 (オプション)
    この引数は、文字列を何進数の数値として解釈するかを指定します。デフォルトは 10 進数です。2 から 36 までの値を指定できます。例えば、16 を指定すると 16 進数として解釈されます。
  • std::size_t* pos = nullptr (オプション)
    この引数に std::size_t 型の変数のアドレスを渡すと、変換が終了した文字の位置がその変数に格納されます。もし変換が全く行われなかった場合(例えば、文字列の先頭が数字でなかった場合)、pos は 0 に設定されます。nullptr を渡した場合は、この情報は無視されます。

例外

std::stoull は、以下のような場合に例外をスローする可能性があります。

  • std::out_of_range
    変換結果の数値が unsigned long long 型の表現可能な範囲を超えた場合にスローされます。
  • std::invalid_argument
    入力された文字列が、数値として全く解釈できなかった場合にスローされます(例えば、空の文字列や数字以外の文字で始まる文字列など)。
#include <iostream>
#include <string>

int main() {
    std::string str1 = "12345";
    std::string str2 = "FF";
    std::string str3 = "100000000000000000000"; // unsigned long long の範囲を超える可能性
    std::string str4 = "  6789abc";
    std::string str5 = "xyz";

    try {
        unsigned long long num1 = std::stoull(str1);
        std::cout << "str1 を変換: " << num1 << std::endl; // 出力: str1 を変換: 12345

        unsigned long long num2 = std::stoull(str2, nullptr, 16);
        std::cout << "str2 (16進数) を変換: " << num2 << std::endl; // 出力: str2 (16進数) を変換: 255

        size_t pos;
        unsigned long long num4 = std::stoull(str4, &pos);
        std::cout << "str4 を変換: " << num4 << ", 変換終了位置: " << pos << std::endl; // 出力: str4 を変換: 6789, 変換終了位置: 6

        try {
            unsigned long long num3 = std::stoull(str3);
            std::cout << "str3 を変換: " << num3 << std::endl;
        } catch (const std::out_of_range& e) {
            std::cerr << "範囲外エラー: " << e.what() << std::endl; // 出力: 範囲外エラー: stoull: out of range
        }

        try {
            unsigned long long num5 = std::stoull(str5);
            std::cout << "str5 を変換: " << num5 << std::endl;
        } catch (const std::invalid_argument& e) {
            std::cerr << "無効な引数エラー: " << e.what() << std::endl; // 出力: 無効な引数エラー: stoull: no conversion
        }

    } catch (const std::exception& e) {
        std::cerr << "例外が発生しました: " << e.what() << std::endl;
    }

    return 0;
}


std::invalid_argument (無効な引数エラー)

  • トラブルシューティング

    • 入力文字列の確認
      変換しようとしている文字列が、意図した数値形式になっているか確認してください。先頭に不要な空白や文字がないかなどもチェックします。
    • 基数の確認
      base 引数を指定している場合は、その値が 2 から 36 の範囲内であることを確認してください。デフォルトの 10 進数で問題ない場合は、base 引数を省略するか 10 を明示的に指定します。
    • 変換前にチェック
      変換を試みる前に、文字列の先頭が数字であるかなどを自分でチェックするロジックを追加することも有効です(例: isdigit(str[0]) など)。
    • 入力された文字列の先頭が数字として解釈できない場合(例: 空の文字列、アルファベットで始まる文字列など)。
    • 指定された基数 (base) が無効な値の場合(2 未満または 36 より大きい場合)。

std::out_of_range (範囲外エラー)

  • トラブルシューティング

    • 入力文字列の確認
      変換しようとしている数値が unsigned long long の範囲内に収まるか確認してください。非常に大きな数値を扱う場合は、他の型(例: long double)の使用を検討するか、文字列として処理するなどの代替手段を検討します。
    • より小さい型への変換
      もし扱いたい数値の範囲がより小さい型(例: unsigned longunsigned int)で十分な場合は、対応する変換関数 (std::stoul, std::stou) の使用を検討します。ただし、この場合も範囲外エラーが発生する可能性はあります。
  • 原因

    • 入力された文字列が表す数値が、unsigned long long 型の表現可能な範囲を超えている場合。unsigned long long は通常 0 から 264−1 までの値を扱えます。

予期しない変換結果

  • トラブルシューティング

    • 変換終了位置の確認
      オプションの pos 引数を使用して、変換がどこまで成功したかを確認します。これにより、予期せず途中で変換が終了している場合に、その位置を特定できます。
    • 文字列のトリム
      先頭や末尾の不要な空白文字は、変換前に削除しておくことを推奨します。
    • 文字列全体の検証
      変換後に、変換前の文字列全体が数値として解釈されたか(pos が文字列の終端を指しているか)を確認することで、意図しない部分的な変換を防ぐことができます。
  • 原因

    • 文字列の途中に数字以外の文字が含まれている場合、std::stoull はそこで変換を終了し、それまでの部分を数値として返します。
    • 先頭に空白文字が含まれている場合、std::stoull はそれを読み飛ばして変換を開始します。

ヘッダーファイルのインクルード忘れ

  • トラブルシューティング

    • ソースコードの先頭に #include <string> を追加してください。
  • 原因

    • std::stoull を使用する前に、<string> ヘッダーをインクルードしていない場合。

基本的なトラブルシューティングの流れ

  1. エラーメッセージの確認
    コンパイラや実行時に表示されたエラーメッセージを正確に読み解きます。エラーの種類(std::invalid_argument, std::out_of_range など)から、問題の原因を特定する手がかりが得られます。
  2. 入力データの確認
    変換しようとしている文字列の内容を注意深く確認します。不要な文字や空白が含まれていないか、数値が範囲内に収まっているかなどをチェックします。
  3. コードの再確認
    std::stoull の呼び出し方、オプション引数の指定、例外処理の記述などが正しいかを見直します。
  4. デバッガの利用
    デバッガを使用して、プログラムの実行中に変数の値や処理の流れを確認することで、問題の原因を特定しやすくなります。特に、変換前後の文字列や数値、pos の値などを監視すると有効です。
  5. 例外処理の実装
    try-catch ブロックを使用して、std::invalid_argumentstd::out_of_range などの例外を適切に処理するようにコードを記述します。これにより、プログラムが異常終了するのを防ぎ、より安定した動作を実現できます。


例1: 基本的な文字列から unsigned long long への変換

#include <iostream>
#include <string>

int main() {
    std::string number_str = "123456789012345";
    unsigned long long number;

    try {
        number = std::stoull(number_str);
        std::cout << "変換後の数値: " << number << std::endl;
    } catch (const std::invalid_argument& e) {
        std::cerr << "無効な引数です: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "範囲外エラーです: " << e.what() << std::endl;
    }

    return 0;
}

この例では、文字列 "123456789012345"std::stoull を使って unsigned long long 型の数値に変換しています。try-catch ブロックで囲むことで、変換に失敗した場合の例外 (std::invalid_argumentstd::out_of_range) を捕捉し、エラーメッセージを出力しています。

例2: 変換終了位置の取得 (pos 引数)

#include <iostream>
#include <string>

int main() {
    std::string mixed_str = "98765xyz";
    unsigned long long number;
    std::size_t pos = 0;

    try {
        number = std::stoull(mixed_str, &pos);
        std::cout << "変換後の数値: " << number << std::endl;
        std::cout << "変換が終了した位置: " << pos << std::endl;
        if (pos < mixed_str.length()) {
            std::cout << "変換されなかった残りの部分: " << mixed_str.substr(pos) << std::endl;
        }
    } catch (const std::invalid_argument& e) {
        std::cerr << "無効な引数です: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "範囲外エラーです: " << e.what() << std::endl;
    }

    return 0;
}

この例では、数値と文字が混在した文字列 "98765xyz" を変換しています。pos 引数に &pos を渡すことで、変換が正常に終了した位置が pos 変数に格納されます。これにより、文字列のどの部分までが数値として解釈されたかを知ることができます。

例3: 異なる基数での変換 (base 引数)

#include <iostream>
#include <string>

int main() {
    std::string hex_str = "FF";
    std::string bin_str = "101010";
    unsigned long long hex_num, bin_num;

    try {
        hex_num = std::stoull(hex_str, nullptr, 16); // 16進数として解釈
        std::cout << "16進数 '" << hex_str << "' の変換結果: " << hex_num << std::endl;

        bin_num = std::stoull(bin_str, nullptr, 2);  // 2進数として解釈
        std::cout << "2進数 '" << bin_str << "' の変換結果: " << bin_num << std::endl;
    } catch (const std::invalid_argument& e) {
        std::cerr << "無効な引数です: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "範囲外エラーです: " << e.what() << std::endl;
    }

    return 0;
}

この例では、base 引数を使って異なる基数の文字列を数値に変換しています。"FF" は 16 進数として、"101010" は 2 進数として解釈され、それぞれの 10 進数表現が出力されます。

例4: 変換に失敗するケース (std::invalid_argument)

#include <iostream>
#include <string>

int main() {
    std::string invalid_str = "abc123";
    unsigned long long number;

    try {
        number = std::stoull(invalid_str);
        std::cout << "変換後の数値: " << number << std::endl;
    } catch (const std::invalid_argument& e) {
        std::cerr << "無効な引数です: " << e.what() << std::endl; // "stoull: no conversion" のようなメッセージが出力される
    } catch (const std::out_of_range& e) {
        std::cerr << "範囲外エラーです: " << e.what() << std::endl;
    }

    return 0;
}

この例では、先頭が数字でない文字列 "abc123" を変換しようとしています。この場合、std::stoullstd::invalid_argument 例外をスローします。

#include <iostream>
#include <string>
#include <limits> // std::numeric_limits

int main() {
    std::string large_str = "18446744073709551616"; // unsigned long long の最大値を超える値
    unsigned long long number;

    try {
        number = std::stoull(large_str);
        std::cout << "変換後の数値: " << number << std::endl;
    } catch (const std::invalid_argument& e) {
        std::cerr << "無効な引数です: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "範囲外エラーです: " << e.what() << std::endl; // "stoull: out of range" のようなメッセージが出力される
    }

    std::cout << "unsigned long long の最大値: " << std::numeric_limits<unsigned long long>::max() << std::endl;

    return 0;
}


std::istringstream を使用する方法

std::istringstream は、文字列を入力ストリームとして扱うことができるクラスです。これを利用して、文字列から数値を読み込むことができます。

#include <iostream>
#include <string>
#include <sstream>
#include <limits> // std::numeric_limits

int main() {
    std::string number_str = "123456789012345";
    unsigned long long number;
    std::istringstream iss(number_str);

    // エラーチェックのためにストリームの状態を確認
    iss >> number;
    if (iss.fail()) {
        std::cerr << "変換に失敗しました (istringstream): 無効な入力または範囲外" << std::endl;
    } else {
        // 文字列全体が変換されたか確認 (余分な文字がないか)
        char remaining;
        if (iss >> remaining) {
            std::cerr << "変換に失敗しました (istringstream): 数値の後ろに余分な文字があります" << std::endl;
        } else {
            std::cout << "変換後の数値 (istringstream): " << number << std::endl;
        }
    }

    // 異なる基数での変換 (手動で行う必要あり)
    std::string hex_str = "FF";
    unsigned long long hex_num = 0;
    std::istringstream hex_iss(hex_str);
    hex_iss >> std::hex >> hex_num;
    if (!hex_iss.fail()) {
        std::cout << "16進数 '" << hex_str << "' の変換結果 (istringstream): " << hex_num << std::endl;
    }

    // 範囲外のチェック (手動で行う必要あり)
    std::string large_str = "18446744073709551616";
    unsigned long long large_num;
    std::istringstream large_iss(large_str);
    if (large_iss >> large_num) {
        if (large_num > std::numeric_limits<unsigned long long>::max()) {
            std::cerr << "範囲外エラー (istringstream): " << large_str << std::endl;
        } else {
            std::cout << "変換後の数値 (istringstream, 注意: 範囲外の可能性あり): " << large_num << std::endl;
        }
    } else {
        std::cerr << "変換に失敗しました (istringstream, 範囲外チェック): 無効な入力" << std::endl;
    }

    return 0;
}

利点

  • 書式付きの入力処理が可能です。
  • 複数の値を一度に読み込む場合などに便利です。
  • より細かい制御が可能で、変換の成否や文字列のどこまでが変換されたかなどをより柔軟に判定できます。

欠点

  • 異なる基数での変換は、マニピュレータ (std::hex, std::oct, std::dec) を使用しますが、std::stoullbase 引数ほど直接的ではありません。
  • std::stoull ほど直接的ではなく、エラー処理や範囲チェックを自分で行う必要がある場合があります。

C 標準ライブラリの関数を使用する方法 (strtoull)

C 標準ライブラリには、文字列を符号なし long long 型の数値に変換する strtoull 関数があります。これは C++ でも利用できます。

#include <iostream>
#include <string>
#include <cerrno> // errno
#include <cstdlib> // strtoull

int main() {
    std::string number_str = "123456789012345";
    char* endptr;
    errno = 0; // エラー状態をクリア
    unsigned long long number = std::strtoull(number_str.c_str(), &endptr, 10);

    if (errno == ERANGE) {
        std::cerr << "範囲外エラー (strtoull): " << number_str << std::endl;
    } else if (endptr == number_str.c_str()) {
        std::cerr << "変換に失敗しました (strtoull): 無効な入力" << std::endl;
    } else if (*endptr != '\0') {
        std::cout << "変換後の数値 (strtoull): " << number << ", 変換されなかった残りの部分: " << endptr << std::endl;
    } else {
        std::cout << "変換後の数値 (strtoull): " << number << std::endl;
    }

    // 異なる基数での変換
    std::string hex_str = "FF";
    errno = 0;
    unsigned long long hex_num = std::strtoull(hex_str.c_str(), nullptr, 16);
    if (errno == ERANGE) {
        std::cerr << "範囲外エラー (strtoull, 16進数): " << hex_str << std::endl;
    } else {
        std::cout << "16進数 '" << hex_str << "' の変換結果 (strtoull): " << hex_num << std::endl;
    }

    return 0;
}

利点

  • 範囲外エラーは errno を通じて報告されます。
  • 基数を指定できます。
  • 変換が終了した位置を示すポインタ (endptr) を取得できるため、文字列のどの部分までが変換されたかを知ることができます。
  • C 標準ライブラリの一部であり、多くの環境で利用可能です。

欠点

  • std::string を直接受け取らず、c_str() で C スタイルの文字列に変換する必要があります。
  • C++ の例外機構を使用しないため、エラー処理が errno の確認など、C スタイルになります。

Boost.LexicalCast ライブラリを使用する方法

Boost ライブラリの lexical_cast は、文字列と数値型の間で安全かつ簡単に変換を行うための汎用的な方法を提供します。

#include <iostream>
#include <string>
#include <boost/lexical_cast.hpp>

int main() {
    std::string number_str = "123456789012345";
    unsigned long long number;

    try {
        number = boost::lexical_cast<unsigned long long>(number_str);
        std::cout << "変換後の数値 (Boost.LexicalCast): " << number << std::endl;
    } catch (const boost::bad_lexical_cast& e) {
        std::cerr << "変換に失敗しました (Boost.LexicalCast): " << e.what() << std::endl;
    }

    // Boost.LexicalCast は基数変換を直接サポートしていません (別途処理が必要)

    // 範囲外のエラーも例外として扱われます
    std::string large_str = "18446744073709551616";
    try {
        unsigned long long large_num = boost::lexical_cast<unsigned long long>(large_str);
        std::cout << "変換後の数値 (Boost.LexicalCast, 注意: 範囲外の可能性あり): " << large_num << std::endl;
    } catch (const boost::bad_lexical_cast& e) {
        std::cerr << "範囲外エラー (Boost.LexicalCast): " << e.what() << std::endl;
    }

    return 0;
}

利点

  • 様々な型間の変換に一貫したインターフェースを提供します。
  • 例外 (boost::bad_lexical_cast) を使用したエラー処理を行います。
  • 型安全な変換を提供します。

欠点

  • std::stoull のような直接的な基数変換の機能はありません。
  • Boost ライブラリへの依存性があります。
  • Boost ライブラリを使用できる環境で、型安全な変換をより一般的に行いたい場合
    boost::lexical_cast が選択肢となります。
  • C 標準ライブラリとの互換性や、変換終了位置を簡単に取得したい場合
    strtoull を検討できます。ただし、エラー処理は C スタイルになります。
  • より細かい制御や書式付きの入力を扱いたい場合
    std::istringstream が適しています。ただし、エラー処理や範囲チェックを自分で行う必要があります。
  • 単純な変換で、C++ 標準ライブラリのみを使用したい場合
    std::stoull が最も簡潔で便利です。