2
@osanshouo

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

この記事は最終更新日から1年以上が経過しています。

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

剰余演算

2 つの正の数 x, l (整数でも整数でなくてもよい) が与えられたとき

x=nl+r

を満たす非負の整数 n と実数 r[0,l) が一意に存在します. そこで n (quotient), r剰余 (remainder) と呼び, rx%l と書きます. また, 等式
r=x%l

において x被除数 (dividend), l除数 (divisor) と呼びます. ここまでは人類の常識ですね.

問題は, x または l が負の数を取り得るように剰余演算を拡張する際に生じます. つまり, 2 つのゼロでない数 x, l が与えられたとき, やはり nZ および適当な区間 I に含まれる r を用いる一意的な表示

x=nl+r

が存在しますが, 「適当な区間」I つまり剰余 r の取り得る範囲はどのように設定すべきでしょうか?

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

  • 常に正: r[0,|l|).
  • 除数と同符号: l>0 のとき r[0,l), l<0 のとき r(l,0].
  • 被除数と同符号: x>0 のとき r[0,|l|), x<0 のとき x(|l|,0].
  • 0 に近い側: r(|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=nl+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 では整数/浮動小数点数どちらもエラーになります.

参考文献

2
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
osanshouo

コメント

2 つのゼロでない数 x, l が与えられたとき

被除数は非零に限定しなくてもいいですね。

Ruby の % は確かに除数と同符号ですが,それをもって

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

と表現するのはちょっと抵抗を感じます。
被除数と同符号の Numeric#remainder も定義されているからです。
まあ確かに,演算子が存在し,かつ対応する整商メソッドが存在する % のほうが基本的1,という捉え方もあるでしょうけど。


  1. remainder のほうは対応する整商メソッドが存在しない。 

1

Fortran には mod 関数の他に modulo 関数があって、それぞれ記事中の双方の場合に対応していると思います。
mod は絶対値ベースで原点に向かって対称になっており、modulo は数直線上で並進的になっています。

丁度、小数点以下を捨てる時に、 trunc (Fortranではint) と floor があるようなものです。
数学的には並進的な方が便利になってます。

2

@scivola コメントありがとうございます. Numeric#remainder の件, 記事に反映させていただきました.
本記事のもともとの意図としては「何も考えず % で剰余を計算すると意図したものじゃないかもよ?」というものだったので, こういうタイトルになりました.

@cure_honey コメントありがとうございます. 記事に反映させていただきました.

1

浮動小数点数に対しても演算子 % が定義されている言語は限られていますが Rust と Python, Julia ではちゃんと定義されていて,

あの,る,Ruby もよろしく・・・

p 1.25 % 0.5 # => 0.25

有理数もいけます:

p (4/3r) % (1/2r) # => (1/3)

小数の剰余は小学校でも習うし,数学的意味も明確なのにイマドキのプログラミング言語で実装されていなかったりするのは何なんでしょうね。

0
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
Microsoft Buildで発表された技術情報に関する記事を投稿しよう!
~
競技プログラミング研究月間 - みんなでさらなる高みを目指そう
~