Help us understand the problem. What is going on with this article?

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

いままで何気なく剰余演算, 例えば 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 では整数/浮動小数点数どちらもエラーになります.

参考文献

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away