乗算と除算 | Programming Place Plus 新C++編

トップページ新C++編

このページの概要

このページでは、乗算(掛け算)と除算(割り算)を取り上げます。また、除算に関係する少し特別な計算として、剰余も取り上げます。計算式の中に乗算や除算が混ざると、計算の順番に気を付けなければなりません。除算には重要な注意事項があることに触れます。

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



乗算

前のページでは、加算と減算を扱いました。次は乗算(掛け算)をやってみます。

C++ の乗算は次のように書きます。

5 * 2

乗算をあらわすには * を使います。× は使えません。

+- がそうであったように* も演算子の一種です。オペランドは2つなので、二項演算子に分類されます。呼び方はさまざまですが、以降は乗算演算子 (multiplication operator) と呼ぶことにします。

5 * 2 の計算をおこなうプログラムの全体像はこうなります。

#include <iostream>

int main()
{
    std::cout << 5 * 2 << "\n";
}

実行結果:

10

除算

次は除算(割り算)です。

C++ の除算は次のように書きます。

10 / 2

除算をあらわすには / を使います。÷ は使えません。

/ は二項演算子です。これもやはり呼び名はさまざまですが、以降は除算演算子 (division operator) と呼ぶことにします。

10 / 2 の計算をおこなうプログラムの全体像はこうなります。

#include <iostream>

int main()
{
    std::cout << 10 / 2 << "\n";
}

実行結果:

5

計算結果に小数点以下ができるとき

除算の場合、整数だけで計算しても、計算結果には小数点以下の数が出てきてしまうことがあります。たとえば、3 / 2 は 1.5 ですし、10 / 3 は 3.3333・・・のような結果になるはずです。

しかし、C++ では、整数どうしの除算の計算結果は必ず整数になります。計算結果が 1.5 なら 1 になりますし、計算結果が -1.5 なら -1 になります1。考え方としては、普通に小数点以下も考えて計算し、計算結果の小数点以下を単純に無視すればいいです。

【C言語プログラマー】このルールは C99 と同じです。

【C++98/03 経験者】C++98/03 では、小数点以下を切り捨てるときのルールは、コンパイラの実装に任されていました。2

実際に試してみると、次のようになります。

#include <iostream>

int main()
{
    std::cout << 3 / 2 << "\n";
    std::cout << 10 / 3 << "\n";
    std::cout << -5 / 2 << "\n";
    std::cout << 5 / -2 << "\n";
    std::cout << -5 / -2 << "\n";
}

実行結果:

1
3
-2
-2
2

ゼロ除算

整数の除算で注意しなければならないこととして、0 で割ってはならないというものがあります。つまり、3 / 0 とか 10 / 0 のような、除算演算子の右オペランドが 0 になっている除算はしてはいけません。左オペランドは 0 でも構いません。

ゼロで割ることをよくゼロ除算 (divide by zero) というので、ここからはそう書きます。

C++ では、整数のゼロ除算は 未定義動作 (undefined behavior) です3

【上級】「整数の」と書いていることに注意してください。整数でない場合のゼロ除算では事情が異なっており、未定義動作にならないこともあります。かなり難しい話題になりますし、当面、整数しか扱いませんので、ここでは詳細には触れないでおきます。

未定義動作とは、標準規格としてはどのような結果になるか一切定めていないということです。また、コンパイラを作る側としても、未定義動作とされている部分については、何も考慮すらせずに作っている可能性もあります。ビルドできるかもしれませんし、できないかもしれません。ビルドできたとしても、プログラマーが想像するような結果にはならないかもしれません。

これではプログラマーとしても、プログラムに対して何も責任をもてないですから、未定義動作を含んだプログラムは書いてはいけません。

これから先も C++ の学習を進めていくと、未定義動作とされるものがいくつか登場します。この言葉には最大限に注意してください。


Visual Studio 2015 の場合、次のプログラムはビルドエラーになります。

今後、ビルドが成功しなかったり、実行するとエラーになるソースコードを示すときには、このように背景を赤くして示すことにします。

#include <iostream>

int main()
{
    std::cout << 3 / 0 << "\n";
}

ビルド結果:

main.cpp(5): error C2124: 除算、剰余演算が 0 で行われています。

これは親切なケースといえますが、ビルドできるコンパイラもあり得ますから注意してください。

計算の順番

計算式に乗算や除算があると、計算の順番に注意しなければならなくなります。加算や減算よりも、乗算や除算のほうが先に計算されます。

#include <iostream>

int main()
{
    std::cout << 5 + 2 * 3 << "\n";
    std::cout << 5 - 6 / 3 << "\n";
}

実行結果:

11
3

5 + 2 * 3 は、加算よりも乗算が先に計算されますから、まず 2 * 3 が計算されて 6 になります。そして 5 + 6 が計算されて 11 という結果を得ます。

5 - 6 / 3 のほうも同様です。減算よりも除算が先に計算されています。

演算子の優先順位

乗算や除算が、加算や減算よりも先に計算されるという話は、言い換えると、式の中に式があるときに、どの式を優先するかという話です。

「演算子とオペランドを組み合わせたもの」を式と呼ぶのでした。ですから、5 + 2 * 3 も式です。この式は、5 + 2 とか 2 * 3 というふうに分解できそうですが、分解してみてもやはり式のかたちをしています。ですから、「式の中に式がある」といえます。

式の中に式があるとき、どの式からみていけばいいのか。それを決定付けるのが、演算子の優先順位 (Operator Precedence) です。C++ に存在する演算子にはそれぞれ、優先順位が番号付けられていて、優先順位が高いものが優先されることになっています。それによると、

となっています。ですから、乗算演算子と除算演算子を使った式のほうが、加算演算子と減算演算子を使った式よりも優先されます。

演算子の優先順位は、自然に感じられるように設計されていますから、すべてを覚えようとしなくても大丈夫です。


それでは、演算子の優先順位が同じ式が複数あるときはどうなるのでしょうか。たとえば、3 * 4 / 2 のような場合です。

この場合のルールは演算子ごとに決められていますが、加算演算子、減算演算子、乗算演算子、除算演算子の場合はいずれも、左側にあるものを優先します。ですから、3 * 4 / 23 * 4 が先に計算されて 12 になり、次に 12 / 2 が計算されて 6 が得られます。

このルールもまた、自然に感じられるように設計されていますから、すべてを覚えようとしなくても大丈夫です。

【上級】左から優先されるのは、加算演算子、減算演算子、乗算演算子、除算演算子が左結合の演算子だからです。3 * 4 / 2(3 * 4) / 2 のように解釈されます。これは、評価順序の話とは異なることに注意してください。f1() * f2() / f3() のように関数呼び出しを含んだ場合に、(f1() * f2()) / f3() と解釈されることは保証されますが、どの関数呼び出しから評価されるかは不定です。

括弧で計算の順番を強制する

加算や減算を先に計算してほしいときは、その部分を ( ) で囲みます。すると、( ) で囲まれた部分が先に計算されるようになります。

#include <iostream>

int main()
{
    std::cout << (5 + 2) * 3 << "\n";
    std::cout << (5 - 2) / 3 << "\n";
}

実行結果:

21
1

( ) が複数登場しても構いません。

#include <iostream>

int main()
{
    std::cout << (5 + 2) * (3 - 1) << "\n";
    std::cout << (5 - 2) / (3 - 1) << "\n";
}

実行結果:

14
1

括弧の内側にさらに括弧があるような書き方も問題ありません。ただし、括弧の形状はいつも () であって、[]{} を使うことはできません。

#include <iostream>

int main()
{
    std::cout << ((2 + 1) * (5 - 3)) / 2 << "\n";
}

実行結果:

3

この場合、内側の括弧の中が先に計算されます。2 + 1 は 3 になり、5 - 3 は 2 になりますから、(3 * 2) / 2 です。次に 3 * 2 が 6 になり、6 / 2 で 3 が最終的な結果です。

剰余

除算に関連して、少し特殊な計算を紹介しておきます。

C++ では、% であらわされる演算子を使って、整数の除算をした余り(剰余)を求められます。やはり呼び名ははっきりしませんが、剰余演算子 (modulo operator) と呼ぶことにします。剰余演算子はオペランドが2つの二項演算子です。

剰余演算子は次のように使います。

10 % 3

この場合の計算結果は、「10 / 3」を行ったときに出る余りの部分、つまり「1」です。ビルドできるプログラムにすると、次のようになります。

#include <iostream>

int main()
{
    std::cout << 10 % 3 << "\n";
}

実行結果:

1

余りがでない場合の結果は当然 0 です。

#include <iostream>

int main()
{
    std::cout << 9 % 3 << "\n";
}

実行結果:

0

剰余演算子の優先順位は、乗算演算子や除算演算子と同じです。よって、剰余は、乗算・除算と同様、加算や減算よりも先に計算されます。

#include <iostream>

int main()
{
    std::cout << 4 + 4 % 3 << "\n";
}

実行結果:

5

剰余演算子の式が複数登場する場合は、左から先に計算されます。これも、乗算演算子や除算演算子と同じルールです(加算演算子や減算演算子でも同じ)。

#include <iostream>

int main()
{
    std::cout << 20 % 7 % 5 << "\n";
}

実行結果:

1

また、剰余は除算をしているので、ゼロ除算の話題は剰余演算子にも当てはまります。10 % 0 のように、右オペランドが 0 の剰余計算は未定義動作なので、書いてはいけません。

剰余の符号

剰余演算子による剰余計算の結果の符号は、左オペランドの符号に合わせられます。

考え方としては、左オペランド、右オペランドともに符号を無視して余りを求めてから、左オペランドの符号を付ければいいです。

#include <iostream>

int main()
{
    std::cout << 5 % 2 << "\n";
    std::cout << -5 % 2 << "\n";
    std::cout << 5 % -2 << "\n";
    std::cout << -5 % -2 << "\n";
}

実行結果:

1
-1
1
-1

【C++98/03 経験者】C++98/03 では、剰余演算子の2つのオペランドの符号がともに正ならば結果の符号も正ですが、それ以外の場合の結果の符号は処理系定義でした。4

まとめ


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


参考リンク


練習問題

問題の難易度について。

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

問題1 (確認★)

次のプログラムの実行結果はどうなりますか?

#include <iostream>

int main()
{
    std::cout << 3 + 3 * 3 << "\n";
    std::cout << 3 + 3 / 3 << "\n";
    std::cout << 3 + 3 % 3 << "\n";
}

解答・解説

問題2 (確認★)

次のプログラムの実行結果はどうなりますか?

#include <iostream>

int main()
{
    std::cout << 3 / 1 << "\n";
    std::cout << 3 / 2 << "\n";
    std::cout << 3 / 3 << "\n";
    std::cout << 3 / 4 << "\n";
    std::cout << 3 / -1 << "\n";
    std::cout << 3 / -2 << "\n";
    std::cout << 3 / -3 << "\n";
    std::cout << 3 / -4 << "\n";
}

解答・解説

問題3 (確認★)

次のプログラムの実行結果はどうなりますか?

#include <iostream>

int main()
{
    std::cout << 3 % 1 << "\n";
    std::cout << 3 % 2 << "\n";
    std::cout << 3 % 3 << "\n";
    std::cout << 3 % 4 << "\n";
    std::cout << 3 % -1 << "\n";
    std::cout << 3 % -2 << "\n";
    std::cout << 3 % -3 << "\n";
    std::cout << 3 % -4 << "\n";
}

解答・解説

問題4 (確認★)

3 + 5 の計算結果と、2 + 4 の計算結果を掛け合わせた結果を出力するプログラムを作成してください。

解答・解説

問題5 (確認★★)

次のプログラムの中で、問題がある行をすべて挙げてください。

#include <iostream>

int main()
{
    std::cout << 3 / 0 << "\n";
    std::cout << 0 / 1 << "\n";
    std::cout << 3 % 0 << "\n";
    std::cout << 0 % 1 << "\n";
    std::cout << 1 / 0 + 1 << "\n";
    std::cout << 1 / (0 + 1) << "\n";
    std::cout << 1 / (3 % 2) << "\n";
    std::cout << 1 / (3 % 3) << "\n";
}

解答・解説

問題6 (応用★★★)

10 / 3 の計算結果は 3 に、10 / 4 なら 2 になりますが、これは小数点以下が切り捨てられているということです。小数点以下を切り上げることを望むのなら、どのようにプログラムを書けばよいでしょうか?

解答・解説


解答・解説ページの先頭



更新履歴




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