《C语言程序设计:问题与求解方法》——3.10节提高部分

简介:

本节书摘来自华章社区《C语言程序设计:问题与求解方法》一书中的第3章,第3.10节提高部分,作者:何 勤,更多章节内容可以访问云栖社区“华章社区”公众号查看

3.10 提高部分
在计算机的内部所有数和码都是二进制的,我们必须对其有所了解,在编程中遇到困难或障碍时才不至于束手无策。
计算机内部的二进制数的表示多数与手写表示的二进制数有所不同。

3.10.1 机内形式的整数
(1)无符号整数
无符号整数即非负整数,与手写表示法相同。
在计算机中,无符号整数可用1个、2个、4个或8个字节来存储和传输。
1个字节的位串能够表示的数值范围是0~255(即11111111=28–1)。
2个字节的位串能够表示的数值范围是0~65535(即216–1)。
4个字节的位串能够表示的数值范围是0~4294967295(即232–1)。
但读者要注意:在编程时,尽量统一采用有符号数(变量与常量),在可以不使用无符号数的情况下,尽量不要用无符号数,以避免类型转换带来的很多麻烦。
(2)有符号整数
有符号整数又称为“真值”。因为真值有正负号,所以在计算机中无法直接用位串来表示真值,要采用某种编码来间接表示有符号整数。
在某个取值范围中的所有有符号整数构成了一个(有限个元素的)集合。因此,当然可以用一定长度的二进制位串来对其进行编码 。用二进制位串通过编码来表示有符号整数时,有多种编码规则,最常用的有原码、反码和补码。
原码表示法
“原码表示法” 编码规则规定:用位串最左边的一位(通常称为最高位)表示该数的符号位:0表示正数,1表示负数,其余各位表示该数的绝对值。下表是一些用1个字节表示的典型数值的8位原码。


8bf03e5e4b8fa9dd02bbc5133c79d9fadf6ba7e5

可见,将原码表示的二进制有符号整数转换成十进制整数很简单,只要将最高位转变成正、负号,将剩下的其余各位用前面学过的方法转变成十进制整数即可。比如,10011011就是十进制数–27。 值得注意的是,原码表示法中有两个0值:正0和负0。
补码表示法
但是,在计算机的设计制造中,人们却偏爱采用补码(编码规则)来间接表示有符号整数。
对于在–128~127之间的真值,人们可以做两个数的加法,只要对两个绝对值一样而符号相反的数做加法,结果就是0。我们如何设计位串长度为8位的“补码”来做类似的事呢?
位串长度为8位的补码是这么设计的,正数的补码值就是所对应的无符号整数;而一个负数的补码值是这样得出来的:要求该补码值在与其绝对值一样大的无符号整数做加法时,结果应当是100000000(注意:一共有8个0)。
也就是说,只要将向更高位的进位直接舍弃,补码也遵守同样的准则:两个绝对值一样而符号相反的补码做加法,其结果是0。
由此,我们很容易得到求一个真值为负数的(位串长度为8位的)补码的方法:先求出它所对应的二进制正数a,然后将其每一位都取反得到另一个二进制数b(即将a中的0变成1,1变成0)。这样的两个二进制数相加(a+b),必然等于11111111(8个1),最后将b再加上1,这样就得到了一个负数的补码,此数与其等值的正数的补码相加,一定会等于100000000。
举例来说,求真值–25的补码,先求25的补码得到00011001,然后每一位都取反得到11100110,然后再加1得到11100111,这就是真值–25的补码。
由于位串长度为8位的补码表太长,我们来研究位串长度为4位的补码,见下表。


e1bb6c9a41cc515b3eceb6a7c3ea076aab5a07bd

注意:从上表可看出,正数补码的最高位都是0,而负数补码的最高位都是1。
取表中有代表性的几个数据进行相加,看看它们的真值运算和补码运算有何对应关系。
1)大正数加小负数。
用真值运算:6+(–3)=3
用补码进行运算: 0110+1101=10011,舍弃4位外的最高位,得到用补码运算的结果0011,这恰好也是二进制形式表示的3。
2)小正数加大负数。
用真值运算:2+(–7)=–5
用补码进行运算:0010+1001=1011,得到用补码运算的结果是1011,查表可看到,补码1011所对应的真值恰好也是–5。
3)负数加负数。
用真值运算:(–1)+(–7)=–8
用补码进行运算:1111+1001=11000 ,舍弃4位以外的最高位,得到用补码运算的结果是1000,查表可看到,补码1000所对应的真值恰好也是–8。
延伸与拓展: 如何从补码的运算结果中求得十进制的真值?
如果最高位为0,只要将此正整数转化十进制整数即可;如果补码结果的最高位是1,说明该结果是一个负数,那么与该负数的补码所对应的二进制的绝对值该如何求得呢?答案很简单:对运算结果每一位取反后再加1(这还是因为:若将该负数的补码与其绝对值一样大的正数做加法,其结果仍然应当是100000000)。
结论:对负数的补码再次求补(即将每一位取反后再加1),得到的是该补码所表示的数的(二进制形式的)绝对值。
例如,假设补码运算的结果是1011,如何求得它所对应的绝对值的大小呢?因为它的最高位为1,表明结果是负数。对其再次求补码(每一位取反后再加1),即可得到该补码所表示的数的绝对值为(0101)2=5。所以,补码运算结果用十进制真值来表示就是–5。
补码运算中的溢出
在一定长度的补码系统中,两正数的补码(最高位同为0)之和或者两负数的补码(最高位同为1)之和可能会超出该补码系统所能够表示的最大、最小数。如何判断运算结果是否溢出呢?只要看运算结果的最高位是否变了号(这是充分必要条件)。例如,真值运算(–7)+(–5)=–12,但是经查表得到用4位补码进行的运算结果却是:1001+1011=(1)0100,等于4,符号位从1变成了0 ! 这就产生了溢出。
符号位的扩展
一个补码表示的有符号数在进行扩展时,直接用它的符号位复制到高位字节中(即提升到字节数更多的有符号整型时),这称为“符号位扩展”。
计算机硬件为何要用补码进行算术运算
为何计算机要如此麻烦地使用补码来进行算术运算,而不使用人们更容易懂得的原码来进行运算?
简单地说,这是由于用原码运算会出现麻烦—符号位在某些情况下不能直接参与运算,比如(10000001)原+(00000001)原=(10000010)原=(–2)10,这明显不对。而且原码还出现了两个0(正0:00000000和负0:10000000)的问题。
而采用补码来进行运算,以上这些问题都迎刃而解!符号位与数字位可以一并参与算术运算,如果有向更高位的进位(即在最高符号位上产生了向高位的进位),只要直接把它舍弃即可。采用补码后,所有减法都可用加法代替,节省了制造算术逻辑单元的成本(不必制造实现减法的电路,在算术逻辑单元中只要有“全加器”电路即可)。
(3)计算机中使用补码的流程
计算机中使用补码的过程如下:
编程(或在程序运行时输入)的有符号整数(即十进制的真值)

数值被编译程序(或由输入库函数)转换成补码

硬件(ALU)用该数补码进行算术运算(代替用真值进行运算)

               ↓
       得到用补码表示的运算结果
               ↓

由输出库函数将结果转换成十进制的真值输出

操作者最终看到十进制的结果
由此可见,对于一般的编程者来说,只要程序运行不会出现溢出问题,不需修改程序将补码进行符号位的扩展,不需要使用补码表示的数据拆开来进行位运算,在编写源程序中,通常不必考虑有符号整数在机内是用补码来表示的这个底层问题。
(4)类型提升时的符号位扩展
补码表示的有符号整数在进行类型提升时,即由短位串变为长位串时(比如,由int型提升为long int型),只要将符号位进行扩展即可。
比如,一个字节的补码表示的有符号数01101000(正数)和11010110(负数),提升为两个字节的补码表示的有符号数分别就是:0000000001101000 (正数)和1111111111010110(负数),低8位没有发生任何变化,只需将符号位向高位扩展即可:符号位是0,扩充的高位字节就全部填充0,符号位是1,扩充的高位字节就全部填充1。只有这样,才能确保在类型提升后的补码所对应的真值与提升前的原来真值一样大。
然而,一个无符号的整数在类型提升时采用的规则,却是将扩充的高位字节全部填充0。
提示:正是由于无符号整数类型提升时(高位都填充0)与用补码表示的有符号数的类型提升有着根本不同,所以,本书不提倡将无符号数和有符号数混用在同一个表达式中,以免类型转换时出现麻烦。万一不得已必须放在一个表达式中,在类型转换时要特别小心,避免犯错误。

3.10.2 二进制浮点数在计算机中的表示方法
不通过某种编码,计算机中无法直接存储手写的小数形式的实数–110110.101或规范化的指数形式的实数–1.10110101×2101。小数点前面只有一位非0的整数,就是规范化的指数形式表示的实数;在二进制中,小数点左边的这个非零整数只能是1。
在计算机中,对实数的编码(比如以下所讲的余127码)是以对二进制的规范化的指数形式为基础来进行的。其编码方式是:省略掉规范化的指数形式中的一位整数部分“1”、小数点“.”、乘号“×”、基数2,只保留数的符号(用0表示正数、1表示负数)、尾数(即实数的小数部分)和指数部分。这就必须规定在位串中这三者的位置和各自所占的字节数。
比如,对于–1.10110101×2101这个数,只需存储符号位(负号)、尾数位10110101和指数位101。
此外,指数部分还要变换。因为在计算机中,IEEE(电气和电子工程师协会)745标准规定:用长度为8位的余127码(而不是用补码)来表示(可正可负的)指数部分。
在计算机中通常规定:从左到右采用“符号位(1位)+指数位(8位的‘移码’或称为余127码)+尾数位(23位)”的方式(一共占据4个字节)来表示和存储一个二进制的单精度实数(又称为单精度浮点数)。
这是由IEEE 745标准规定的一种二进制实型数表示法,精度是十进制的6位(见Behrouz Forouzan等著,刘艺等译《计算机科学导论》,机械工业出版社出版)。大多数计算机制造商都支持这种IEEE 745标准。
【例题3.4】求一个十进制数–41.75的余127码的实数表示法。
1)符号为负,所以最左边符号位S=1(如果是正数,符号位用0表示)。
2)十进制转换为二进制 41.75=(110011.11)2。其中,整数和小数部分分别转换。
3)规范化:(110011.11)2=(1.1001111×2201)(即移动小数点)。
4)指数位的余127码为 E=101+1111111=(10000100)2(即5+127=132)。
5)尾数位为 M=(1001111)2。
6)最终得到:

   1           10000100       10011110000000000000000
  ↑             ↑                                    ↑
符号位(S)  指数位(E 8位)  尾数位(M 23位)
延伸与拓展:
单精度浮点数的取值范围
由上可知,能够表示的最大正数用余127码表示是:
0   11111111   11111111111111111111111
=(211111111–1111111)×1.11111111111111111111111=2128×1.11111111111111111111111
=2105×111111111111111111111111≈3.4×1038
能够表示的最小正数用余127码表示是:
0 00000000 00000000000000000000001
=2–127×1.00000000000000000000001≈1.17×10–38

对于负数情况完全是类似的,绝对值最大的负数是–3.4×1038,绝对值最小的负数是–1.17×10–38。
单精度浮点数的精度
浮点数的精度取决于它的规范化表示法的尾数部分,与指数位无关。指数位仅决定小数点的位置。尾数部分的位数越多,能够表示的有效数字就越多,精度就越高。
在IEEE 745标准中,单精度浮点数的尾数用23位存储,加上默认的小数点前的1个整数位(该位的值是1)。用24位二进制能够表示的最大十进制正整数是224–1=16777215,所以单精度的浮点型数的精度是十进制的7位。也就是说,只有十进制数值的高7位是准确无误的(严格来说,如果一个十进制数的高8位小于等于16777215,那么这个数的高8位都是准确无误的,否则精度只有7位)。
实数值0.0规定用余127码的全0来表示
用以上转换方法,规范化指数表示法的0无法用余127码来表示,因为它没有一位非零的整数部分。因此,IEEE 745标准特别规定:用余127码的全0来表示数值0,即0 00000000 000000000000000000000。

相关文章
|
1月前
|
C语言
C语言模块化程序设计
C语言模块化程序设计
21 0
|
1月前
|
C语言
【C语言】循环结构程序设计(第二部分 -- 习题讲解)
【C语言】循环结构程序设计(第二部分 -- 习题讲解)
|
1月前
|
C语言
你知道C语言中实现有序序列并序输出的2种方法吗?
你知道C语言中实现有序序列并序输出的2种方法吗?
|
1月前
|
C语言
【C语言】大小写字母的相互转化:多种方法解析及原理说明
【C语言】大小写字母的相互转化:多种方法解析及原理说明
109 0
|
4月前
|
存储 程序员 C语言
【C语言程序设计】数组程序设计
【C语言程序设计】数组程序设计
65 0
C4.
|
1月前
|
程序员 C语言
C语言循环结构与程序设计
C语言循环结构与程序设计
C4.
23 0
|
27天前
|
编译器 C语言
【C语言】字母转换大小写的三种方法
【C语言】字母转换大小写的三种方法
44 0
|
1月前
|
存储 文件存储 C语言
《C语言程序设计》课程设计 -- 火车票票务管理系统
《C语言程序设计》课程设计 -- 火车票票务管理系统
23 1
|
1月前
|
存储 C语言
C语言顺序结构程序设计
C语言顺序结构程序设计
21 0
|
1月前
|
存储 C语言
C语言的顺序程序设计
C语言的顺序程序设计
11 2