LoginSignup
4
2

More than 3 years have passed since last update.

剰余演算の符号は「被除数と同符号」な言語が多い, ただし Python と Ruby を除く

Last updated at Posted at 2019-09-12

いままで何気なく剰余演算, 例えば 5.5 % 2.0 == 1.5 を書いてましたが, 実は結構危うかったので報告します. 本記事の内容は全部 wikipedia に載っているんですけどね.

剰余演算

2 つの正の数 $x$, $l$ (整数でも整数でなくてもよい) が与えられたとき
$$x = n l + r$$
を満たす非負の整数 $n$ と実数 $r \in [ 0, l )$ が一意に存在します. そこで $n$ を (quotient), $r$ を 剰余 (remainder) と呼び, $r$ を $x \% l$ と書きます. また, 等式
$$r = x \% l$$
において $x$ を 被除数 (dividend), $l$ を 除数 (divisor) と呼びます. ここまでは人類の常識ですね.

問題は, $x$ または $l$ が負の数を取り得るように剰余演算を拡張する際に生じます. つまり, 2 つのゼロでない数 $x$, $l$ が与えられたとき, やはり $n \in \mathbb{Z}$ および適当な区間 $I$ に含まれる $r$ を用いる一意的な表示
$$x = n l + r$$
が存在しますが, 「適当な区間」$I$ つまり剰余 $r$ の取り得る範囲はどのように設定すべきでしょうか?

この問題に対する考えられる解答としては次の 4 パターンがあり得ます.

  • 常に正: $r \in [ 0, | l | )$.
  • 除数と同符号: $l > 0$ のとき $r \in [ 0, l )$, $l < 0$ のとき $r \in ( l, 0 ]$.
  • 被除数と同符号: $x > 0$ のとき $r \in [ 0, | l | )$, $x < 0$ のとき $x \in (-|l|, 0 ]$.
  • 0 に近い側: $r \in ( -|l|/2, |l|/2 ]$.

どの選択肢を採用するかは純粋に定義/仕様の問題です. そして困ったことに, 言語ごとに仕様が異なるのです.

整数型の場合

% 演算子として提供される整数剰余演算は, 多くの言語で 被除数と同符号 が採用されています. これには 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 で仕様が違う のです. さらに言うと, これに伴って 除算の結果 (上の記法では整数 $n$) も両グループ間で一致しないということになります. なぜならば, (整数の場合) 商 $n = x/l$ とは $x = n l + r$ を満たす整数 $n$ ですから, 剰余 $r$ が違えば商 $n$ も変わります. 実際, 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)
  • ゼロによる剰余 ($x\%0$) はゼロ除算と同じく定義されません. Rust で検証したところ, コンパイラが気づいたらコンパイルが通らず, 通ってしまった場合, 整数ならパニックし, 浮動小数点数なら NaN になりました. Python では整数/浮動小数点数どちらもエラーになります.

参考文献

4
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2