相信熟悉 C/C++的读者对%运算符一定不会陌生,我们将其称为求模运算符,通俗的讲即求一个数被另一个数除后剩余的余数。
%运算符的用法非常简单,我们用形如 a % b 的语句来调用该运算符。其中变量 a,b 必须为整型变量,例如 int、short 等,而不能为浮点数。且 b 变量必须为非零值,若出现模零错误,程序会因为该异常意外终止。在评判系统中表现为评判系统给出了运行时错误,程序未运行完成就异常终止。
以 a % b 语句为例,我们先不加说明的指出该运算的特点。其运算在行为上好像是按如下步骤进行的,首先计算出 a 的绝对值被 b 的绝对值除所得的余数, 再使该余数的符号与 a 保持一致。即若 a 为正数,则该表达式结果必为非负数(可 能为 0);若 a 为负数,则表达式结果必为非正数(可能为 0)。而表达式结果与 b的符号没有直接关系,即a%-b 与 a%b的结果相同。
我们注意到,通过求模运算符求得的余数存在着负数的可能。而这与数论中关于余数的定义是不相符的。数论指出,余数的取值范围为从 0 到除数减 1,即 在 a % b 表达式中,其符合数论规定的的结果取值范围应是 0 到 b - 1。%运算符的运算特性仅保证余数的绝对值在如上所述的范围内,而不保证不会出现负数, 出现负余数也为我们下一步操作带来诸多不便。那么我们必须保证表达式求得的余数在数论定义的区间范围内。相信很多读者心中都有答案,我们只需在该负的余数上再加上除数再对除数求一次余即可。那么它的原理又是如何的呢?我们来看下两式:
r = a%b;
a = k *b + r;
r 即为我们要求的余数,它是由第一式求得的。同时它应符合第二式中关于余数的原始定义,即 a 将等于某个整数与 b 的积再加上余数 r,由于 C/C++的% 算符特点,当 a 为负数时 r 很可能出现负数(或为 0),我们为了得到正确范围内 的余数,我们可以对该式作如下变形(这里假设 b 大于 0,否则取绝对值):
a = (k −1)*b + r + b;
若 r 非零,该式同样符合关于余数的相关定义,但是它的余数部分(r + b) 将不再为负数,而是落在我们之前讨论的、数论规定的范围[0,b-1]内,即由 0 至 b-1。而这个符合我们要求的新余数即为原负余数加上 b,我们正是利用该方法来使余数落入所需的区间内。但是读者还需特别注意,即使被除数为负数,余数也 是有可能为 0 的(刚好整除),那么假如我们与对待其他负余数一样为其加上余 数以期其能够落入我们需要的区间内,这将会适得其反(将会使余数等于 b)。 所以我们可以统一的对取得的余数加上除数后再对该和求模,即:
r'=(r+b)%b;
这样做,不仅能对可能出现的负余数做适当的修正,同时对出现的零和正余 数也不会改变他们的值。
另外,我们也可以顺便来看一下为什么在%运算中余数的值看起来好像与除数的符号无关。我们假设 b 为正数,我们利用 r = a % b 求得的余数 r 将会满足 下式:
a = k *b + r;
其中 r 绝对值的取值范围为从 0 到 b-1,其符号保持与 a 一致(除非 r 为 0)。
若此时,我们利用 r = a % -b 来求得的余数 r'又会满足下式
a = k'*(−b) + r';
同样,其中 r 的取值范围为从 0 到 b-1,其符号保持与 a 一致。比较两式我 们即能发现,当 k' 等于-k 时两式成立,且 r'与 r 相同。这就是为什么我们用% 运算符求得的余数看起来好像与除数的符号无关的原因。
最后我们总结一下%运算的数学解释,以式 r = a % b 为例,其所求得的 r将满足下式:
a = k *b + r
其中 k 为某整数,r 的绝对值取值范围是[0,b-1],其符号将与 a 保持一致,
除非其为 0。这就是我们需要了解的%运算符的工作特点。
另外%运算还具有如下运算规律,牢记这些规律将帮助我们有效的避免大数求模中的溢出问题,关于大数求模,在后文中会有所涉及。
(a*b)%c = (a%c*b%c)%c;
(a + b)%c = (a%c + b%c)%c;
参考:wdjs