计算机中的取模运算与补码

文章目录

  • 前言
  • 一、模的概念
  • 二、10进制取模运算
    • 1.解释
    • 2.运算
    • 3.举例说明
  • 三、二进制计算机系统
    • 1.说明
    • 2. 取模与补码
  • 四、总结


前言

最近在看redis数据结构时,文中提到了全局哈希表,并且在redis Cluster(redis集群)方案中关于如何处理数据切片和实例的对应分布关系的解决方案中给出了取模的实现思路。对于取模、模数的关键词在我们实际开发中可能并不会出现,但是,它确是很多产品架构中在处理一些数据结构或分布式应用中广泛应用到的,此外它也是计算机组成原理中基础支撑,读了很多相关的网上资料,下面整理出来一些便于理解的相关网文,以及自己的一些总结供学习者参考。


一、模的概念

模实际指的就是一个范围,下面摘抄自百度百科的一段话:

“模”是指一个计量系统的计数范围,如过去计量粮食用的斗、时钟等。计算机也可以看成一个计量机器,因为计算机的字长是定长的,即存储和处理的位数是有限的,因此它也有一个计量范围,即都存在一个“模”。如:时钟的计量范围是0~11,模=12。在计算机中表示n位的计算机计量范围是 0~2^n-1,模= 2的n次幂,“模”实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。任何有模的计量器,均可化减法为加法运算 。就是取反后加1。
假设当前时针指向8点,而准确时间是6点,调整时间可有以下两种拨法:一种是倒拨2小时,即8-2=6;另一种是顺拨10小时,8+10=12+6=6,即8-2=8+10=8+12-2(mod 12).在12为模的系统里,加10和减2效果是一样的,因此凡是减2运算,都可以用加10来代替。若用一般公式可表示为:a-b=a-b+mod=a+mod-b。对“模”而言,2和10互为补数。实际上,以12为模的系统中,11和1,8和4,9和3,7和5,6和6都有这个特性,共同的特点是两者相加等于模。对于计算机,其概念和方法完全一样。n位计算机,设n=8,所能表示的最大数是11111111,若再加1成100000000(9位),但因只有8位,最高位1自然丢失(相当于丢失一个模)。又回到了 00000000,所以8位二进制系统的模为2^8 。
。在这样的系统中减法问题也可以化成加法问题,只需把减数用相应的补数表示就可以了。把补数用到计算机对数的处理上,就是补码 。

从上述的解释中可发现一个结论: 在某个数值范围内的加法运算可在MOD值的介入下转换为减法运算,当然也可由减法运算转换为加法运算。这因此也正好引出来计算机的补码。

为了便于理解,下面列举百度知道上的一个问答:

摘自计算机原理中:
:定点运算中 对整数活小数的补码加减法如
小数:[A]补+[B]补=[A+B]补(mod2) 这个公式该如何理解?
答: 由于定点小数指明了范围是纯小数即(-1~1)的(补码有个-1)。在2个小数做加法时,有可能出现超出1的情况。比如0.5+0.9=1.4。所以就要用MOD2的方法使其还在(纯)小数的范围内。可能会想,那要是不超过范围呢?很简单不超过范围的时候MOD2以后还是原数。为了确保其范围还是小数范围内,同时也为了操作的统一性,所以有MOD2操作。
至于MOD2是什么意思,就是对2取模,通俗的说,如果x>2.,反复操作x-2,直到在某一步x-2操作后,其值落在了(-1~1)范围内.比如,3MOD2的结果就是1。对于负数,就是反复x+2。比如-6MOD2结果是-6+2=-4,-4+2=-2,2-+2=0 就是说结果是0。

总结: 这里在计算 -6 MOD 2 时,加了3次2 才将值落到 (-1,1)范围内。即 -6/2 = -3 余 0 ,即最终的值为取模之后的值,2 为模。下面来说明10进制的模运算。


二、10进制取模运算

1.解释

取模运算(mod)和取余运算(rem)两个概念有重叠的部分,但又不完全一致;主要区别在于对负整数进行除法运算时操作不同。取模主要是用于计算机术语中;取余则更多是数学概念。

2.运算

对于整型数a,b来说,取模运算或者求余运算的方法都是:
1.求整数商: c = [a/b];
2.计算模或者余数: r = a - c*b.

当a和b正负号一致时,求模运算和求余运算所得的c的值一致,因此结果一致。当正负号不一致时,结果不一样。
运算规则:求模运算和求余运算在第一步不同: 取余运算在取c的值时,向0 方向舍入(fix()函数);而取模运算在计算c的值时,向负无穷方向舍入(floor()函数)。

3.举例说明

符号不一致举例:-7 Mod 4 那么:a = -7;b = 4;

第一步:求整数商c:
①进行求模运算c = [a/b] = -7 / 4 =-1.75 向负无穷方向舍入则商为-2(向负无穷方向舍入),
②进行求余运算c = [a/b] = -7 / 4 =-1.75 向0方向舍入则商 -1(向0方向舍入);

第二步:计算模和余数的公式相同,但因c的值不同,
①求模时:r = a - c*b =-7 - (-2)4 = 1,
②求余时:r = a - c
b = -7 - (-1)*4 =-3。

符号一致举例:7 Mod 4 那么:a = 7;b = 4

第一步:求整数商c:
①进行求模运算c = [a/b] = 7 / 4 = 1
②进行求余运算c = [a/b] = 7 / 4 = 1

第二步:计算模和余数的公式相同
①求模时:r = a - c*b =7 - (1)4 = 3,
②求余时:r = a - c
b = 7 - (1)*4 =3。


三、二进制计算机系统

1.说明

在计算机的二进制系统中,如果是64位机,能表示的数字的范围为[0,2(64)-1],此范围的数据均为正数,但是负数该如何表示呢?在计算机中,在模的介入下负数使用补码表示。即负数+正数正好达到模的溢出值2(64) 。 下面来计算补码和模之间的关系。

2. 取模与补码

假如在8位计算机中,模值为 2^8 (代表2的8次幂)

(1)若 X = + 101001 为正数,补0到8位后则表示 00101001 请计算X的补码。而我们知道,在计算机中,正数的补码为原码,故 【X】补 =2^8 +00101001
转换为二进制计算则为 100000000+00101001 = 1 0 0101001 ,最高为1 溢出,则最终的结果值为 0 0101001 ,其中高位0为符号位。

(2)若 X = - 101001 则【X】补 =2^8 -00101001 = 1 00000000 - 00101001 根据二进制减法计算得出结果为 0 11010111 这样高位0溢出得到的结果即为 11010111,此时最高为为符号位。我们都知道计算一个负数的补码,首先是符号位不变,其他位取反,然后再加1。下面我们按照正常计算补码的步骤进行计算:
X = - 101001 由于为负数,所以符号为 为1 则X为 10101001 取反后 为 1 1010110 ,再进行加1 为11010111 由结果可以得出以下结论:

在n位计算机系统中【X】补 =2^n+X = 模 + X 这就是模与补码的关系。

关于在计算机中为什么使用补码来表示,下面上一张图相信你就会明白:
计算机中的取模运算与补码_第1张图片

上图中,+0 和-0 的补码均为00000000…000。


四、总结

下面来总结一下:
(1)模:本质上表示一个范围的临界值,在计算机中,由与它的介入可将减法转换为加法,这因此和补码的本质正好相吻合。
(2)如果想让某个地址落在某个范围内,那就让这个数对模值进行取模,比如模2 就是对2取模。
(3)模的应用比较广泛,比如下面一些算法:【摘自百度百科】

判别奇偶数: 奇偶数的判别是模运算最基本的应用,也非常简单。
已知一个整数n对2取模,如果余数为0,则表示n为偶数,否则n为奇数。

代码如下:

/*
函数名:IsEven
函数功能:判别整数n的奇偶性。能被2整除为偶数,否则为奇数
输入值:intn,整数n
返回值:bool,若整数n是偶数,返回true,否则返回false
*/
bool IsEven(int n)
{
    return(n%2==0);
}

判别素数: 一个数,如果只有1和它本身两个因数,这样的数叫做质数(或素数)。例如 2,3,5,7 是质数,而 4,6,8,9 则不是,后者称为合成数或合数。判断某个自然数是否是素数最常用的方法就是试除法——用不比该自然数的平方根大的正整数去除这个自然数,若该自然数能被整除,则说明其非素数。因为我们知道,一个数若可以进行因数分解,那么分解时得到的两个数一定是一个小于等于sqrt(n),一个大于等于sqrt(n),据此,上述代码中并不需要遍历到n-1,遍历到sqrt(n)即可,因为若sqrt(n)左侧找不到约数,那么右侧也一定找不到约数

代码如下:

/*函数名:IsPrime函数功能:判别自然数n是否为素数。输入值:intn,自然数n返回值:bool,若自然数n是素数,返回true,否则返回false*/
bool IsPrime(unsigned int n)
{
    unsigned maxFactor=sqrt(n);//n的最大因子
    for(unsigned int i=2;i<=maxFactor;i++)
    {
        if(n%i==0)//n能被i整除,则说明n非素数
        {
            return false;
        }
    }
    return true;
}

求最大公约数: 求最大公约数最常见的方法是欧几里德算法(又称辗转相除法),其计算原理依赖于定理:gcd(a,b) = gcd(b,a mod b)
证明:
a可以表示成a = kb + r,则r = a mod b
假设d是a,b的一个公约数,则有d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公约数
假设d 是(b,a mod b)的公约数,则d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公约数
因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证。

代码如下:

/*函数功能:利用欧几里德算法,采用递归方式,求两个自然数的最大公约数函数名:Gcd输入值:unsigned int a,自然数a;unsigned int b,自然数b返回值:unsigned int,两个自然数的最大公约数*/
unsigned int Gcd(unsigned int a,unsigned int b)
{
    if(b==0)
    return a;
    return Gcd(b,a%b);
}
/*函数功能:利用欧几里德算法,采用迭代方式,求两个自然数的最大公约数函数名:Gcd输入值:unsigned int a,自然数a;unsigned int b,自然数b返回值:unsigned int,两个自然数的最大公约数*/
unsigned int Gcd(unsigned int a,unsigned int b)
{
    unsigned int temp;
    while(b!=0)
    {
        temp=a%b;
        a=b;
        b=temp;
    }
    return a;
}

还有水仙花数、模幂运算、凯撒密码等。

下面用一段redis集群方案来总结全文,也作为实战性描述,请细品!
Redis Cluster采用哈希槽来处理数据与实例之间的映射关系,redis集群方案中,一个切片集群共有16384个哈希槽,这些哈希槽类似数据分区,每个键值对都会根据它的key被映射到一个哈希槽中。具体的映射过程分为两大步:首先根据键值对的key,按照某种CRC16算法计算一个16位的值,然后再用这个值对16384取模,得到0~16384范围内的模数,每个模数代表一个相应编号的哈希槽。

参考文章:
https://zhidao.baidu.com/question/203637031.html
https://baike.baidu.com/item/%E5%8F%96%E6%A8%A1%E8%BF%90%E7%AE%97/10739384?fr=aladdin
https://baike.baidu.com/item/%E8%A1%A5%E7%A0%81?fr=aladdin

你可能感兴趣的:(数据结构/算法,数据结构,算法,散列表)