C++ での、負の整数の除算・剰余算について考察してみました。
Python の場合
Python で次のコードを実行してみてください。
1 2 |
print("-5//3=", -5//3) print("-5%3=", -5%3) |
結果はこうなります。「-5//3= -2」のあたり、直感的には少し違和感を感じる人もいるかも知れません。
1 2 |
-5//3= -2 -5%3= 1 |
-5 〜 5 の範囲で結果を一覧にしてみるとこうなります。負の数の除算・剰余算が上のような結果になった理由がわかると思います。
i | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
i//3 | -2 | -2 | -1 | -1 | -1 | 0 | 0 | 0 | 1 | 1 | 1 |
i%3 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
C++ の場合
C++ で次のコードを実行してみてください。main() 関数等は省略してあります。
1 2 |
cout << "-5/3= " << -5/3 << endl; cout << "-5%3= " << -5%3 << endl; |
gcc 13.2.0 の場合の結果はこうなります。直感的にはこちらのほうが合っているように思うかもしれません。
1 2 |
-5/3= -1 -5%3= -2 |
ところが、-5 〜 5 の範囲で結果を一覧にしてみるとこうなります。
i | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
i/3 | -1 | -1 | -1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
i%3 | -2 | -1 | 0 | -2 | -1 | 0 | 1 | 2 | 0 | 1 | 2 |
i=0 の前後で除算・剰余算の振る舞いが不自然になっていて、正・負によって計算結果に連続性がありません。そもそもC言語の言語仕様では負数の除算についての定義が無く、実装依存とされてきた歴史からこのようになっています。現実には上のような振る舞いが C/C++言語の標準になっているようですが、連続した数列を扱うときに正・負で場合分けする必要が出てきたり、面倒なことが起こります。
マクロ
対策として、Python と同じふるまいになるようにマクロを作りました。
1 2 3 4 |
// Pythonと同じ動きの剰余演算 #define pydiv(a,b) ((0<=((a)^(b))) ? (a)/(b) : (0<(b)) ? ((a)-(b)+1)/(b) : ((a)-(b)-1)/(b)) // Pythonと同じ動きの除算 #define pymod(a,b) ((0<=((a)^(b))) ? (a)%(b) : ((a)%(b)+(b))%(b)) |
このマクロを使って試してみましょう。
1 2 3 |
for (int i=-5; i<=5; i++) { cout << i << " " << pydiv(i, 3) << " " << pymod(i, 3) << endl; } |
結果は下のようになり、Python と同じ振る舞いになりました。
i | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
i/3 | -2 | -2 | -1 | -1 | -1 | 0 | 0 | 0 | 1 | 1 | 1 |
i%3 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
除算については floor() 関数などもあるので、別の良い方法もあるかもしれません。
例題
AtCoder の ABC334 のB問題を解いてみてください。解説動画では正・負を考慮せずに済むように「座標系をずらす」工夫をして正答していますが、pydiv()・pymod() マクロを使えば Python の解と同じように単純に書くことができます。
