信息的表示和处理

2019-04-01  本文已影响0人  Teech

信息的存储

字数据大小

计算机中,字长指的是指针数据标称大小,虚拟地址以字来进行编码的,所以字长w位的机器,可以表示的虚拟内存的范围是0~2^w-1。32位字长的机器可以表示的范围是4GB。一个指针占用的大小,编译的阶段确定。当用gcc -m32的时候,即表明只能在32位字长的机器上运行,生成的可执行文件中,表示了指针的大小被分配了32位,反之用gcc -m64则64位。区别在于代码如何被编译。

寻址和字节顺序

对象的地址是指的对象中连续字节序列中的最小地址。字节如何被排列有两种做法,即大段和小端。

表示字符串

c语言中,字符串可以被认为是以null(0)结尾的字符序列,每个字符都是由ASCII 编码。内存中分布的顺序和与字节顺序无关。

移位运算

c语言中提供的位移运算,c语言标准中没有提出到底是右移是逻辑位移还是算术位移,但基本所有的编译器和机器组合都是实现的是算术右移(个人理解因为大部分机器都是基于补码实现的机器)。算术右移n位,最左边补充的n位,都是和原来最高位的bit值相同。在序列位向量[xw-1,....x0]中,如果算术右移动n位,即得出的新的位向量位[xw-1,xw-1,...,xw-1,xw-2,...,x0],其中[xw-1的个数位n+1个。位运算左移操作都是在最右边填充0.
移位操作优先级比+,-,*,/都是要低。

整数表示

无符号数的编码

针对向量 \vec{x} = [xw-1,....x0] ,
B2Uw( \vec{x}) = \sum_{i=1}^{w-1}x_i2^{i} .
B2Uw表示把长度w的位向量映射位无符号整数。UMaxw = \sum_{i=1}^{w-1}x_i2^{i} = 2^w-1.B2Uw为一个双射,每一个无符号整数都有一个w位的位向量,每一个位向量都可映射为一个唯一的无符号整数。

补码编码

补码的定义:B2Tw( \vec{x}) = -x_{w-1}2^{w-1} + \sum_{i=1}^{w-2}x_i2^{i}.最高位w-1为符号位。符号位为1时,即为负数。Tminw = -2^{w-1}.Tmaxw = \sum_{i=1}^{w-2}x_i2^{i} = 2^{w-1}-1.B2Tw同样是一个双射。补码的范围是不对称的。|Tminw| = |Tmaxw + 1|.Tminw没有对应的正数。最大符号数Umaxw = 2Tmaxw + 1.-1和Umaxw具有相同的表示,即所有位均为1.

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

有符号和无符号数的转换,会保持位不变,只是改变解释这些位的方式。数值可能会改变,但是位不变。如果 0≤x≤Umaxw,函数U2Bw都会给出唯一的w位无符号表示。当Tminw≤x≤Tmaxw,函数T2Bw都会给出唯一的w位补码形式表示。当输入一个Tminw~Tmaxw之间的数都会对应唯一一个0 ~ Umaxw的值。这两个数具有相同的位模式。反之,则一样。

c语言中无符号数与有符号数。

声明一个常量是。比如0xABCD或者12345 默认是有符号的。如果要申明一个符号整数 12345_u.c语言标准没有说明无符号和有符号之间如何转换,大部分是保持位不变,改变解释位的形式转换。当一些表达式同时出现有符号和符号的时候,c语言会隐似的把有符号转换成无符号的形式。< 和 >这样的关系运算符往往会产生非直观的结果。比如-1 < 0u 表达式结果为1。 2147483647 > (int)2147483648u表达式结果为1。

拓展一个数字的位表示

截断数字

整数运算

无符号数的加法

x,y \in [0,2^w),==> x+y \in [0,2^{w+1}).所以当x+y \in [2^w,2^{w+1})时,发生溢出。溢出结果是减去2^w的结果。x+_{w}^{u}y表示,x + y的结果截断位w位在把这个结果看做一个无符号数,这个也是一个形式的模运算。比如x=9,y=12的位表示位[1001]以及[1100].他们的和是21[10101].简单丢弃高位得到[0101](十进制5)。这个结果和21 mod 16结果一致。

//如果溢出,返回0,不会溢出返回1
int uadd_ok(unsigned x,unsigned y){
     unsigned s = x + y;
     if(s < x) return 0; //等价于 if(s < y) return 0;当且仅当这个情况发生溢出
     return 1;
}

补码加法

给定范围 -2^{w-1} \leq x,y \leq 2^{w-1}-1.
x+_{w}^{t}y =\left\{ \begin{aligned} & x+y - 2^w , x+y \geq 2^{w-1} (正溢出)\\ & x+y , -2^{w-1} < x+y \leq 2^{w-1} (正常)\\ & x+y + 2^w , x+y < -2^{w-1} (负溢出)\\ \end{aligned} \right.
对于Tmin_w \leq x,y \leq Tmax_w,令s = x + _{w}^{t}y,当且仅当x > 0,y > 0时,s \leq 0时发生正溢出,x<0,y<0时,s\geq0时发生负溢出

//如果溢出,返回0,不会溢出返回1
//版本1 根据定义
int tadd_ok1(int x,int y){
  int s =  x + y;
  int ret = 1;
  if (x > 0 && y > 0 && s<= 0 ) ret = 0;
  if (x < 0 && y < 0 && s>= 0) ret = 0;
  return ret;
}
//版本2
int tadd_ok2(int x,int y){
  int s = x+ y;
  return (s - x) == y; //等价于(s-y) ==  x,所以只需要判定一个条件即可。
}

补码的非

Tmin_w \leq x \leq Tmax_w中,都有一个逆-_{w}^{t}x使得 -_{w}^{t}x + x = 0。所以
-_{w}^{t}x =\left\{ \begin{aligned} & Tmin_w , x = Tmin_w \\ & -x \\ \end{aligned} \right.
在这个也和无符号的逆运算的位结果相同。|Tmin_w| = |Tmax_w + 1|时,在x = Tmin_w时没有对应的正整数,负数表示范围比正数表示范围大1.即[1000...000]表示Tmin_w,而[0000...000]则表示0.
从补码位级的值有两种技巧。

无符号数的乘法

0 \leq x ,y \leq 2^w-1中,x*_{w}^{u}y = (x \times y) mod 2^{w}。直接进行位截断处理。

补码的乘法

-2^{w-1} \leq x ,y \leq 2^{w-1}-1中,x \times y的取值范围在[-2^{2w-2}+2^{w-1} ,2^{2w-2}].如果想表示全需要2w-1位来表示。c语言中,直接采用截断位w位的方式来实现。x*_{w}^{t}y = U2T_{w}((x \times y) mod 2^w)。所以无符号乘法和补码乘法具有相同的位级等价性。
如果[101] 和[011]相乘。

  1. 如果解释成无符号乘法等价于5 \times 3 = 15 ,15 mod 2^3 = 7.位级结果为[1111]丢弃最高位为[111]即为7.
  2. 如果解释成补码乘法等价于-3 \times 3 = -9 ,-9 mod 2^3 = -1. 位级别结果为[1111]丢弃最高位为[111]即为-1.
    可能针对完整的最终位计算结果可能不同,但是截取后的结果是相同的。参考[100] * [111].

乘以常数

因为乘法需要的cpu周期往往是10个甚至更多。所以编译器优化的时候,会考虑用移位计算和加法运算替代乘法运算。

除以2的幂

在大多数机器中整数除法比整数乘法更慢,往往需要30甚至以上的时钟周期。除以2的幂,也可以用右移来实现。无符号和补码分别使用逻辑右移和算术右移来达到目的。
整数除法总是舍入到0,对于x>=0,采用向下舍入的方式,对于x<0采用向上舍入的方式。即总是向0舍入。

浮点数

二进制小数

b_{w}b_{w-1}...b_1b_0.b_{-1}b_{-2}...b_{-n}的表示法可以表示二进制小数。b = \sum_{i=-n}^{m}b_i2^{i},十进制无法能精确表示1/3.同样有限位数的二进制小数也无法精确表示0.1,0.2之类,只能近似的表示。

IEEE浮点数

IEEE浮点数标准用V =(-1)^s \times M \times 2^E ,

//输出的结果符合数学定义
unsigned int u1 = 0x7F800000;
unsigned int u2 = 0x7F800000;
float p1 = *(float*)(&u1); //p1表示+无穷
float p2 = *(float*)(&u2);//p2表示-无穷
printf("%f\n",p1+p2); //输出nan
printf("%f\n",p1+p1); //输出inf +无穷
printf("%f\n",p2+p2); //输出-inf -无穷
printf("%f\n",p1+0.5f);//输出inf +无穷
printf("%f\n",p2+0.5f);//输出-inf -无穷

unsigned int maxu1 = 0x7F7FFFFF;
float maxf1  = *(float*)(&maxu1 );
float maxf2 = maxf1 + 1024.0f; //有符号整数溢出,由于精度问题会舍去1024。
unsigned maxe = 0x7F000000;//指数位相同,尾数M=1.0。
float maxf3 = maxf1 + maxe; //输出无穷 上溢
printf("f max=%f,max2=%f,maxf3=%f\n",maxf1,maxf2,maxf3); //输出f max=340282346638528859811704183484516925440.000000,max2=340282346638528859811704183484516925440.000000
printf("hex max=%x,max2=%x\n",*(unsigned*)(&maxf1),*(unsigned*)(&maxf2));//输出hex max=7f7fffff,max2=7f7fffff 加法会溢出掉最小的数字不会溢出到无穷大。
printf("f max=%f",maxf1*1.1f); //输出inf.乘法会溢出到无穷大

例子中和最大浮点数加上1024,由于浮点加法的运算法则的关系,先保证指数位一致,来缩放尾数,在由于精度只有23位,故丢去了1024.

舍入

浮点运算的结果,针对中间值的情况采用偶数舍入的方式。举个例子要求舍入到二进制小数点后1位。
Round(0.0011) = 0.0
Round(0.0101) = 0.1
Round(0.1100) = 1.0
Round(0.0100)= 0.0

浮点数的运算

浮点数的加法,只具有交换性,不具备结合性。比如(3.14+1e10)-1e10,结果为0,3.14+(1e10-1e10)结果为3.14.前者3.14+1e10,由于浮点加法特性,3.14被舍入了。因为浮点加法不具备结合性。
浮点数的乘法,乘法可能会溢出到无穷,也不具备结合性。举个反例(1e201e20)1e-20,1e201e20溢出到无穷,所以结果是无穷,而1e20(1e20*1e-20)结果为1e20.

c语言的浮点数

c语言没有强制要求使用IEEE浮点数,所以没有标准的方法获取+0,-0,+∞,-∞,NaN之类的特殊值,GNU编译器GCC通过包含头文件比如#include<math.h>来获取定义的一些特殊值的常数,INFINITY表示+∞,NAN表示NaN。

    unsigned int mm1 = 0x7FF00000;
    unsigned int mm2 = 0xFFF00000;
    unsigned int zz1 = 0x00000000;
    unsigned int zz2 = 0x80000000;
    unsigned int nn1 = 0x7FF00000;
    unsigned int nn2 = 0xFFF00000;
    if(*(float*)(&mm1) == *(float*)(&mm2))
        printf("正无穷==负无穷\n");
    else
        printf("正无穷!=负无穷\n");

    if(*(float*)(&mm1) == *(float*)(&mm1))
        printf("正无穷==正无穷\n");
    else
        printf("正无穷!=正无穷\n");
            
    if(*(float*)(&zz1) == *(float*)(&zz2))
        printf("正0==负0\n");
    else
        printf("正0!=负0\n");
            
    if(*(float*)(&nn1) == *(float*)(&nn2))
        printf("NaN1==NaN2\n");
    else
        printf("NaN1!=NaN2\n");

    if(*(float*)(&nn1) == *(float*)(&nn1))
        printf("NaN1==NaN1\n");
    else
        printf("NaN1!=NaN1\n");

输出结果:
正无穷!=负无穷
正无穷!=正无穷
正0==负0
NaN1!=NaN2
NaN1!=NaN1
可以得出结论,无穷/NaN不等于任何值,+0等于-0。

上一篇 下一篇

猜你喜欢

热点阅读