浮動小数点数 | Programming Place Plus 新C++編

トップページ新C++編

このページの概要

このページでは、小数点を含んだ数(一般的によく「小数」と呼ばれているもの)について取り上げます。コンピュータで小数をうまく、扱うことはとても難しいのですが、ここではまずそうした難しさは考えないことにして、すべてがうまくいく前提で話を進めていきます。小数を扱うために必要な型を紹介し、電卓プログラムで小数を使えるようにします。そして、小数と整数を両方使うプログラムを書くために知っておく必要があるルールを説明します。

以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。



浮動小数点数

電卓プログラムに残された課題として、「3 + 4 - 1」のように3つ以上の数を一気に入力して計算させられないことと、小数点を含んだ数を使えないことが大きいかもしれません。前者は「3 + 4 * 2」だったら、掛け算のほうを優先させなければならないとか、式の長さが毎回異なってもいいようにしなければならないので入力処理自体が難しくなるといった問題かあって、道のりは意外と険しいものです。新C++編としてはもう1つの課題、小数点を含んだ数の使用に対応させることを最後の目標として、このページで解決を図ります。

C++ で小数点を含んだ数を表現するには、浮動小数点数 (floating point number) を用います。ソースコードに書くならこうです。

1.25
0.01
-10.55

何のことはない普通の表記だと思います。これはリテラルであり、浮動小数点数リテラル (floating point number literal) と呼びます。

桁区切り文字(「int型の限界」のページを参照)を入れることも可能です。

少し特殊な書き方として、整数部分が 0 の場合に .123、小数部分が 0 の場合に 123. のように一方の側を省略した記述が許されています。なお、ゼロのことを 0 と書くと整数リテラルですから、浮動小数点数のゼロを記述するなら 0.0 とか .00. のように小数点を明示します。


浮動小数点数を扱う型を、浮動小数点型 (floating point type) と呼びます。

浮動小数点数型と表記されることもあります

浮動小数点型にはいくつか種類がありますが、まずはもっとも基本となる double型 (double type) だけに限定して話を進めることにします。浮動小数点数リテラルの型も double型です。

double型の変数を宣言して、double型の値を扱うプログラムは次のようになります。

#include <iostream>

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

    value = 0.01;
    std::cout << value << "\n";
}

実行結果:

1.25
0.01

別に特別なことはありません。整数を扱うときとの違いは、int型の代わりに double型を使い、整数を与える代わりに浮動小数点数を与えるというだけです。

なお、{} を使った初期化で、明示的に初期値を与えなかったときには 0.0 で初期化されます。

浮動小数点数の入力

浮動小数点数の入力はこれまでどおり、std::cin を使って行えます。

#include <iostream>

int main()
{
    std::cout << "Please enter the floating point number.\n";
    double value {};
    std::cin >> value;
    std::cout << value << "\n";
}

実行結果:

Please enter the floating point number.
1.23  <-- 入力した内容
1.23

このとき、入力内容が 123 のような整数だった場合、123.0 であると解釈され、その入力は成功します。しかし、abc のように浮動小数点数として解釈できないものを入力されるとエラーになります。入力のエラーを検出する方法については「int型の限界」のページで説明した通りです。

#include <iostream>

int main()
{
    std::cout << "Please enter the floating point number.\n";
    double value {};
    std::cin >> value;

    if (std::cin.fail()) {
        std::cout << "input error.\n";
    }
    else {
        std::cout << value << "\n";
    }
}

実行結果:

Please enter the floating point number.
abc  <-- 入力した内容
input error.

ここまで分かれば、電卓プログラムを小数点を含んだ数に対応させるのは簡単です。

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    constexpr auto addition = '+';
    constexpr auto subtraction = '-';
    constexpr auto multiplication = '*';
    constexpr auto division = '/';

    while (true) {

        // 計算式、または "exit" の入力を得る
        std::cout << "Please enter the formula.\n";
        std::string input_string {};
        std::getline(std::cin, input_string);

        // "exit" が入力されたら終わり
        if (input_string == "exit") {
            break;
        }

        // 計算式を分解する
        std::istringstream iss {input_string};
        double value1 {};
        std::string op {};
        double value2 {};
        iss >> value1 >> op >> value2;

        if (iss) {
            // 計算結果を出力
            switch (op[0]) {
            case addition:
                std::cout << value1 + value2 << "\n";
                break;
            case subtraction:
                std::cout << value1 - value2 << "\n";
                break;
            case multiplication:
                std::cout << value1 * value2 << "\n";
                break;
            case division:
                if (value2 == 0.0) {
                    std::cout << "zero divide.\n";
                }
                else {
                    std::cout << value1 / value2 << "\n";
                }
                break;
            default:
                std::cout << "The formula is incorrect.\n";
                break;
            }
        }
        else {
            // 入力がエラーになっている
            std::cout << "error.\n";
        }
    }
}

実行結果:

Please enter the formula.
10.5 + 3.3  <-- 入力した文字列
13.8
Please enter the formula.
20.05 - 2  <-- 入力した文字列
18.05
Please enter the formula.
4.2 * 2.5  <-- 入力した文字列
10.5
Please enter the formula.
8.0 / 2.5  <-- 入力した文字列
3.2
Please enter the formula.
exit

int だったところを double に変えて、整数リテラルの 00.0 に変えただけです。これで電卓プログラムは、浮動小数点数を使った計算に対応できましたし、整数で入力されても問題ありません。

電卓プログラムでの浮動小数点数の使用という意味ではこれだけのことですが、浮動小数点数は使い方によっては非常に難しくなります。その理由として、誤差や精度に関する問題があります。1 の次は 2、2 の次は 3、・・・といったように進んでいく整数とちがって、小数は数と数のあいだに無限のパターンがあります。たとえば、0.0 と 0.1 のあいだには、0.001、0.0011・・・0.099 といった具合です。コンピュータで無限の数のパターンを表現することはできないため、正確に表現できる数はごく限られており、ほとんどの数は近い数(近似値)でごまかすことになります。こうした不正確な数を使って計算しなければならなくなると、その計算結果にも誤差が生まれてきます。

いきなりこうした話にまで踏み込むのは非常に難しいので、このページでは誤差が生まれる可能性があるのだということだけ知っておいてください。ここからは、整数と浮動小数点数が混在する場合のルールについて、知っておくべきことを説明します。

浮動小数点数の演算

剰余の演算子(%%=)においては、そのオペランドに浮動小数点数を使用できません1

double f {};

f = 10 % 2.5;  // コンパイルエラー
f = 10.5 % 3;  // コンパイルエラー
f %= 2.5;      // コンパイルエラー

ここまでに登場しているそのほかの演算子(++=--=**=//=++--)はそれぞれ、浮動小数点数のオペランドが許されます。++-- は 1.0 の加算・減算です。

なお、0.0 で除算することは当然ながらゼロ除算であって、未定義の動作です。

また、演算ではないですが、switch文の条件式として、浮動小数点数を使うことはできません(「switch文」のページを参照)

整数型との混在

浮動小数点型も整数型も、数を扱うという意味では同じような役割をもった型であるといえます。両者を併せて、算術型 (arithmetic types) と呼びます。

ちなみに、char型や bool型も整数型に分類されます。char型はあくまでも文字を表現するために使うべきであって、整数を扱う目的で使うことはお勧めしません(「文字」のページを参照)。bool型と浮動小数点型との混在のルールについては、あとで別のところで取り上げます

C++ の型の分類については、APPENDIX にもまとめてあります。

整数型と浮動小数点型はどちらも数を扱うものなので、計算式の中で混在する可能性があります。たとえば、消費税込みの定価を計算するとか、ある集団の中に含まれるプログラミング経験者は全体の何パーセントか求めるといったときです。このような型が混在する場合のルールは理解しておく必要があります。

異なる型での初期化

まず、変数の宣言時に、異なる型の初期値を与えるとどうなるのかを確認しておきます。

#include <iostream>

int main()
{
    double value {125};  // OK
    std::cout << value << "\n";
}

実行結果:

125

double型の変数を int型の値で初期化しています。

浮動小数点型に対して整数の値を与えた場合、小数点以下が 0 であるものと考えて、浮動小数点型に変換します。つまり、125 で初期化すると、125.0 であると解釈されます。

しかし、整数型から浮動小数点型への変換は、想定どおりの結果にならないこともありえます(難しいので、上級者向けのコラムで説明します)。未定義の動作になる可能性もあるので注意が必要です。この手の変換が安全であるために必要なことは、「変換元の整数型より、変換先の浮動小数点型のほうが大きいこと」です。ほとんどの処理系では int型から double型への変換であれば、これは問題ありません。

たとえば Visual Studio 2015 では int型は 32ビット、double型は 64ビットです。

整数型には int 以外にも型がありますし、浮動小数点型にも double 以外の型があります。ほとんどの処理系で問題がない(だろう)といえるのは、int から double の話であって、整数型から浮動小数点型への変換のすべてが安全だといっているわけではありません。

【上級】整数型から浮動小数点型への変換のルールはこうです。まず、変換先の型で表現できるなら何も問題なく、想定どおりの数に変換されます。次に、変換先の型の限界値の範囲内ではあるが、正確に表現することができない場合には、近似値が処理系定義の方法で選択されます。そして、変換先の型の限界値を超えてしまっている場合には未定義の動作です。2

なお、このように型が自動的に変換されることを、暗黙の型変換(暗黙的な型変換) (implicit conversion) といいます。少し変形して、「int型から double型には暗黙的に変換できる」といった言い回しがなされることもあります。また、標準型変換 (standard conversion) と呼ぶこともあります。

型変換は、その場でだけ起こることであって、元々の型を変更しているということではありません。たとえば、

int main()
{
    int int_value {125};
    double double_value {int_value};

    // int_value はあくまでも int型である
}

この場合、double_value の初期値として、変数 int_value を指定しています。int_value の値が型変換されて double型の値として使われますが、変数 int_value の型が double型に変わるわけではありません。


では反対に、浮動小数点数を使って整数型の変数を初期化するケースではどうでしょう。

#include <iostream>

int main()
{
    int value {1.25};  // コンパイルエラー
    std::cout << value << "\n";
}

このケースでも暗黙の型変換が起こりますが、浮動小数点型から整数型への変換では明確な問題があります。それは、浮動小数点型の値には小数点以下になにか数があるかもしれませんが、整数型では小数点以下の部分を表現できないということです。

C++ では、浮動小数点型から整数型に変換する際には、小数点以下を切り捨てるという動作をとります。そのため、1.25 は 1 になります。

【上級】浮動小数点型から整数型に変換するために小数点以下を切り捨てた結果の数が、変換先の型で表現できない場合は未定義の動作です。3

このような、情報の一部を失うかもしれない暗黙の型変換を {} による初期化は許さないため、コンパイルエラーになります。変数の初期化に {} を使うように勧めているのは、このような情報の欠損に気付けるからです。

なお、int value {1.0}; のように書いた場合、小数点以下は 0 なので情報の欠損はなさそうですが、1.0 は浮動小数点数リテラルであって double型の値なので、やはりコンパイルエラーになります。実際の値でなく、型によって決まるのだということに注意してください。

{} を使わなければ、コンパイルエラーとはなりません(警告は出るかもしれません)。

int value = 1.25;  // OK だが 1 になる


情報が失われるかもしれない変換を暗黙的に行うことは避けるべきです。もし本当に必要なのであれば、それを明確に示すようにします。次のように記述すれば、プログラマーの意志によって、浮動小数点型から整数型に変換しているのだという主張になります。

int value {static_cast<int>(1.25)};  // OK. ただし、情報が失われるという事実に変わりはない
int value = static_cast<int>(1.25);  // OK. ただし、情報が失われるという事実に変わりはない

static_cast は型の変換を強制する記述で、() の内側の式を評価したあとの値が <> で示した型に強制的に変換されます。そのままですが、static cast と呼ばれます。

あらゆる型の間での変換を許すわけではありません。変換が許されない場合はコンパイルエラーになります。

【C言語プログラマー】これは C++ で導入された新しいキャストの構文です。C++ でもC言語と同じ () を使ったキャストも可能ですが、C++ 方式のキャストを使うことが推奨されます。C++ には static_cast 以外にもキャストが種類分けされており、不適切なキャストをエラーとして検知できるようになっています。また、ソースコード内でキャストを行っている箇所を目立ちやすくする効果もあるとされています。

異なる型への代入

次は代入の場合です。

#include <iostream>

int main()
{
    int int_value {15};
    double double_value {};

    double_value = int_value;   // OK
    std::cout << double_value << "\n";

    int_value = double_value;   // OK だが情報を失う恐れがある (警告されるかもしれない)
    std::cout << int_value << "\n";

    int_value = static_cast<int>(double_value);  // OK. 情報を失う恐れがあるが、それを受け入れている
    std::cout << int_value << "\n";
}

実行結果:

15
15
15

代入の場合のルールは初期化のときと同じです。

浮動小数点型から整数型への代入では、{} を使うとコンパイルエラーになります。

int_value = {double_value};   // コンパイルエラー

異なる型の混ざった計算式

整数型と浮動小数点型が1つの計算式の中で混在している場合はどうでしょう。

演算子のオペランドが2つある場合、演算子ごとの処理(加算とか減算とか)を始める前に、オペランドの型が調べられます。もし、オペランドの片方が浮動小数点型で、他方が整数型の場合、浮動小数点型のほうに合わせるように型が変換されます。4

たとえば、10 + 3.3 という式では、加算演算子の2つのオペランドはそれぞれ int型と double型です。double型が浮動小数点型なので、int型の 10 を double型に合わせて変換します(10.0 になる)。こうして型を揃えてから、実際の処理(ここでは加算)が行われます。10.0 + 3.3 という式を評価して 13.3 が得られます。この計算結果もやはり double型です。そのため、実際のコードで、以下のように書いたとします。

// result は int型の変数
result = 10 + 3.3;  // OK だが情報を失う (警告されるかもしれない)

この場合、double型の 13.3 を int型の変数に代入することになるので、代入時に暗黙の型変換が起こります。小数点以下が捨てられて、result に代入される値は 13 です。

ここでも static_cast を使って、int型に変換していいのだと明示できます。

// result は int型の変数
result = static_cast<int>(10 + 3.3);  // OK. 警告はされないが、情報は失っている

bool型との混在

最後に、bool型と浮動小数点型が混在する場合についてですが、このケースに関しては、そもそも「そんなことに意味があるのか?」という指摘をするべきでしょう。論理値と浮動小数点数を混ぜ合わせることに、納得がいくような意味はおそらくありません(たとえば、true * 3.5 にどんな意味があるのか?)。ですから避けるのが基本です。

【上級】実際、静的解析ツールに指摘されることがありますし、static_cast を使ってもなお、コンパイラは警告を出してくることがあります。

一応、簡潔に説明しておくと、bool型の値から浮動小数点型には暗黙の型変換が働き、元の値が true のときは 1.0 に、false のときは 0.0 になります。2

反対に、浮動小数点型から bool型にも暗黙の型変換が働きます。こちらは、0.0 以外のときは true に、0.0 のときには false になります。5ただし、こちらは縮小変換にあたるので、{} を使うとコンパイルエラーになります。

演算子の2つのオペランドが、bool型と浮動小数点型だった場合、bool型の側が浮動小数点型に変換されます(整数型と浮動小数点型だったときと同じです)。たとえば true * 3.5 は、true1.0 に変換され 1.0 * 3.5 を計算していることになります4

まとめ


新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。


参考リンク


練習問題

問題の難易度について。

★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。

問題1 (確認★)

次のプログラムがコンパイルエラーになる理由を説明してください。

#include <iostream>

int main()
{
    double double_value {2.0};
    int int_value {double_value};

    std::cout << double_value << "\n";
    std::cout << int_value << "\n";
}

解答・解説

問題2 (確認★)

次のそれぞれの式で、2つのオペランドは何型として扱われますか?

  1. 2.5 + 4.0
  2. 2.5 + 10
  3. true + 2.5
  4. ‘a’ + 2.5

解答・解説

問題3 (基本★)

商品の値段を入力すると、消費税込みの価格を出力するプログラムを作成してください。消費税率は 8% および 10% として、両方の場合を出力するようにしてください。

解答・解説

問題4 (基本★★)

1 を 2 で割った結果として 0.5 を得るには、どのようにコードを書けばいいですか?

解答・解説

問題5 (応用★★)

double型から int型の暗黙の型変換では、小数点以下が捨てられますが、四捨五入にしたい場合はどうすればいいでしょうか?

解答・解説


解答・解説ページの先頭



更新履歴




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