いままで何気なく剰余演算, 例えば 5.5 % 2.0 == 1.5
を書いてましたが, 実は結構危うかったので報告します. 本記事の内容は全部 wikipedia に載っているんですけどね.
剰余演算
2 つの正の数 , (整数でも整数でなくてもよい) が与えられたとき
を満たす非負の整数 と実数 が一意に存在します. そこで を 商 (quotient), を 剰余 (remainder) と呼び, を と書きます. また, 等式
において を 被除数 (dividend), を 除数 (divisor) と呼びます. ここまでは人類の常識ですね.
問題は, または が負の数を取り得るように剰余演算を拡張する際に生じます. つまり, 2 つのゼロでない数 , が与えられたとき, やはり および適当な区間 に含まれる を用いる一意的な表示
が存在しますが, 「適当な区間」 つまり剰余 の取り得る範囲はどのように設定すべきでしょうか?
この問題に対する考えられる解答としては次の 4 パターンがあり得ます.
- 常に正: .
- 除数と同符号: のとき , のとき .
- 被除数と同符号: のとき , のとき .
- 0 に近い側: .
どの選択肢を採用するかは純粋に定義/仕様の問題です. そして困ったことに, 言語ごとに仕様が異なるのです.
整数型の場合
%
演算子として提供される整数剰余演算は, 多くの言語で 被除数と同符号 が採用されています. これには C/C++, Go, Java, JavaScript, Julia, OCaml, PHP, Rust などが含まれます. Fortran の mod
関数もそうです. しかし, そうでない言語も多くあります:
- 除数と同符号: Lua, Mathematica, Python, Ruby など.
- Lisp, Prolog, Haskell, Julia: これらの言語はいずれも
mod
が除数と同符号,rem
が被除数と同符号の演算になっています. なお Julia では%
はrem
の意味です.
そう, Python/Ruby と C/C++/Julia/Rust で仕様が違う のです. さらに言うと, これに伴って 除算の結果 (上の記法では整数 ) も両グループ間で一致しないということになります. なぜならば, (整数の場合) 商 とは を満たす整数 ですから, 剰余 が違えば商 も変わります. 実際, Python では -3//2 == -2
, C/C++/Rust 等では -3/2 == -1
になります.
浮動小数点数の場合
浮動小数点数に対しても演算子 %
が定義されている言語は限られていますが Rust と Python, Julia ではちゃんと定義されていて, 整数型のときと同じ仕様になっています. なお C/C++ は fmod
/std::fmod
関数がこの機能を提供します.
補足
- Python の
math.fmod
関数は 被除数と同符号 な剰余演算を提供します. - Ruby の
Numeric#remainder
関数は 被除数と同符号 な剰余演算を提供します. (コメントに基づいて追記 2019-09-15) - Fortran の
mod
関数は 被除数と同符号,modulo
は 除数と同符号 な剰余演算を提供します. (コメントに基づいて追記 2019-09-15) - ゼロによる剰余 () はゼロ除算と同じく定義されません. Rust で検証したところ, コンパイラが気づいたらコンパイルが通らず, 通ってしまった場合, 整数ならパニックし, 浮動小数点数なら NaN になりました. Python では整数/浮動小数点数どちらもエラーになります.
参考文献
- 剰余演算 - Wikipedia
- 先行研究その1: C++における負の整数の割り算および余りについて
- 先行研究その2: 余り(剰余)の性質をプログラムに活かす
- 先行研究その3: 負数の剰余を計算してはならない
コメント@scivola
-
1 @cure_honey 2 @osanshouo 1 @scivola
0
被除数は非零に限定しなくてもいいですね。
Ruby の
%
は確かに除数と同符号ですが,それをもってと表現するのはちょっと抵抗を感じます。
被除数と同符号の
Numeric#remainder
も定義されているからです。まあ確かに,演算子が存在し,かつ対応する整商メソッドが存在する
%
のほうが基本的1,という捉え方もあるでしょうけど。remainder
のほうは対応する整商メソッドが存在しない。 ↩Fortran には mod 関数の他に modulo 関数があって、それぞれ記事中の双方の場合に対応していると思います。
mod は絶対値ベースで原点に向かって対称になっており、modulo は数直線上で並進的になっています。
丁度、小数点以下を捨てる時に、 trunc (Fortranではint) と floor があるようなものです。
数学的には並進的な方が便利になってます。
@scivola コメントありがとうございます.
Numeric#remainder
の件, 記事に反映させていただきました.本記事のもともとの意図としては「何も考えず % で剰余を計算すると意図したものじゃないかもよ?」というものだったので, こういうタイトルになりました.
@cure_honey コメントありがとうございます. 記事に反映させていただきました.
あの,る,Ruby もよろしく・・・
有理数もいけます:
小数の剰余は小学校でも習うし,数学的意味も明確なのにイマドキのプログラミング言語で実装されていなかったりするのは何なんでしょうね。