(二)信息的表示和处理

目录

信息存储 

信息的存储

寻址和字节顺序

进制间的转换

整数的表示

无符号数的编码

有符号-补码编码

反码和原码

有符号和无符号数之间的转换

C语言中的有符号数和无符号数以及扩展和截断数字

整数的运算

无符号数加法运算

补码加法运算

无符号数乘法运算

补码乘法

乘法优化

除法运算

浮点数

二进制小数

IEEE 浮点表示

舍入

浮点运算


信息存储 

信息的存储

访问计算机最小的单位是八个位构成的字节,而不是值0或值1的单个位

程序会将存储器视为一个非常大的字节数组,称为虚拟存储器(virtual memory)。存储器的每一个字节都由唯一的数字来标识,也就是我们说的地址(address),所有可能地址的集合称为虚拟地址空间(virtual address space)

计算机进行数据处理时,一次存取、加工和传送的数据长度称为字(word),

一个字通常由一个或多个(一般是字节的整数位)字节构成,

字的位数叫做字长(word size) 

  • 由于虚拟地址空间中的地址就是使用一个字来编码的,因此字长决定了系统的虚拟地址空间的最大大小。
  • 字长是CPU的主要技术指标之一,指的是CPU一次能并行处理的二进制位数,字长总是8的整数倍,通常PC机的字长为16位(早期),32位,64位。

寻址和字节顺序

对于跨越多个字节的程序对象来说,我们需要制定两个规则:

  •               ①、这个对象的地址是什么?
  •     ②、在存储器中如何排列这些字节?

1:在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。

2:采用如下两种方式:

                   x 的低位字节值到高位字节值分别为 67,45,23,01。用大端法和小端法表示:             

                    

  • 小端法:按照从最低有效字节到最高有效字节的顺序存储对象,                也就是最低有效字节在最前面
  • 大端法:按照从最高有效字节到最低有效字节的顺序存储对象,                也就是最高有效字节在最前面。

 

进制间的转换

①. 其他进制转十进制:将二进制数、十六进制数的各位数字分别乘以各自基数的(N-1)次方,其相加之和便是相应的十进制数,这是按权相加法

②. 十进制转其他进制:整数部分用除基取余法,小数部分用乘基取整法,然后将整数与小数部分拼接成一个数作为转换的最后结果。

③. 二进制转十六进制:从小数点位置开始,整数部分向左,小数部分向右,每四位二进制为一组用一位十六进制的数字来表示,不足四位的用0补足。

④. 十六进制转二进制:每一位十六进制对应每四位二进制,不足用0补足。 

整数的表示

计算机在解释一个数据类型的值时主要有四个因素:

                       位排列规则(大端或者小端)、起始位置、数据类型的字节数、数据类型的解释方式

 

无符号数的编码

定义:假设对于一个w位的无符号整数,用二进制比特位可以表示为[xw-1 , xw-2 , … , x2 , x1 , x0]。那么我们可以用一个函数表示如下:

                                                   

            

无符号的二进制,对于任意一个w位的二进制序列,都存在唯一一个整数介于0 到 2w-1之间,与这个二进制序列对应。

反过来,在0 到 2w-1之间的每一个整数,存在唯一的二进制序列与其对应。 

有符号-补码编码

补码的定义如下:

                                    

其中最高有效位 xw-1 也称为符号位,符号位为 1 时表示负数,当设置为 0 时,表示非负数。下面我们看几个例子:

          

对于任意一个w位的二进制序列,都存在唯一一个介于-2w-1 到 2w-1-1的整数,与这个二进制序列对应。反过来,对于任意介于-2w-1 到 2w-1-1的整数,存在唯一的长度为w二进制序列与其对应。

反码和原码

反码定义:除了最高有效位的权是-2w-1-1,而不是-2w-1其余的和补码表示方式一样

                                  

原码定义:最高有效位是符号位,用来确定剩下的位是正还是负

                                  

  •             原码:一个整数,按照绝对值大小转换为二进制数,最高位为符号位。
  •       反码:将原码除最高位(符号位)外,其余各位按位取反,所得到的二进制码。正数的反码为原码。
  •       补码:反码最低位加1即为补码。

对于正整数,原码、反码、补码完全一样,即符号位固定为0,数值位相同。

对于负整数,原码和补码互相转换的简便方法:

  • 从数的右边往左开始数,遇到“0”不理它,直到遇到第一个“1”为止,以后的每一位数取反即是它的原码或补码,符号位不变,还是“1”(补码的补码是原码)。

比如:11010100 ----- 从右往左数,第一位是0,不理它,第二位还是0不理它,第三位是1,那么从此以后的每位取反,即为它的补码了.答案为:10101100

有符号和无符号数之间的转换

有符号数强转为无符号数

下图为表示补码编码与无符号编码的对应关系,可以看出在0至2w-1-1之间,两者是相等的,而其余区间则不同。

                                                        

从上图我们也可以得出:当将一个有符号数映射为它相应的无符号数时,负数就被转换成了大的正数;而非负数会保持不变。

 

无符号数转换为有符号数

同样的,在0至2w-1-1之间,两者依然是相等的,而其余区间则不同。

                                                        

C语言中的有符号数和无符号数以及扩展和截断数字

注意:在 C 语言中,当执行一个运算,会隐式的将有符号参数强转为无符号参数

 

扩展一个数字的位表示

①、零扩展

  •     将一个无符号数转换为一个更大的数据类型,我们只需要简单的在二进制序列前面添加 0 即可。

②、符号位扩展

  •     将一个补码数字转换为一个更大的数据类型,我们需要在开头添加符号位。

由上面两条我们可以总结:

  •               如果我们原始位为[xw-1 , xw-2 , … , x2 , x1 , x0],那么扩展后就可以表示为:[xw-1 ,xw-1 ,...,xw-1 , xw-2 , … , x2 , x1 , x0]。

 

截断数字

这和上面的扩展刚好相反。即我们不需要额外的扩展一个数的位,而是减少一个数字的位数。

  •             将一个 w 位的数 [xw-1 , xw-2 , … , x2 , x1 , x0] 截断为一个 k 位数字时,我们会丢弃高 w-k 位。得到 [xk-1 , xk-2 , … , x2 , x1 , x0]

而对于有符号(补码编码)的截断,我们只需要多加一步,将无符号编码转换为补码编码就可以了。

整数的运算

 无符号数加法运算

                                     

如果两个无符号整数作加法运算。当 x+y < 2w 时,它们的结果不变;当 2w <= x+y < 2w+1,它们的结果为 x+y-2w

补码加法运算

范围在 -2w-1 <= x,y <= 2w-1-1 做加法运算时

                             

补码加法运算就是先按照无符号加法进行运算,而后在进行无符号和有符号的转换。

#include <stdio.h>
 
int main()
{
 short int i = -32768;
 short int j = i-1;
 printf("%d\n",j);
 return 0;
} 

                                                   

为什么 -32768-1 结果会是 32767?

                                                   

我们需要先将 -32768 和 -1 分别转换成无符号数进行加法运算,然后对得到的结果转换成有符号数。

  ①、-32768 转换成无符号数也就是 -32768+2^16=32768  

  ②、-1 转换成无符号数也就是-1+2^16=65535 

  ③、将上面两步的结果相加,然后转换成有符号数:

    即(65535+32768)-2^16=65535+32768-65536=32767

这个过程用到的公式分别有

                                                    

无符号数乘法运算

                                                       

#include <stdio.h>
 
int main()
{
 unsigned short int i = 2;
 unsigned short int j = i*2;
 printf("%u\n",j);
 return 0;
} 

上面的程序结果是:(2*2)mod 2^16=4 mod 65536=4

补码乘法

补码乘法运算公式为

                                    

 

乘法优化

编译器使用了一项重要的优化,使用移位和加法的组合来代替乘法。

结论:对于一个w位的二进制数来说,它与2k的乘积,等同于这个二进制数左移k位,在低位补k个0。

比如:

    计算 x*14 的乘积。

  •                由于 14 = 23+22+21 ,那么编译器会将乘法重写为(x<<3)+(x<<2)+(x<<1)。这样就将乘法替换为三个移位和两个加法。无论 x 是无符号还是补码,甚至当乘法会导致溢出时,两个计算都会得到一样的结果。
  •     更好的编译器,可能会将 14 = 24-21。那么就会变成(x<<4)-(x<<1),只需要两个移位和一个减法。

除法运算

结论:对于除以 2 的幂可以用移位来运算。无符号除法使用逻辑移位,补码除法使用算术移位。

浮点数

二进制小数

进位计数制的要素:

  • ①、数码:用来表示进制数的元素。比如二进制数的数码为:0,1。十进制数的数码为:0,1,2,3,4,5,6,7,8,9。
  • ②、基数:数码的个数。比如二进制数的基数为2。十进制数的基数为10。十六进制数的基数为 16.
  • ③、位权:数制中每一固定位置对应的单位值称为位权。例如十进制第2位的位权为10^1即10,

我们可以说:每个数码所表示的数值=该数码值 * 所处位置的位权。

比如

  • 十进制数:(123.45)10      =1×10^2+2×10^1+3×10^0+4×10^-1+5×10^-2
  • 十六进制数:(BAD)16      =11× 16^2+10×16^1+13×16^0=(2989)10
  • 二进制小数(10010.1)2 = 1 * 2^4 + 0 * 2^3 + 0 * 2^2 + 1 * 2^1 + 0 * 20 + 1 * 2^-1 

对于一个形式为bm....b0.b-1....b-n的二进制小数b来说,二进制表示公式

                                                                    

从上面的二进制公式我们可以看出,

        小数点向左移动一位,则相当于 (∑ 2i * bi)/2。因为每一位的位权都*2-1;反过来,小数点向右移动一位,则相当于该数乘以2。

注意:

         二进制小数不像整数一样,只要位数足够,它就可以表示所有整数。比如十进制小数0.2,我们并不能将其准确的表示为一个二进制数,只能增加二进制长度提高表示的精度。

IEEE 浮点表示

IEEE 浮点标准表示: V = (-1)^s * M * 2^E 。

  •        ①、s 是符号位,为0时表示正,为1时表示负。
  •   ②、M为尾数,是一个二进制小数,它的范围是0至1-ε,或者1至2-ε(ε的值一般是2-k次方,其中设k > 0)
  •   ③、E为阶码,可正可负,作用是给尾数加权

我们将浮点数的位划分为三个阶段,分别对这些值进行编码。

  •      一、一个单独的符号位 s 直接编码符号 s
  •   二、k 位的阶码字段 exp =ek-1ek-2...e1e0 编码阶码E
  •   三、n 位小数字段 frac = fn-1fn-2...f1f0      编码尾数 M,但是编码出来的值也依赖于阶码字段的值是否等于0.

一般来说,现在的编译器都支持两种浮点格式,一种是单精度,一种是双精度。

  •   其中float是单精度的,采用32位二进制表示,其中1位符号位,8位阶码以及23位尾数。
  •   double是双精度的,采用64位二进制表示,   其中1位符号位,11位阶码以及52位尾数。如下图表示

                                 

如果给定了位 s 的表示,根据 exp 的值,被编码的值可以分为三种不同的情况(最后一种情况有两个变种)。下图是单精度的情况:

                         

 

规格化

阶码E 的位模式exp既不全为0(数值0),也不全为1

 

非规格化的值

当阶码域为全 0 的时候,所表示的数就是非规格化形式。

特殊值

特殊值是指阶码全为 1 的时候出现的

  •    在阶码全为1时,如果尾数位全为0,则表示无穷大。符号位为0则表示正无穷大,相反则表示负无穷大。
  •    倘若尾数位不全为0时,此时则表示NaN,表示不是一个数字。
  •    一些运算的结果不能是实数或者无穷,就会返回NaN值,比如正无穷减正无穷,-1的根号值。 在某些应用中表示未初始化的值,也很有用处。

舍入

舍入一共有四种方式,分别是向偶数舍入、向零舍入、向上舍入以及向下舍入

                          

浮点运算

在IEEE标准中的运算规则,就是我们将把两个浮点数运算后的精确结果的舍入值,作为我们最终的运算结果。

                

我们看到 f1 的值是0,f2的值才是3.14。为什么呢?

  •        这是因为前面3.14f+10000000000f 时,会将 3.14 这个有效数值舍入掉,而导致最终结果为0.0
  •   f2 由于括号的存在,会先进行括号里面的运算,结果是0,然后在与3.14相加
  1. 也就是浮点运算不满足加法的结合律 a + b + c != a + (b + c)。
  2. 同时乘法结合律也不满足:a * b * c != a * (b * c);还要分配律也不满足: a * (b + c) != a * b + a * c

 

浮点数失去了很多运算方面的特性,因此也导致很多优化手段无法进行,比如我们试图优化下面这样一段程序。

/*   优化前       */
 float x = a + b + c;
 float y = b + c + d;
 /* 编译器试图省去一个浮点加法      */
 float t = b + c;
 float x = a + t;
 float y = t + d; 

上面优化前是进行了四次浮点运算,而编译器优化后只需要进行三次浮点运算。

  • 但是这中间的 x 可能回产生与原始值不同的值,因为它使用了加法运算不同的结合方式。所以现在的编译器都倾向于保守的方式,避免任何对功能产生的优化,即使是很轻微的影响。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值