余数与二进制数的数学性质

整数的带余除法定理

余数在计算机中非常重要,二进制计算机只能用有限数目的二进制位处理和表示数据。当数据超出这个限制时,会被截断。这个特点可以用余数描述,而余数本身是由整数除法定义的。在小中学阶段就会学习余数,但主要是学习如何求余数,没有提到除法和余数的严格定义,以及对整数的严格定义。这部分内容属于数学中的整数理论整数集中的除法和余数是由如下事实定义的,其本身也是一个定理,是整数理论的重要基础:

a) 整数的带余除法定理:设\(a,b(b>0)\)为任意整数,存在唯一整数对\(q,r\),使\(a=bq+r\),其中\(0\leq r < b\);

其中\(q,r\)是余数,计做\(a\div b = q……r\),读作a除以b等于q余r。需注意的是上述定义包含了被除数为负的情况,而小中学阶段没有讨论过这种情况。另外这种除法和平时做数学计算的除法有一些区别:

1) 平时使用的数默认是实数,实数集上的常用除法则不是按整数带余除法定义的,虽然实数带余除法定理也定义了种实数中的一种除法,但实数中最常使用的除法还是用分数定义的,数学中并不关心实数中的余数问题(计算机会关心);

2) 整数带余除法定理没定义除数为负的情况,这种情况是把负号给被除数(被除数也有负号就“负负得正”);

讨论了这么多,实际上只比小中学多定义了负被除数的余数。“扩展”余数概念使其支持负被除数,对于之后要讨论的内容并不是必要的,但这样能为之后的讨论带来一些方便。

 

同余关系

为能够借助余数讨论二进制计算方面的问题,要先讨论余数中重要的同余关系。如果\(a,b\)除以\(m\)得到的余数相同,则称\(a,b\)对\(m\)同余,计作\(a\equiv b~(mod~m)\),其中\(\equiv\)表示在“同余”意义下的相等关系。该式子也称为同余式,其有如下的性质:

1) 设\(a\equiv b~(mod~m), c\equiv d~(mod~m)\)。则\((a\pm c)\equiv (b\pm d)~(mod~m)\);

2) 设\(a\equiv b~(mod~m), c\equiv d~(mod~m)\)。则\(ac\equiv bd~(mod~m)\);

\(mod\)还可看作一种计算,比如“\(a~mod~m\)”表示\(a\)除以\(m\)得到的余数值。这种表示下,上述两条性质可推出如下性质:

1) 余数加法定理:\((a\pm b) ~mod~ c = ((a ~mod~ c) \pm (b ~mod~ c)) ~mod~ c\);

2) 余数乘法定理:\((ab) ~mod~ c = ((a ~mod~ c)(b ~mod~ c)) ~mod~ c\);

之后主要用\(mod\)表示“求余数”,而不是用来表示“同余式”。

 

计算机中的余数

计算机编程语言对余数的处理和上面的整数带余除法并不一样,当被除数或除数为负,计算机或编程语言的除法不一定遵守上述整数除法。更麻烦的是,计算机或编程语言对这种情况也没有统一规范。下面给出部分语言中的\(mod\)结果:

1) \((-7) ~mod~ 3\)的结果:

1.1) C++ (G++):输出 -1;

1.2) Java 1.6:输出 -1;

1.3) Python 2.6:输出 2;

1.4) Google计算器:输出 2;

2) \(7~mod~(-3) \)的结果:

2.1) C++ (G++ ):输出 1;

2.2) Java 1.6:输出 1;

2.3) Python 2.6:输出 -2;

2.4) Google 计算器:输出 -2;

这里不去管它们各自使用的除法定义,大致定性的分析下即可。首先这些除法都遵守“\(商\times除数 + 余数 = 被除数\)”,而且余数的绝对值小于除数的绝对值。另外就是它们都没有遵守整数带余除法余数必须非负的要求。而且在处理负除数时,也没有像整数除法那样,把负号放到被除数上。这几种除法对商的取值可分为2种风格:

1) 让商尽量靠近0:C++、Java;

2) 让商尽量靠近负无穷:Python、Google计算器;

之后在讨论负数的算术运算时,可能也会遇到这个除法的问题。

另外有的语言还让小数也支持取余数的运算,这在平时的计算中很少遇到。比如有的语言\(1.2 ~mod~ 1 = 0.2\)。

 

二进制与十六进制整数

f6868b6f-7f7a-4c74-b17d-1dad318f2a03(a)

8325f166-79d4-40c0-aa6d-dbd9e79af204(b)

先讨论数学意义下的二进制非负整数,及其和十进制非负整数的转换。负数和正数在这里区别不大,在前面加上符号即可。但对于计算机中用二进制位表示的二进制数,正负整数间的区别比较大,但这里不讨论这部分内容。

图(a)是二进制数10110001转十进制数177的过程,思路是把10110001拆成1、10000、100000、10000000的和,这样再转化成十进制数就非常容易了。对于十进制数转二进制数。任何十进制数\(N\)都可按(a)方式整理成\(N=a_{0}2^0+a_{1}2^1+a_{2}2^2…\),其中\(a_k\)等于0或1。如果用(b)的方式连除和求余数,就可依次得到\(a_0,a_1…\)的值,即每个位。

另一种计算机中常见的是十六进制计法,由于十进制只使用0~9这10个字符,所以表示十六进制数时用a-f表示10-15,并加上前缀“0x”,比如0x1e表示十进制30。十六进制与二进制有简便转换的方法,设\(N\)为十进制数,可用上图的方法做二进制或十六进制的分解,从而\(N=\sum a_i2^i=\sum b_i{16}^i\),其中\(a_i\in [0,1],b_i\in [0,15]\)。该关系使二进制数可从低到高按“每4位1组”转化为1个十六进制位,比如1010111111可从右到左拆为10/1011/1111,分别等于0x2/0xb/0xf,拼接得到0x2bf。十六进制转二进制同理。该方法可行是因为16是2的幂。由于10不是2的幂,所以十进制与二进制间无法使用这种方法。

在实际的计算机硬件中,并不会使用十六进制。十六进制只是人们为了方便“书写”二进制而引入的,因为目前的计算机大多数是32、64位的,能“一次性处理”达32、64个位,这些二进制位很不方便“阅读”。比如人们在讨论ASCII码的“大写字母I”时,更愿意将其写作“0x49”,而非“01001001”。虽然计算机本身是按“01001001”处理的。

 

二进制小数

二进制小数比二进制整数更复杂,需要先简单讨论有理数小数的关系。有理数是指分子分母都是整数的分数,其中分母不为0。而小数是有理数的一种表示方式。比如整数1有1和0.99…两种表示(1=0.99….)。任何进制的小数的定义都是:“在\(k\)进制中,乘\(k\)表示小数点右移1次,除以\(k\)表示左移1次”。任何进制下,有限小数都只能表示部分有理数。但实数理论指出,任何进制下的有限小数无限循环小数都能一起表示全体有理数集。这方面的数学讨论可参考《数学分析》。

根据上述的小数定义,一个有理数能写成十进制小数当且仅当其能被写成\(A\cdot10^{-x}\)(其中\(A\)为整数,\(x\)为非负整数),能被写成二进制小数当且仅当其能被写成\(A\cdot2^{-x}\)(其中\(A\)为整数,\(x\)为非负整数)。于是有这样的结论:

b) 二进制有限小数一定能转化为十进制有限小数;

c) 十进制有限小数不一定能转化为二进制有限小数;

想证明上述结论很简单,对于(b)因为10=2*5,所以不停用10乘二进制小数就能把\(2^{-x}\)消掉,多出来的5吸收进\(A\)即可。而对于(c)则是因为无论乘多少次2都无法消掉10=2*5中,5的负幂次。比如十进制0.1就无法用有限二进制小数表示。

小数的这种性质在计算机中导致一些问题,假如一个二进制计算机没有记录循环小数的循环节的能力,那么其在接收到十进制的0.1后,由于无法处理二进制循环小数0.0001100110011…..会截断前面几位,这就是数学意义的精度丢失

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部