あるプログラムを作っていて、”負の値に対する剰余算結果” がややこしいなぁと感じたので、このページで情報をまとめておこうと思います。
特に除数が正の整数の場合に剰余演算も正の整数として得たい場合があると思いましたので、その際の対処法も記載しています。よろしければこちらも参考にしてください!
Contents
剰余演算結果の符号は “被除数” と同じになる
C言語において剰余演算は %
演算子によって実行することができ、被除数(割られる数)を除数(割る数)で割った時の余りを求めることができる演算になります。
被除数 % 除数
ややこしいのは、剰余演算の結果は “正の整数” としても “負の整数” としても表現できてしまうという点です。
C言語(特に C99 以降)においては、この剰余演算の結果を “正の整数” とするか、”負の整数” とするかが、除数と被除数それぞれの符号(正負)の関係によって下記のように決まります。
正の整数 % 正の整数
:正の整数
正の整数 % 負の整数
:正の整数
負の整数 % 正の整数
:負の整数
負の整数 % 負の整数
:負の整数
つまり、剰余演算結果の符号は “被除数” の符号と一致することになります。
具体的な数値として考えると下記のような結果になることになります。
17 % 4 = 1
17 % -4 = 1
-17 % 4 = -1
-17 % - 4 = -1
このような計算結果になることは事実なのですが、その一方で、特に 負の整数 % 正の整数
の結果は “正の整数” になって欲しいケースがあると思います(先ほどの例でいうと、-17 % 4
の結果は -1
ではなく 3
になって欲しい)。
例えば、剰余演算を “除数未満の整数に丸める” ことを目的として使用するケースなどがコレに当てはまると思います。
もっと具体的にいうと、リングバッファとして扱う配列の添字を剰余演算で計算する場合などです。配列の添字は “正の整数” である必要がありますが、剰余演算時に被除数が負の整数になってしまうと剰余演算結果も負の整数になり、さらにそれが配列の添字として扱われると配列外にアクセスすることになり不正アクセスとなってしまいます。
上記のようなケースにも対応できるように、ここからは 負の整数 % 正の整数
の結果を “正の整数” として得たい場合の対処法を解説していきたいと思います。
負の整数 % 正の整数
の結果を “正の整数” にしたい場合の対処法
負の整数 % 正の整数
の結果が “負の整数” になるのは事実なので、これはもう変えられないです。
なので、この事実は受け止めて、下記のどちらかの方針で剰余算結果を正の整数として得るようにするのが良いと思います。
- 被除数を正の整数にしてから剰余演算を行う
- 剰余演算結果で得られた負の整数を正の整数にする
スポンサーリンク
被除数を正の整数にしてから剰余演算を行う
剰余演算では、被除数に除数の倍数を足したとしても結果が変化しないという特徴があります。例えば被除数の整数を x
、除数の整数を y
、さらに n
を任意の整数とすると下記が成立します。
x % y = (x + n * y) % y
ですので、被除数が負の整数の場合は、被除数が正の整数になるまで除数の倍数を足してやれば、負の整数 % 正の整数
の結果を 正の整数 % 正の整数
により得ることができるようになります。
そして、前述の通り 正の整数 % 正の整数
の結果は “正の整数” ですので、結果的に 負の整数 % 正の整数
の結果を “正の整数” として得られることになります。
具体的に、”被除数が負の整数” “除数が正の整数” の場合に剰余演算結果を “正の整数” として得るための関数例が下記のようになります。
#include <stdio.h>
#include <stdlib.h>
int positive_mod(int x, int y) {
int divisor = y; /* 除数 */
int dividend = x; /* 被除数 */
while (dividend < 0) {
/* 被除数が正の値になるまで除数を足し算 */
dividend += divisor;
}
return dividend % divisor;
}
int main(int argc, char *argv[]) {
int dividend;
int divisor;
int remainder;
if (argc != 3) {
printf("引数に被除数と除数を指定してください\n");
return -1;
}
dividend = atoi(argv[1]);
divisor = atoi(argv[2]);
remainder = positive_mod(dividend, divisor);
printf("%d %% %d = %d\n", dividend, divisor, remainder);
return 0;
}
positive_mod
関数が、”被除数が負の整数” “除数が正の整数” の場合に剰余演算結果を “正の整数” として算出する関数になります。
ただし、対応しているのはあくまでも “除数が正の整数” の場合のみです。除数が “負の整数” の場合はうまく動作しないので注意してください。
剰余演算結果で得られた負の整数を正の整数にする
剰余演算で得られる結果は、正の整数であっても負の整数であっても絶対値は必ず “除数未満” となります。
ですので、剰余演算結果が負の整数である場合は、その結果に “除数” を一回足してやれば、剰余演算を結果を正の整数として得ることができます。
この方針で剰余演算を算出する場合、先程示した positive_mod
関数は下記のようになります。
#include <stdio.h>
#include <stdlib.h>
int positive_mod(int x, int y) {
int divisor = y; /* 除数 */
int dividend = x; /* 被除数 */
int remainder;
remainder = dividend % divisor;
if (remainder < 0) {
/* 除数を足して正の整数にする */
remainder += divisor;
}
return remainder;
}
int main(int argc, char *argv[]) {
int dividend;
int divisor;
int remainder;
if (argc != 3) {
printf("引数に被除数と除数を指定してください\n");
return -1;
}
dividend = atoi(argv[1]);
divisor = atoi(argv[2]);
remainder = positive_mod(dividend, divisor);
printf("%d %% %d = %d\n", dividend, divisor, remainder);
return 0;
}
ただし、こちらも対応しているのはあくまでも “除数が正の整数” の場合のみなので注意してください。
まとめ
このページでは負の値に対する剰余演算の動作についてまとめました。さらに、剰余演算の被除数が負の値である時に、剰余演算結果を正の値にするための対処法についても解説しました。
基本的に剰余演算結果の符号は、被除数と同じ符号になります。つまり、剰余演算結果が “正の値” or “負の値” のどちらになるかは被除数によって決まります。
正の整数 % 正の整数
:正の整数
正の整数 % 負の整数
:正の整数
負の整数 % 正の整数
:負の整数
負の整数 % 負の整数
:負の整数
ただし、特に 負の整数 % 正の整数
の演算結果としては、負の整数ではなく “正の整数” のものを得たいという方も多いのではないかと思います。その際は下記の方針で剰余演算結果を正の整数として得らようにしましょう!
- 被除数を正の整数にしてから剰余演算を行う
- 剰余演算結果で得られた負の整数を正の整数にする
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/