深入理解计算机系统-第2章信息的表示和处理

信息的表示和处理
在信息存储中,模拟信号可以发挥作用,但是由于会存在很大的干扰,于是引进了数字信号,采用二进制表示法,0/1对应不同的电压范围,就能够很简单并且准确的表示数值
单个的0/1或者1bit并不能表示一个完整复杂的信息,通过对0/1的逻辑组合在加以某种解释,就能够表示足够复杂的信息了,因此计算机中的数字表示都是bit位,0/1
最基本的表示单元有了,那么在现实世界运算的数字如何去在计算机中表示并运算呢?采用二进制方法,在现实世界中有整数,浮点数,在计算机中这些都是通过相应的二进制编码来表示的。
无符号的编码基于传统的二进制表示法,能够表示>=0的整数值;补码编码表示的是有符号的整数;浮点数采用的是编码方式是以2为基数的科学计数法
真实世界中的数字可以无限大,但是计算机是一个物理硬件,只能够表示有限大的数字,这是因为计算机中的bit位是有限的,组合出来的数字总是有一个上限值,如果需要表示的数字超出了这范围就会发生数值溢出(overflow),得到无法预知的数值
接着就需要对数值进行数学运算,在真实世界中的数学运算规则在计算机中不是都适用。计算机中的整数运算基本满足,而浮点数就有些不能满足,根本原因在于它本身在计算机中被表示的时候采用的就是近似值,整数的表示是精确的
为什么需要了解这些数值在计算机中表示背后的原理以及相关的运算规律,这是因为作为程序员在编写程序的时候,需要保证数值计算在不同的机器平台、操作系统、编译器下都能够正确的运行,并且能够发现计算的漏洞以防止黑客攻击,提前做好安全保护

计算机上的字长,即32位,64位表示的是虚拟地址空间的大小,采用多少个bit表示地址,不是硬件决定的,因此同一台电脑可以安装32/64的系统。同时程序在编译时,可以选择32位/64位编译,这样在32位的机器上可以编译出64位的程序,但是64位的程序只能跑在支持64位的机器上。
不同的编译器对同一个数据类型有可能会产生不同字节长度的解释,因此为了能够使编译运行的程序能够在不同的机器上移植,ISO C99引入了固定字节长度的数据类型,int32_t和int64_t,通过使用确定大小的整数类型可以准确的控制数据表示。对于程序员就应该保证在不同的机器上和编译器上程序都能够正确的运行。

字节顺序
大端法和小端法
大端法是最高有效字节在最前面,小端法是最低有效字节在最前面,而这两种方式在技术上没有太多理由应该选择哪一种,机器都能将他们解释为被表示的数据。
注意点:大部分时候不需要关心字节顺序。现在的网络数据传输使用的大端,而手机处理器,电脑使用的小端表示法,因此在进行网络传输时就需要先遵循小关的顺序规划,以确保在小端机器上接受到的数据能够被正确的解释。另一个是反汇编器生成的数据,是小端的,看的时候需要注意

字符串的结尾是null/0,字符串被编码后,在任何机器上表现都是一样的,与字节顺序和字大小无关。

不同的数据类型在不同的机器上字节长度会有所不同
比特位操作 &(位与) |(位或) ^(异位与) ~(位反)
&& || !逻辑操作符(0为FALSE,其他非0值为TRUE,返回0或1,可以提前终止)
有符号数,最高位表示为符号位,1 for -,0 for +
如果在一个表达式中包含了无符号数和有符号数,有符号数会被隐式的转换为无符号数
怎么实现绝对数值的?
example
int i=7;
int array[7];
for(;i-sizeof(array)>=0;i–)
{…}
例子中使用了sizeof,其返回值为无符号数,比较表达式左边将变量i隐式转为无符号数,于是左边成

为了无符号数运算,无符号数总会是大于0,循环变成了死循环。在C/C++中需要注意
符号扩展
目标:给定一个w bit的有符号整数x,将它转换为w+k bit位的相同的数值
规则:通过复制k个符号位,向左边扩展,因为扩展的位和原来的位将会发生效应(数值)上的抵消。所以数值和原来的一样

整数的表示
无符号数的编码,有符号数的(补码)编码。其中无符号数的编码所有的比特位都用来表示数据,最小值为0,最大值2w-1,补码编码表示的有符号数,最高位为符号位,权重是-2w。C语言没有规定采用补码作为有符号数的编码,但是基本上都采用补码表示有符号数。

强制类型转换
无符号数和有符号数之间的转换原理是底层的位是保持不变的,那么久有可能发生溢出。
无符号数对于系统程序员会很有用,比如地址就是无符号的数值,而无符号数对于普遍的应用程序带来的麻烦远比益处多,尽量避免使用无符号数,并且有些内置函数(如:sizeof()返回的就是无符号数,而它和其他有符号数运算时会发生隐式的强制类型转换,变为无符号数,可能导致程序出错),这些地方也要特别注意

扩展一个数字的位表示
无符号数是直接在前面添加0,有符号数是在最前面添加1,向左边扩展,因为扩展的位和原来的位将会发生效应(数值)上的抵消,所以数值和原来的一样

整数运算
计算机运算的有限性
C语言不会将溢出当做错误而发出信号
有符号数乘法和无符号数乘法发生溢出都是将多出的位丢掉,有符号数的最高位决定最终截断数值的正负,不关心原来的运算数值正负
补码加法是,如果数值太大,会发生负溢出,如果数值太小会发生正溢出
取相反数,直接将0,1翻转,在最低位加1,(-6)1010->0110()

1.怎么表示浮点数
2.舍入问题(Rounding)
3.加法、乘法和关系运算符的数学属性

思路:先将十进制浮点数表示为二进制浮点数,二进制浮点数再利用IEEE浮点数标准改写为32位/64位bit数
例如:一共长8位的数字表示,8个bit(这里方便,采用8位,而不是32/64位)
小数值为0.125(1/8),二进制小数可以表示为0.001,写成浮点格式(假设一个符号位,四个exp位,三个frac位)为00000001
最核心的思想是,一个二进制浮点数,包含三个部分,一为符号,二为M,三为指数,在32位bit中,就需要包含这些信息,IEEE浮点数标准就解决了这个问题,其中s占1位最高位,0表示正数和1表示负数,M为frac部分,表示为二进制并处理好后,直接写入0和1,指数E经过偏置编码(E=exp-Bias)可以得出一个编码值,写在exp部分,Bias为,k是Length(bit) of exp,这里解决了一个很关键的问题,指数是有正负的,而在浮点数的编码表示中只表示了数值的正负,通过偏置将指数负值映射到了正值,
其中的偏置值刚好为exp能表示最大值的一半,这样在机器中就能很好的表示指数的负值了。

注意点一:用十进制表示的浮点数不能在机器中全部被准确的表示,因此很多数都是近似值
注意点二:规格化的值表示的是1.xxxx,所有十进制的大于1的浮点数都可以通过移动小数点并且变化为1.xxxx的形式,这样抽象的描述了所有的浮点数,非规格化的值表示的是0.xxxx,这样表示了所有小于1的浮点数。
注意点三:对于浮点数的描述,有几种不同的类型,规格化的值(normalize value)、非规格化的值(denormalize value)、特殊值。规格化的值和非规格化的值的最大区别是在进行exp编码时不同,规格化的值exp部分不能全为0和1,非规格化的值为0,留下全1的部分用来处理一些特殊情况,其中frac全0表示无限大ifinite,frac为其他值表示不是一个数NAN
注意点四:非规格化的值exp编码,里面采用1可以保证从非规格化到规格化的平滑过渡,原因是当非规格化的frac值最大即全为1时,frac的值再加1则跳转到规格化的值

舍入
因为表示方法限制了浮点数的范围和精度,这是因为机器表示的位数有限,所以浮点运算只能近似的表示实数运算。对于一个期望的x值,通过某种方法可以找到最接近x值得匹配值,并用期望的浮点形式表示出来。

有四种不同的舍入方式
向零舍入、向下舍入、向上舍入、向偶数舍入(nearest even 2.5->2 3.5->4)

在计算机中大多采用向偶数舍入的方式,这种方式能够保证一组数据能够获得尽量靠近原始数据的平均值,因为有百分之50 的时间在向上舍入,百分之50的时间在向下舍入

向偶数舍入的方法是,当要控制的位置精度后面的数小于中值时,向下舍入,大于中值时,向上舍入,等于中值时,向最近的偶数舍入。举例:1.6854,精度为小数点后2位,1.69,1.6850,精度为小数点后2位,1.68,1.6849,精度为小数点后2位,1.68
需要注意的是,二进制中,偶数是0,奇数是1,举例:1001.110101 精度为小数点后2位,
1001.11;1001.111000 精度为小数点后2位,1010.00;1001.111001 精度为小数点后2位,1010.00

浮点运算
在数值运算中,重新结合或者改变运算的顺序,可能无法得到相同的答案
一个很大的数加上或减去一个相比之下很小的数,很小的数会被忽略(舍入原因)
浮点运算不满足结合律 3.14+(1e10-1e10)=3.14 (3.14+1e10)-1e10=0.0

C语言中的数据类型转换
需要注意的地方是,由于精度原因,高精度向低精度转换时,会发生截断,被舍入,甚至溢出

发布了1 篇原创文章 · 获赞 0 · 访问量 214

猜你喜欢

转载自blog.csdn.net/qq_41624560/article/details/104581026