要約
算術式は、読むときのように左から右へ計算されるのではなく、隠れた構造、すなわち式の木に従って計算される。この木を決めるのは演算子の優先順位と結合規則と括弧である。そして木の各ノードでは、オペランドの型がどの演算を行うかを決めるので、7 / 2 は 3 になり、7 / 2.0 は 3.5 になる。このレッスンで学ぶのは、整数と実数に対する C++ の五つの算術演算子、C++11 以降 0 方向に切り捨てる整数除算と余りの演算(ここから、余りは被除数の符号をとり、被除数が負のときはユークリッド除法の余りと異なることが従う)、ユークリッド除法の余りを得る方法、式の木、オペランドの型が異なるときの結果の型、そして整数の算術式が意味を失う二つの場合、すなわちゼロによる除算と途中の計算でのオーバーフローである。
学習目標
- C++ の整数除算の商と余りを、負のオペランドの場合も含めて計算し、ユークリッド除法の商と余りと関係づける。
- 優先順位と結合規則に従って算術式の木を描き、その値を計算する。
- 型の異なるオペランドどうしの演算の結果の型を予測する。
- 整数除算と余りを使って量を分解する。数の各桁、時間と分で表した長さ、曜日である。
算術演算子
C++ には二項の算術演算子が五つある。加算 +、減算 -、乗算 *、除算 /、余り % である。さらに、ただ一つのオペランドの符号を変える単項マイナス(-x のように使う)と、オペランドをそのままにする単項プラス +x がある。最初の四つは整数にも実数にも適用され、剰余は整数にだけ適用される。ところが、同じ書き方がいつも同じ演算を表すわけではない。/ の二つのオペランドがともに整数なら、除算は整数除算であり、その結果は整数、すなわち小数部分を除いた商である。どちらかが実数なら、除算は実数の除算である。レッスン 文字・真偽値・型変換・定数 は、この規則の最も目につく帰結を示した。double r{7 / 2}; は 3 を保存する。結果が実数に変換される前に、除算が整数どうしで行われたからである。
このプログラムは五つの演算子を 17 と 5 に適用し、実数の被除数で除算をもう一度行う。
#include <print>int main(){ const int a{17}; const int b{5}; std::println("{} + {} = {}", a, b, a + b); std::println("{} - {} = {}", a, b, a - b); std::println("{} * {} = {}", a, b, a * b); std::println("{} / {} = {}", a, b, a / b); std::println("{} % {} = {}", a, b, a % b); const double x{17.0}; std::println("{} / {} = {}", x, b, x / b);}17 + 5 = 2217 - 5 = 1217 * 5 = 8517 / 5 = 317 % 5 = 217 / 5 = 3.417 を 5 で割ると 3 になり、2 が余る。これは、17 個の物を 5 個ずつの組に分けるときの商と余りである。最後の行は実数を整数で割り、3.4 を得る。注意すべきことに、std::println は実数 17.0 を「17」と書く。空のプレースホルダは値の最短の表現を書くので、書かれたものからは型がわからない。型を見るには、{:.1f} で小数の桁を求める。
演算子 % は実数を受けつけない。7.5 % 2 と書くことは、プログラムが実行時に見つける論理エラーではなく、コンパイルエラーである。コンパイラは、型 double と int のオペランドはこの演算子には不正だと告げる。
#include <print>int main(){ std::println("{}", 7.5 % 2);}main.cpp: In function ‘int main()’:main.cpp:5:28: error: invalid operands of types ‘double’ and ‘int’ to binary ‘operator%’ 5 | std::println("{}", 7.5 % 2); | ~~~ ^ ~実数の除算の余りは存在し、ヘッダ <cmath> の関数 std::fmod がそれを計算する。この関数はレッスン ビットと数学ライブラリ でべき乗とともに学ぶ。C++ にはべき乗の演算子がなく、2 ^ 3 は 8 ではなく別のものになるが、それもそこで説明する。
コンテンツを読み込んでいます…