5.7. 进制.1
我女儿读幼儿园时,我就开始关心起她的算术能力。于是我了解到,她在小班的时候,就会个位数的加法,但一晃两年,到大班要结束了,才见她拿着算盘在那里炫她的百位数以内加减。
我 提到了“个位”、“百位”,您懂吧——不懂?不会吧,我的书居然也有幼儿园的小朋友读者了。没错,平常我们用惯的数,是逢十进一,称为“十进制”数。十进 制数的最低位,称为“个位”,而后是“十位”,“百位”,“千位”。不过,今天我们学习的“二进制”数,它的最低位,称为“1位”,而后是“2位”,“8 位”,“16位”,“32位”,“64位”……
一个1,对于十进制数,在百位上,它代表100,在十位上,代表10,在个位上,才代表1。所以,一个十进数的123,它之所以是“一百二十三”,是因为……请看大屏幕:
123 = 1×100 + 2×10 + 3×1。
〖轻松一刻〗:二进制其实很简单
我知道性急的读者读到此处,已经直接打电话到出版社咆哮了:“我要的是一本计算机编程教材,而不是一本幼儿算术启蒙读物!”
有关“十进制”的知识,确实很简单,比“十进制”还要简单5倍的,是“二进制”。 可是,真的有很多程序员,一提到“二进制”就晕乎,所以大家就耐心一点,我们的知识,马上就会上升到初中代数的水平了。
另外,如果我们把一个数,从右到左,依次称为“第0位”、“第1位”、“第2位”……的话,那么,“十进制”各位的权值,以“10”为基数,以位为指数。请再次看大屏幕:
123 = 1×(10的2次方) + 2×(10的1次方) + 3×(10的0次方)
其中,10的0次方为1,因为初一代数告诉我们:“除0以外,任何数的0次方为1”。
这是十进制,那么,二进制呢?
5.7.1. 正整数和零
“二进制”各位的权值,则以“2”为基础。这样,二进制数123——且慢,二进制数中,是不可能出现2,3的,因为它逢2就进1了,所以只有0~1,这和十进制数中只有0~9是一个道理。那我们以二进制数“110”为例。
再啰嗦一下,既然是二进制数,那就不能把110读成“一百一十”。为了避免混淆,我们用110(2)明确表示一个二进制数。
110(2)= 1×(2的2次方) + 1×(2的1次方) + 0×(2的0次方)
= 1×4 + 1×2 + 0×1
= 4 + 2 + 0
= 6
原来,二进制的110,不过是十进制的6。如果我女儿读幼儿园时学的是二进制数的话,那么她读小班时,就可以掌握3位数的加减了。
〖课堂作业〗:练习二进制数换算成十进制数
1、写以下二进制数的十进制值: 0000、0001、0010、0110、0011、1000、1010、0111、1111、1001。
2、请将以上二进制数从小到大排列,然后补充出同样处于0000~1111范围的其它二进制整数,并将新增的二进制数换算成十进制。
看 来,二进制挺“耗位置”的。一个小小的十进制数6,用二进制表达,就得用到3位数。之前我们在说机型,提过16位、32位、64位,没错,这里的“位 /bit”,指的是二进制数。当前大多数人用的PC机是32位机,在32位机上,一个最大的二进制整数是多大呢?拍拍脑袋就知道了:
11111111 11111111 11111111 11111111
这 就是最大的32位无符号正整数,换算成十进制是多大呢?答:4294967295,读作:“四十二亿九千四百九十六万又七千两百九十五”。如果问的是: 32位的二进制,最多可以表达几个整数?则回答:可以表达4294967296个(例如:0~4294967295)。在这么多数中,二进制和十进制来表 达一致的,只有0和1。
至此,我们在说的整数,都是0和正整数,负整数如何用二进制表达呢?比如-1?
5.7.2. 负整数(原码、反码、补码)
如果让你来设计,你认为在计算机中,二进制的负整数该如何表达呢?有丁小明同学站起来回答:
“前面加个负号就行了呗!比如,十进制的-1,还是-1(2),十进制的6,就是-110(2)……”
〖轻松一刻〗:丁小明同学罚洗校厕三天
早就说过,在计算机中,一切都是用数字表达。虽然电脑键盘是有负号正号,电脑显示屏上也能打出负号正号,但这都是给人看的,一切符号对于机器来说,最终都需要用二进制数表达。倘使“负的二进制数”需要用“负号”表达,但“负号”本身又需要用二进制表达……
没辙,既然计算机中一切都是0和1,那么负号肯定也得用0或1来表达了。所以,可能除丁小明同学以外,大家最容易想到的方法,就是0表示正号,1表示负号。对于一个32位的数来说:
+1还是:00000000 00000000 00000000 00000001
-1则是:10000000 00000000 00000000 00000001
咦?真的很直观噢,并且感觉很有创意啊……哈哈哈哈!然而,数学家们对我们这个创意很不满意。他们说,“+1加-1 应该等于0啊。你且把那两个数加加!” 得,居然是“-2”。
我 们把目光转向计算机学家,更惨,计算机学家基本也是一位数学家,所以他们有双重的不满意:“0怎么表达啊?我们制造计算机,第一要求就是准确无二义!你搞 这样一个正负表达,请问,0是00000000 00000000 00000000 00000000好呢?还是10000000 00000000 00000000 00000000好?还是两者都可以?”
的确噢,(+1) + (-1) 应等于0,并且+0应该等于-0……这要求不算高啊,怎么办呢?当初我也曾为此抓耳挠腮,终于得出结论:干嘛呢?!我只是要来打个酱油,你凭什么让我回答这个问题!为什么不直接告诉我先哲们是如何处理的?
先哲出场了!他说:“年轻人,用二进制数中最右边的一位0表示正号,最右边的一位1表示负号,其实是完全正确的思路!只不过,如果是负数的话,我们需要用补码来表达就一切OK了……”先哲的声音越来越远,越来越远。
“等等啊,什么叫补码啊?”
“至于什么叫补码,就让《白话 C++》来告诉你吧……”
计算机中,负数是用补码表示的。什么叫补码?让我且从“原码”说起……
前面一直在说的正整数和0的表示法,就叫做原码。例如,以32位的0和1为例:
0:00000000 00000000 00000000 00000000
1:00000000 00000000 00000000 00000001
我们仅用原码来表达正整数和0,而不用来表达负整数,因为,假设用原码表示-0和-1的话:
-0:10000000 00000000 00000000 00000000
-1:10000000 00000000 00000000 00000001
这就是我们刚才失败的创意。虽然失败,但却是后面变换过程的开始。
将原码中的0变成1,1变成0,就叫做反码。因为我们已经肯定要用“原码”来表达0和正整数了,所以让我们看看反码如何表达-0和-1。
先看原码:
-0的原码:10000000 00000000 00000000 00000000
-1的原码:10000000 00000000 00000000 00000001
然后分别取反:
-0的反码:11111111 11111111 11111111 11111111
-1的反码:11111111 11111111 11111111 11111110
反码的“-0”和“+0”长得更不像了(完全相反嘛)!再试一下(+1) + (-1),我们排一下竖式:
00000000 00000000 00000000 00000001
+ 11111111 11111111 11111111 11111110
---------------------------------------
11111111 11111111 11111111 11111111
结果成了那个正大的无符号整数:4294967295。看来,反码只是一个中间状态,我们既不用它表达正整数或0,也不用它表达负整数。
将反码加1得到的数,就叫做补码。
这么简单?不过,也得注意一点小事情:溢出。
〖轻松一刻〗:世界上最大数是多少?
小 学一二年级时,最爱和同学抬杠的事之一,就是比谁的数大了。甲说自家养了5只鹅,乙就说他家有6只。甲一生气:10只!乙更夸张100只(他家的鹅繁殖得 可真快)!后面就更了不得了,千只,万只,十万只,最后有一方嘴累了,就来一句:“反正无论你有多少只,我就比你多1只。”
一方垂死挣扎:“我家鹅的只数,是世界上最大的数!”
另一方:“那我家的是世界上最大的数再加1那个数。”
如 果这世界是上真有一个最大数,那么,这个最大数再加1得到的数是什么呢?这听着怎么也不像是个算术问题,倒像是个哲学问题啊。还好,在计算机的世界里,我 们可以回答这个问题。32位机里,最大的数,不就是“四十二亿九千四百九十六万又七千两百九十五”,那么,这个数加上1,是什么呢?
11111111 11111111 11111111 11111111
+ 00000000 00000000 00000000 00000001
-----------------------------------------
00000000 00000000 00000000 00000000
哈 哈哈哈,重大发现啊,原来计算机世界里,最大数加上1以后,居然得到0。咦,不是一直进位,进到最高位那个1上哪去了——你是说第33位吗?专业一点! 32位机,哪来的33位。那1位数,就这样“机间消失”了,对了,这也有一个专业名词,叫“溢出/overflow”。
言归正传。既然,将反码加1得到的数,就叫做补码。我们来看看补码如何表达-0和-1。
先看反码:
-0的反码:11111111 11111111 11111111 11111111
-1的反码:11111111 11111111 11111111 11111110
然后分别取补:
-0的补码:00000000 00000000 00000000 00000000
-1的补码:11111111 11111111 11111111 11111111
用补码表示-0,则它正好就是用原码表示的+0。数学家们提的第一个要求满足了!
再来算一下(1) + (-1) :
00000000 00000000 00000000 00000001
+ 11111111 11111111 11111111 11111111
----------------------------------------
00000000 00000000 00000000 00000000
溢出再一次发生,而结果却正是我们想要的:正1加上负1,等于0了!
〖重要〗: 小结:正、负整数、及0的二进制表示
小结一下:二进制数中,最高位可以被用来做符号位,0表示正号,1表示负号。但负数还需要由此换算成补码表示。至于0,用原码表示或用补码表示,都是一致的。
5.7.3. 无符号数vs.有符号数
前面提到一件事,说32位的最大整数,是32位上全是1的二进制数:
11111111 11111111 11111111 11111111
但后来我们又说,最高位的1可以用来表示是一个负数。这样,这个最大的数,一下子变成是“-1”(补码形式)了。
显然,如果最高位拿来当符号位的话,32位整数的最大数,应该是:
01111111 11111111 11111111 11111111
换 算成十进制数,是:2147483647,读作:“二十一亿四千七四八万又三千六百四十七”。哇,一下子缩水不少啊。不过,如果问的是: 32位的二进制,最多可以表达几个整数?则还是回答:可以表达4294967296个(例如:-2147483648~2147483647)。
〖小提示〗:16位整数的表达范围
在上个世纪我学习C++时,还时个人电脑还多数是16位的,所以一个整数的表达范围,就是区区的:-32768~32767;真是一个令人捉襟见肘的可用范围。
考虑到现实生活中,有许多数量是不需要“负数”的,比如“天上星星有几颗?”,或者“房地产商的利润是多少?”,所以C++语言决定区分“有符号”和“无符号”两种数量类型。
比如,我们一直在用的“int”,它是有符号的,即区分正负的32位数。而“unsigned int”,就是无符号的,只能表达0和正整数。常见的有: