本篇基于我对模运算的一点愚见,写出来供大家参考。
一、为什么会有模运算?
首先,我以钟表为例,举一个例子。
我们想要把4点钟方向的指针拨到9点钟,有两种方法:
方法1、顺时针拨5个小时;
方法2、逆时针拨7个小时;
假如我们把顺时针看作是正向操作,那么方法1就是相当于4+5=9;而方法2就是相当于4-7=-3。
注意,由于这里的-3是相对于12点方向的指针而言的,所以在钟表上表现为指针指向9点钟方向。
在上面的例子中我们发现,实际上我们对钟表的所有操作都是局限在表盘里的1到12这12个固定方向里的。也就是说,即使我们想要完成4+12=16的运算,但在钟表上实际显示的结果仍然是指针指向4。
在上面的例子中就是一种以12为模的模运算。在这个例子中反映出了一个问题:就是我们需要在一些受限的条件下完成正常的加减计算时会出现什么样的结果?
我们日常的加减乘除运算都是不限制数的位数的,即无论结果为多大都能够得到保留,但在计算机中不是这样的,受到寄存器的限制或者是其他条件的限制,当我们的计算结果超出寄存器的有限存贮容量时,就会发生溢出(这就有点类似于上面的钟表一样,超出了有限存储容量就会发生溢出)。溢出的部分会被舍弃,而留下的部分也就成为了“余数”。
而模运算适用于的条件就是在一些数的位数受到限制的环境中使用的(也就是我们常说的以n为模的运算),其目的就是为了将受限制的运算转换为符合计算机运算条件的计算。这也说明了计算机内部的加减运算是遵从模运算的规则。
总之,模运算的目的就是将无限的整数域映射到有限的整数集合里去,使其变为一种逻辑上的环形结构。就像钟表一样,无论走过的时间有多少,都能够准确地显示当前的时间。
二、关于模运算的理解
给定一个一般整数p,任意一个整数n,一定存在等式 :
n = kp + r ;
其中 k、r 是整数,且 0 ≤ |r| < |p|,则称 k 为 n 除以 p 的商,r 为 n 除以 p 的余数。
对于整数 p 和整数 a,b,定义如下运算:
取模运算:a % p(或a mod p),表示a除以p的余数。
(需要注意的一点是,余数并不是唯一的,也不是只有正数。当然,这里说的是在计算机领域。)
同时,在数学上还有一个定义:
我们可以称p为模,写作mod p,而称a是b以p为模的补数,记作:a ≡ b (mod p)。
对于这个取模运算中
- 当同符号整数a,b对p取模,它们的余数相同,记做 a ≡ b (mod p)。例如:4和16 对12取模,余数都是4。因此可以记作 4 ≡ 16 (mod 12)。
- 当整数a,b的符号不同时,它们的余数也是相同的,但是余数可以是正数,也可以是负数。记法同样是 a ≡ b (mod p)。例如:254 ≡ -2 (mod 256),254对256取模,余数可以是254(商为0)或者是-2(商为1)。
由此存在以下结论:
- 在模运算中,一个负数可以用它的正补数来代替。这也计算机定点数减法转加法的理论基础。
- 一个正数和一个负数互为补数时,它们的绝对值之和即为模数。
- “正数a”相对于“模p”的补数是a + kp(k是自然数)。但一般认为正数的补数是正数本身。因为对模p而言,满p会自动丢失,所以正数a的补数a + kp就是正数a本身。
下面我举一个无符号整数相减的例子(以16为模):
0111(7) - 1100(12) = ?
根据无符号整数的计算规则,减法要转为加法,因此上式便转换为:0111+0100 = 1011。1011转变为无符号整数就是11。那么这个11可以看作是0111(7)的补码1 0111(23) - 1100(12)所得。(当然,在c语言中如果求无符号数7-12所得的结果是-5,这是因为在C语言中的定点整数都是采用“补码”存储的,因此1,011由补码转换为原码就是1,101(-5))。
以上仅是我的一己之见,错误之处欢迎大家指正。
参考文章: