JP / EN

広告
2023/04/06

C/C++で負の数の剰余は負になる:確実に正にする方法

タグ:c++

結論

C言語やC++では、剰余演算子の戻り値は正とは限らない。
必ず正でほしい場合は、負の数で帰ってきたら正に直す。
    int dividend = -7;  // 割られる数
    int divisor = 5;  // 割る数
    int rem = dividend % divisor;  // -2
    if(rem < 0)
        rem += divisor; // 3
  


どうしてもワンライナーにしたいなら1. 余りにdivisorを足す、2. オーバーしたかもしれないので もう一度 % divisorする、の2回の処理でできる。
    int dividend = -7;  // 割られる数
    int divisor = 5;  // 割る数
    int rem = ((dividend % divisor) + divisor) % divisor;  // 3
    
  


解説:C/C++の仕様

実はこの負の数に対する剰余演算子の挙動は、初期のC言語の仕様(ISO1990) では未定義であった。実際に正の剰余を返す処理系も、 負の剰余を返す処理系も混在していたようだ。 その後「負の数に対しては負の剰余」を返すことが仕様に盛り込まれたので、 今は安心して使える。C++もこれを踏襲している。

剰余の符号は割られる数(%左辺)の符号と一致することが定められている。 これはマイナスとマイナスをかけるとプラス、のような掛け算方式とは違うので 注意が必要だ。 まとめると次の表のようになる。
割る数 割られる数 返り値
5 % 3 == 2
5 % (-2) == 2
-1 % 3 == -1
-1 % (-2) == -1


負の剰余を正に直す必要があるのはやや面倒ではあるが、 改正された後のこの仕様は妥当であるように思える。 負の剰余を正に直すのはこのように簡単であるのに対し、 「正・負の数両方に正の剰余を返す」という実装をしてしまった後で、 やっぱり負の数に対しては負の剰余を返したくなってしまうと、 元の数の符号による場合分けが必要になってしまい煩雑であるからだ。

数学/算数ではどうか

プログラミングから離れて、 剰余の符号問題は算数/数学の学問的にはどう扱われているのだろうか? まず算数の範囲ではこのような問題に明確な根拠をもって答える 方法はないようだ。 そもそも余りのある割り算は小学校でしか扱わず、中学一年生の数学で習う「負の数」 が出てきてから割り算をどうすべきかは習いなおさない。 学校によっては「余りは必ず正の数にする」 とか「割られる方の符号に合わせる」 と教わった、という投稿もネットではいくつか見受けられたが、 どちらもあるのでやはり決まってはいないようだ。 先生の教えた決まりごとに従おう、としかこの範囲では教えられないのだ。

しかしより進んだ数学的にも剰余の定義をどうするか、またしてもどちらかに決める統一的な考えはないようだ。 数学では割り算の余り方式はやや記述上問題がある(イコールで繋いだ左辺と右辺が別物になり、イコールの数学上の定義に違反する) のでmodなる表記を導入して
-2 ≡ 3 (mod 5)
と書く。 これは「5で折り返すような周期的な整数の世界を考えると、3と-2は同等のものと見なせる」 と主張しているに過ぎない。その同等と見なされた..., -7, -2, 3, 8, ... のなかから 代表元として-2と3のどちらがふさわしいか、ということには立ち入っていない。 つまるところ、どちらにするか都度明記して混同を避けるよりほかない。


このエントリーをはてなブックマークに追加

https://wonderhorn.net/