CSAPP——数据在计算机内部的表示(整型与浮点型)
目录
一.数据类型
在冯偌依曼体系结构中,程序代码和数据都是以二进制存储的,对计算机系统和硬件本身而言,数据类型的概念并不存在,任何数据在计算机内部都是0/1序列。这里讲的数据类型是以C语言为基础的,下面的这张图概括了C语言中主要的数据类型。
那么对于一串0/1序列 我们怎么知道该数值数据代表的值是多少呢?所以为了确定一个数值数据,我们必须确定数值数据表示的三要素:
- –进位计数制
- –定、浮点表示
(解决小数点问题) - 如何用二进制编码
(解决正负号问题)
下面主要来讲一讲整型数据和浮点数据的相关表示。
二.整型
1.类别
整型数据分为:无符号整数 和 带符号整数
有符号数和无符号数的区别主要在于有没有最高位的符号位,以及由此带来的计算方式的不同。符号位中,0 表示非负数,1 表示负数。
在32位机器上long int 为4个字节,而在64位机器上,long int为8个字节!
2.表示范围
可以发现,带符号数的最大值TMax = 2^(w-1)-1 (w为位数)与最小值TMin = $-2^(w-1), 这两个数的绝对值并不相等,为了弄清楚这个问题,我们必须知道定点数的编码表示。
3.定点数的编码表示方式
1.原码
符号位:正号用0表示,负号用1表示,其它数字位代表数值本身的绝对值。
采用原码表示,虽然容易理解,但是会带来很多麻烦:
- 0的表示不唯一,不利于程序员编程;
- 加、减运算方式不;
- 需要额外对符号位进行处理,不利于硬件设计。
所以,从50年代开始,整数都采用补码表示。浮点数的尾数还是用原码定点小数表示(下面相应部分将会讲到)
2.反码
如果是正数,则表示方法和原码一样;如果是负数,符号位不变,其余各位取反,则得到这个数字的反码表示形式。(反码很少用到)
3.补码-模运算
(1)如何求得补码
如果是正数,则表示方法和原码一样;如果是负数,则将数字的反码加上1即得到它的补码;
举个例子: 为了简便,这里只定义位数w=4。
-
-5的原码是1101;
-
将除符号位的其余位置取反得到反码1010;
-
末尾加1,得补码:1011
(2)补码与真值的关系
当我们知道一个数的补码,要求它的真值,可以采用以下两种方式:
-
(1)看符号位! 若符号位为0,则为正数,其值就是后面的数值部分的值;若符号位为1,则为负数,将数值位各位取反,再加1,取结果为负值。
举例:0110,其值为0 * 1+1 * 2+1*4=6;
1110值就等于将110取反再加1的值,即:110---->001---->010,所以其值为-2。 -
(2)公式:
因为能力有限,只能在word里打出来这个公式,且不能复制过来,只好截图了。
解释一下:w是这个数据的位数,k是从0开始计数的序数,如:第一位的k=0,第二位的k=1,以此类推…
对于上面的补码1110=- 2^3+(1 * 2+1*4) = -2
有了这个公式,就可以解开上面提到的那个问题:最大值比最小值的绝对值小1。
(3)模运算概念:
在一个模运算系统中,一个数与它除以“模”后的余数等价!
举个例子:时钟就是一种模12系统。如果现在是时针指向8点,要将它拨向6点,则有两种拨法:
- 倒拨2格:8-2=6
- 顺拨10格: 8+10=16; (mod 12)
模12系统中:8-2=8+10 (mod 12)
-2=10 (mod 12)
则称-2是10对模12的补码。
4.有无符号数的转换
转换公式:当TMin< T<0时:U=2^w+T; (T为补码)
注意点: 如果一个表达式既包含有符号数也包含无符号数,那么会被隐式转换成无符号数进行比较
三.浮点数
1.IEEE754标准
在 IEEE 标准中,我们用下面的公式来表达浮点数:
其中 s 是符号位,决定正负;M 通常是一个值在 [1.0, 2.0) 的小数;E 是次方数。具体编码时结构如下,这里用单精度、双精度和扩展精度为例:
其中 s 对应着符号位,exp 对应着 E(注意,不一定等于 E,因为位数限制表达能力有限),frac 对应着 M(注意,不一定等于 M,因为位数限制表达能力有限)。不同的位数就代表了不同的表示能力,也就是单精度,双精度,扩展精度的来源。
规范化值(Normalized Values)
这里的 E 是一个偏移的值 ,其中
Exp: 是 exp 编码区域的无符号数值
Bias:值为 的偏移量,其中w 是 exp 编码的位数,也就是说
单精度:127(Exp: 1…254, E: -126…127)
双精度:1023(Exp: 1…2046, E: -1022…1023)
之所以需要采用一个偏移量,是为了保证 exp 编码只需要以无符号数来处理。
而对于 M,一定是以 1 开头的:也就是 。其中 xxx 的部分就是 frac 的编码部分,当 frac=000.00 的时候值最小(),当 frac=111。。。1 的时候值最大(),也就是说开头的 1 是免费附送的,并不需要实际的编码位。
举个例子,float F = 15213.0;,那么
于是 frac 部分的值就是小数点后面的数值,而 Exp = E + Bias = 13 + 127 = 140 = ,于是编码出来的浮点数是这样的:
0 10001100 11011011011010000000000
非规范化值(Denormalized Values)
当 的时候,值是非规范化的,意思是,虽然实数轴上原来连续的值会被规范到有限的定值上,但是并些定值之间的间距也是一样的。
和前面不同的是
而且 ,不是以 1 开头了。
当 exp=000…0 且 frac = 000…0 时,表示 0,而且因为符号位的缘故,实际上是有 +0 和 -0 两种的。而在 exp=000…0 且 时,数值是接近 0 的,并且间距是一致的
特殊值
还有一种特殊情况,就是 时,表示一些特殊值。
当 exp=111…1 且 frac = 000…0 时,表示 ,而且因为符号位的缘故,实际上是有 和 两种的。那些会溢出的操作就会用这个来表示,比如
而在 exp=111…1 且 时,我们认为这不是一个数值(Not-a-Number,NaN),用来表示那些没办法确定的值,比如
2.浮点数精度
浮点数精度主要是由尾数部分决定的,有效的位数部分越长,其精度越高(单精度浮点数尾数部分部分最高为23位,双精度浮点数最高为51位)。一个0/1序列的浮点数可以准确对应一个小数,而一个小数却不一定能表示成0/1序列
61.419998和61.420002是两个可表示数,两者相差0.000004,这之间的数是不可表示的,当输入的数据是一个不可表示数时,机器数将其转换为最邻近的可表示数。
看下面的代码:
#include <stdio.h>
int main()
{
float a;
printf("请输入:\n");
scanf("%f",&a);
printf("x=%f",a);
return 0;
}
测试
参考资料、网站:
上一篇: Linux libcurl安装及注意事项
下一篇: Lombox 介绍及使用注意事项