int型の限界 解答ページ | Programming Place Plus 新C++編

トップページ新C++編int型の限界

このページの概要

このページは、練習問題の解答例や解説のページです。



解答・解説

問題1 (確認★)

8ビットの情報量で表現できる整数の個数はいくつですか?


2進数で1桁分の情報量が1ビットです。2進数は 0 か 1 だけで表現された数なので、1ビットでは 0、1 の2通り。2ビットでは 00、01、10、11 の4通り。3ビットでは 000、001、010、011、100、101、110、111 の8通りが表現できます。この規則をいいかえると、nビットあれば 2n通りが表現できるということになります(本編解説)。

したがって、8ビットならば 28 = 256通りの表現ができることになります。この表現力を、整数を表現することに使えば、256個の整数が表現できるということになります。

ちなみに、int型の大きさは最低でも 16ビットあることが保証されています(本編解説)。

問題2 (確認★)

次のプログラムはコンパイルエラーになる可能性があります。その理由を説明してください。

#include <iostream>

int main()
{
    int value {123456};
    std::cout << value << "\n";
}


コンパイルエラーの原因になり得るのは、変数 value の初期値が 123456 であることです。この数は、int型の大きさが 16ビットだった場合、表現しきれません。

問題1でみたとおり、nビットで扱える整数は 2n 個なので、16ビットでは 216 = 65536 です。123456 は倍近く大きい整数です。

ここでは説明を簡略化して、「要するに int型で扱いきれない初期値を与えているからコンパイルエラーになり得る」という話にしています。より正確な解説は、本編の上級者向けコラムに譲ります。

int型の大きさは処理系の違いによって決まります。C++ の標準規格として保証しているのは、16ビット以上の大きさであるということだけです(本編解説)。

int型が十分な大きさをもっている処理系だけをターゲットにするのであれば、このプログラムにはなんら問題はありません。int型が小さい処理系のことを考えなければならないのであれば注意が必要です。

問題3 (基本★★)

整数の入力を受け取って、なんらかの処理をおこなうプログラムにおいて、次の各種の問題はどのように検知できますか?

  1. 入力された整数が int型で表現できない
  2. 入力されたデータが整数でない
  3. 入力された整数が、予定していた条件を満たしていない(たとえば、正の整数を要求したのに、負の整数が入力されてきた)


1番は、入力された整数が大きすぎたり、小さすぎたりした場合で、具体的には std::numeric_limits<int>::max() より大きい整数か、std::numeric_limits<int>::min() より小さい整数です。このようなケースでは、入力処理自体がエラーになっているので(本編解説)、以下のようにエラー判定を行うことで検知できます。

#include <iostream>

int main()
{
    std::cout << "Please enter the integer.\n";
    int value {};
    std::cin >> value;
    if (std::cin) {
        // 正常
        std::cout << value << "\n";
    }
    else {
        // エラー
        std::cout << "error\n";
    }
}

実行結果:

Please enter the integer.
100000000000  <-- 入力した内容
error

エラーの判定には if (std::cin)if (std::cin.fail()) が使えます。前者は「真」のときに「エラーが起きていない」、後者は「真」のときに「エラーが起きている」と判定できます。! を使うと、これらの真と偽を逆にできます(if (!std::cin)if (!std::cin.fail()))(本編解説)。


2番の「入力されたデータが整数でない」は、1番と同じ方法で検知できます。いずれにしても、「入力時点でのエラー」という意味で共通だからです。

実行結果:

Please enter the integer.
hello  <-- 入力した内容
error

3番の「入力された整数が、予定していた条件を満たしていない」は、入力時点では正常な入力が行えているので、ここまでの方法では検知できません。このような、プログラムの都合によるチェックは、自分で判定コードを書きます。

#include <iostream>

int main()
{
    std::cout << "Please enter the integer.\n";
    int value {};
    std::cin >> value;
    if (std::cin) {
        // 正常

        // 正の整数かどうか
        if (value >= 0) {
            std::cout << value << "\n";
        }
        else {
            std::cout << "Please enter a positive integer.\n";
        }
    }
    else {
        // エラー
        std::cout << "error\n";
    }
}

実行結果:

Please enter the integer.
-100  <-- 入力した内容
Please enter a positive integer.

問題4 (発展★★★)

整数の加算がオーバーフローしないかどうかを確認するために、次のようにプログラムを書きました。このプログラムの問題点を指摘してください。また、その問題点の解決を試みてください。

#include <iostream>
#include <limits>

int main()
{
    int n1 {};
    int n2 {};

    std::cin >> n1 >> n2;
    if (std::cin) {
        if (n1 > 0) {
            if (n1 + n2 <= std::numeric_limits<int>::max()) {
                std::cout << n1 + n2 << "\n";
            }
            else {
                std::cout << "overflow.\n";
            }
        }
        else if (n1 < 0) {
            if (n1 + n2 >= std::numeric_limits<int>::min()) {
                std::cout << n1 + n2 << "\n";
            }
            else {
                std::cout << "overflow.\n";
            }
        }
        else {
            std::cout << n1 + n2 << "\n";
        }
    }
    else {
        std::cout << "input error.\n";
    }
}


問題点は、実のところ、オーバーフローをチェックできていないことです。

2147483647 + 1 を計算してみると、Visual Studio 2015 では、次の実行結果が得られます。

2147483647 1
-2147483648

“overflow” という出力を期待していたのですが、そうはならないようです(この結果は処理系によって異なる可能性があります)。

int型の上限値を超えていないことを調べるために n1 + n2 <= std::numeric_limits<int>::max() という条件式の if文を書いています。一見正しそうに思えますが、もし n1 + n2 がオーバーフローするのなら、この計算式を実行した時点で未定義の動作ですから(本編解説)、何も保証できません。現実的には、上記の実行結果のように、int型どうしの加算で上限値を超えるのなら、小さい数に変化(上限値を突き抜けたら、下限値側から戻ってくるようなイメージ)する処理系が多いですが、その場合、n1 + n2 <= std::numeric_limits<int>::max() という判定が、オーバーフローしているのにも関わらず「真」になってしまうので、やはり正しく判定できません。

下限値の方も、正負が入れ替わるだけで、同じように問題があります。


この問題を解消するには、if文の条件式を変形します。

#include <iostream>
#include <limits>

int main()
{
    int n1 {};
    int n2 {};

    std::cin >> n1 >> n2;
    if (std::cin) {
        if (n1 > 0) {
            if (std::numeric_limits<int>::max() - n1 >= n2) {
                std::cout << n1 + n2 << "\n";
            }
            else {
                std::cout << "overflow.\n";
            }
        }
        else if (n1 < 0) {
            if (std::numeric_limits<int>::min() - n1 <= n2) {
                std::cout << n1 + n2 << "\n";
            }
            else {
                std::cout << "overflow.\n";
            }
        }
        else {
            std::cout << n1 + n2 << "\n";
        }
    }
    else {
        std::cout << "input error.\n";
    }
}

上限値の判定を、std::numeric_limits<int>::max() - n1 >= n2 という条件式にすることで、この式の中でオーバーフローが発生することがなくなります。n1 は int型の変数なので、そこに入っている値は int型で表現できていますから、std::numeric_limits<int>::max() - n1 の結果は 0以上、上限値以下にしかならないからです。

std::numeric_limits<int>::max() - n1 の結果はいわば、n2 のために残された int型の許容量ということになります。n2 の値が、許容量以内に収まっているなら、n1 + n2 はオーバーフローしません。

下限値の判定の方も、std::numeric_limits<int>::min() - n1 ではオーバーフローしません。n1 が 0 より大きい正の整数であることに注意してください。この計算結果もやはり、n2 のために残された(マイナス方向の)許容量です。n2 が巨大な負数であると、n1 + n2 の計算で、下限値を突き破ってしまうことになります。

長ったらしくて、重複が多いプログラムになっていますが、これは現時点で解説済みの機能だけで実装しているからです。2つの条件式を合体する方法を学ぶと、もう少し整理できます。

【上級】乗算のオーバーフローを調べる場合はさらに複雑になります1


参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る