c语言计算结果为1. inf0000,信息的表示和处理

信息的存储

字数据大小

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

寻址和字节顺序

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

大端 : 最高有效字节在最前面的方式(内存低地址存储最高的8位),和书写顺序相同。

小端 :最低有效字节在最前面的方式(内存高地址存储最高的8位)。低地址存储最低8位,最高地址存储最高8位的形式排序。

表示字符串

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.

移位操作优先级比+,-,*,/都是要低。

整数表示

无符号数的编码

针对向量

math?formula=%5Cvec%7Bx%7D = [xw-1,....x0] ,

B2Uw(

math?formula=%5Cvec%7Bx%7D) =

math?formula=%5Csum_%7Bi%3D1%7D%5E%7Bw-1%7Dx_i2%5E%7Bi%7D .

B2Uw表示把长度w的位向量映射位无符号整数。UMaxw =

math?formula=%5Csum_%7Bi%3D1%7D%5E%7Bw-1%7Dx_i2%5E%7Bi%7D =

math?formula=2%5Ew-1.B2Uw为一个双射,每一个无符号整数都有一个w位的位向量,每一个位向量都可映射为一个唯一的无符号整数。

补码编码

补码的定义:B2Tw(

math?formula=%5Cvec%7Bx%7D) =

math?formula=-x_%7Bw-1%7D2%5E%7Bw-1%7D%20%2B%20%5Csum_%7Bi%3D1%7D%5E%7Bw-2%7Dx_i2%5E%7Bi%7D.最高位w-1为符号位。符号位为1时,即为负数。Tminw =

math?formula=-2%5E%7Bw-1%7D.Tmaxw =

math?formula=%5Csum_%7Bi%3D1%7D%5E%7Bw-2%7Dx_i2%5E%7Bi%7D =

math?formula=2%5E%7Bw-1%7D-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 默认是有符号的。如果要申明一个符号整数

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

拓展一个数字的位表示

无符号数的拓展,将无符号数据拓展为一个更大的数据类型,将开头的部分填充0.

补码的拓展,将宽度为w的位向量

math?formula=%5Cvec%7Bx%7D = [xw-1,....x0] 拓展成为

math?formula=w%5E%7B'%7D的位向量 [xw-1,xw-1,xw-1,....x0] .这个可以算算术填充。可以证明B2Tw+k[xw-1,xw-1,xw-1,....x0] = B2Tw[xw-1,....x0] .

截断数字

截断无符号数,令向量

math?formula=%5Cvec%7Bx%7D 为 [xw-1,....x0] 截断为k位数字时,会丢弃高w-k位。得到一个新向量

math?formula=%5Cvec%7Bx%5E%7B'%7D%7D位 [xk-1,....x0],截取一个无符号数可能会改变数字结果(溢出的一种方式)。数学上采用mod

math?formula=2%5E%7Bk%7D次方的形式表示。

截断补码,令向量

math?formula=%5Cvec%7Bx%7D 为 [xw-1,....x0] 截断为k位数字时,得到一个新向量

math?formula=%5Cvec%7Bx%5E%7B'%7D%7D位 [xk-1,....x0]。有符号数32位53191(十六进制CFC7),截取成16位整数时,-65536 + 53191 = -12345。

当一个有符号负数转变位无符号数时(往往隐式转换),会产生一个很大的符号数,往往就会产生出乎意料的bug,所以尽量不使用无符号数。往往在使用bit位表示不同布尔含义的时候才使用无符号数。

整数运算

无符号数的加法

math?formula=x%2Cy%20%5Cin%20%5B0%2C2%5Ew)%2C%3D%3D%3E%20x%2By%20%5Cin%20%5B0%2C2%5E%7Bw%2B1%7D).所以当

math?formula=x%2By%20%5Cin%20%5B2%5Ew%2C2%5E%7Bw%2B1%7D)时,发生溢出。溢出结果是减去

math?formula=2%5Ew的结果。

math?formula=x%2B_%7Bw%7D%5E%7Bu%7Dy表示,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;

}

补码加法

给定范围

math?formula=-2%5E%7Bw-1%7D%20%5Cleq%20x%2Cy%20%5Cleq%202%5E%7Bw-1%7D-1.

math?formula=x%2B_%7Bw%7D%5E%7Bt%7Dy%20%3D%5Cleft%5C%7B%20%5Cbegin%7Baligned%7D%20%26%20x%2By%20-%202%5Ew%20%2C%20x%2By%20%5Cgeq%202%5E%7Bw-1%7D%20%EF%BC%88%E6%AD%A3%E6%BA%A2%E5%87%BA%EF%BC%89%5C%5C%20%26%20x%2By%20%2C%20-2%5E%7Bw-1%7D%20%3C%20x%2By%20%5Cleq%202%5E%7Bw-1%7D%20%EF%BC%88%E6%AD%A3%E5%B8%B8%EF%BC%89%5C%5C%20%26%20x%2By%20%2B%202%5Ew%20%2C%20x%2By%20%3C%20-2%5E%7Bw-1%7D%20%EF%BC%88%E8%B4%9F%E6%BA%A2%E5%87%BA%EF%BC%89%5C%5C%20%5Cend%7Baligned%7D%20%5Cright.

对于

math?formula=Tmin_w%20%5Cleq%20x%2Cy%20%5Cleq%20Tmax_w%EF%BC%8C%E4%BB%A4s%20%3D%20x%20%2B%20_%7Bw%7D%5E%7Bt%7Dy%2C%E5%BD%93%E4%B8%94%E4%BB%85%E5%BD%93x%20%3E%200%2Cy%20%3E%200%E6%97%B6%EF%BC%8Cs%20%5Cleq%200%E6%97%B6%E5%8F%91%E7%94%9F%E6%AD%A3%E6%BA%A2%E5%87%BA%EF%BC%8Cx%3C0%2Cy%3C0%E6%97%B6%EF%BC%8Cs%5Cgeq0%E6%97%B6%E5%8F%91%E7%94%9F%E8%B4%9F%E6%BA%A2%E5%87%BA

//如果溢出,返回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,所以只需要判定一个条件即可。

}

补码的非

math?formula=Tmin_w%20%5Cleq%20x%20%5Cleq%20Tmax_w中,都有一个逆

math?formula=-_%7Bw%7D%5E%7Bt%7Dx使得

math?formula=-_%7Bw%7D%5E%7Bt%7Dx%20%2B%20x%20%3D%200。所以

math?formula=-_%7Bw%7D%5E%7Bt%7Dx%20%3D%5Cleft%5C%7B%20%5Cbegin%7Baligned%7D%20%26%20Tmin_w%20%2C%20x%20%3D%20Tmin_w%20%5C%5C%20%26%20-x%20%5C%5C%20%5Cend%7Baligned%7D%20%5Cright.

在这个也和无符号的逆运算的位结果相同。

math?formula=%7CTmin_w%7C%20%3D%20%7CTmax_w%20%2B%201%7C时,在

math?formula=x%20%3D%20Tmin_w时没有对应的正整数,负数表示范围比正数表示范围大1.即[1000...000]表示

math?formula=Tmin_w,而[0000...000]则表示0.

从补码位级的值有两种技巧。

表达式-x 和 ~x +1得到的结果一样。所以在求取的值时0xfffffffa。~x+1 = 0x05 + 1 = 0x06 = -x。所以x的值是-0x06.

x的位表示位

math?formula=%5Bx_%7Bw-1%7D%2Cx_%7Bw-2%7D%2C...1%2C0...%2C0%5D,这个值的非的表示可以写为

math?formula=%5B~x_%7Bw-1%7D%2C~x_%7Bw-2%7D%2C...1%2C0...%2C0%5D。继续看上面的例子0xfffffffa位级表示是[1,...1010],-x = [0...0110] = 6.所以x = -6.

无符号数的乘法

math?formula=0%20%5Cleq%20x%20%2Cy%20%5Cleq%202%5Ew-1中,

math?formula=x*_%7Bw%7D%5E%7Bu%7Dy%20%3D%20(x%20%5Ctimes%20y)%20mod%202%5E%7Bw%7D。直接进行位截断处理。

补码的乘法

math?formula=-2%5E%7Bw-1%7D%20%5Cleq%20x%20%2Cy%20%5Cleq%202%5E%7Bw-1%7D-1中,

math?formula=x%20%5Ctimes%20y的取值范围在

math?formula=%5B-2%5E%7B2w-2%7D%2B2%5E%7Bw-1%7D%20%2C2%5E%7B2w-2%7D%5D.如果想表示全需要2w-1位来表示。c语言中,直接采用截断位w位的方式来实现。

math?formula=x*_%7Bw%7D%5E%7Bt%7Dy%20%3D%20U2T_%7Bw%7D((x%20%5Ctimes%20y)%20mod%202%5Ew)。所以无符号乘法和补码乘法具有相同的位级等价性。

如果[101] 和[011]相乘。

如果解释成无符号乘法等价于

math?formula=5%20%5Ctimes%203%20%3D%2015%20%2C15%20mod%202%5E3%20%3D%207.位级结果为[1111]丢弃最高位为[111]即为7.

如果解释成补码乘法等价于

math?formula=-3%20%5Ctimes%203%20%3D%20-9%20%2C-9%20mod%202%5E3%20%3D%20-1. 位级别结果为[1111]丢弃最高位为[111]即为-1.

可能针对完整的最终位计算结果可能不同,但是截取后的结果是相同的。参考[100] * [111].

乘以常数

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

乘以2的幂的无符号乘法等价于左移一个数值。表达式

math?formula=0%20%5Cleq%20k%20%5Cleq%20w,x<

math?formula=x*_%7Bw%7D%5E%7Bu%7Dy,补码乘法和无符号乘法也一样。注意无论补码运算还是无符号运算,乘以2的幂都会导致溢出,但是不论移位还是乘法导致的溢出的结果都是一致的。

由于乘法比移位和加法的组合代价要大的多。所以很多编译器会尝试着用加法和移位的组合来代替乘法。比如x*14 可以用(x<<3) + (x<<2) + (x<<1)这样的组合来替代乘法。甚至可以用(x<<4) - (x<<1)来替换,这样只需要2个移位和1个减法就可以了。

除以2的幂

在大多数机器中整数除法比整数乘法更慢,往往需要30甚至以上的时钟周期。除以2的幂,也可以用右移来实现。无符号和补码分别使用逻辑右移和算术右移来达到目的。

整数除法总是舍入到0,对于x>=0,采用向下舍入的方式,对于x<0采用向上舍入的方式。即总是向0舍入。

除以2的幂的无符号除法。采用逻辑右移的方式。移位总是舍入到0.

除以2的幂的补码除法,采用算术右移的方式。当x<0的时候要先加上一个“偏置量”1<>k的形式来保证向0舍入。

浮点数

二进制小数

math?formula=b_%7Bw%7Db_%7Bw-1%7D...b_1b_0.b_%7B-1%7Db_%7B-2%7D...b_%7B-n%7D的表示法可以表示二进制小数。b =

math?formula=%5Csum_%7Bi%3D-n%7D%5E%7Bm%7Db_i2%5E%7Bi%7D,十进制无法能精确表示1/3.同样有限位数的二进制小数也无法精确表示0.1,0.2之类,只能近似的表示。

IEEE浮点数

IEEE浮点数标准用

math?formula=V%20%3D(-1)%5Es%20%5Ctimes%20M%20%5Ctimes%202%5EE ,

s为符号位(sign),负数s = 1,正数s = 0

M为二进制小数(significand),可以表示

math?formula=2-%20%5Cvarepsilon,也可以是

math?formula=1-%20%5Cvarepsilon,后面会讲到前者是规范模式下,后者是非规范模式下表示趋近0的小数。

E位阶码(exponent), 加权作用。偏置量 bias =

math?formula=2%5E%7Bk-1%7D-1.规范化下,阶码E = e- bias.其中e 为无符号整数

math?formula=e_%7Bk-1%7De_%7Bk-2%7D...e%7B1%7D

最常见的浮点类型,单精度浮点数和双精度浮点数,就单精度(c语言中float)s的位数为1,exp的位数为8位,frac位23位。在双精度浮点数(c语言的double)中s的位数为1,exp位数为11,frac位数为52位。根据exp的值的不同,可以分为3种情况。

1.规范化表示,当exp的位模式既不是全0,也不是全1。阶码的字段被解释成偏置(biased)的形式表示的有符号整数。阶码E = e - bias。其中e为无符号整数,其位表示为

math?formula=e_%7Bk-1%7De_%7Bk-2%7D...e_0,bias为

math?formula=2%5E%7Bk-1%7D%20-1。在float类型中,规范化下

math?formula=e%5Cin%5B1%2C254%5D%2Cbias%20%3D%20127%20%E5%8F%AF%E4%BB%A5%E6%8E%A8%E5%AF%BC%E5%87%BAE%5Cin%5B-126%2C127%5D,同理在double类型下

math?formula=E%20%5Cin%20%5B-1022%2C1023%5D

小数字段frac被描述成f,

math?formula=0%5Cleq%20f%20%3C%201,其二进制表示为

math?formula=0.f_%7Bn-1%7D...f_1f_0,尾数M定义为1+f。这个是以隐含1开头的表示方法,通过这种方法我们可以额外的获得1位精度。

2.非规格化的表示,当exp的位全部为0时,阶码的字段被解释成E= 1- bias.尾数M = f.小数的部分不包含1开头的小数。所以可以表示+0.0和-0.0,exp位全部为0,以及frac的位全部为0,符号位s为0时表示+0.0,符号位1时表示-0.0。之所以用1-bias 而不是用0-bias的原因是规范化下exp段最小值也是1-bias,而frac被解释成不带1的小数。所以用1-bias可以平滑过渡小数的表示。在float类型中,E = -126。同理在double类型中E = -1022.

非规格化的数表示那些非常接近0的数,提供一种属性称为“逐渐溢出”,分布的数值均匀的分布地接近0.0.

3.特殊值,当exp的位全部为1时,如果frac的位全部为0,那么被解释成

math?formula=%5Cinfty,符号位s=0时,解释成

math?formula=%2B%5Cinfty,符号位s=1时,解释成

math?formula=-%5Cinfty,比如两个很大的数相乘或者除以0,无穷表示溢出的结果。当frac的位不全为0时,表示NaN,比如

math?formula=%5Csqrt%7B-1%7D,或者

math?formula=%5Cinfty%20-%20%5Cinfty时。

//输出的结果符合数学定义

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来获取定义的一些特殊值的常数,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。

c语言数据类型转换

当从int转换成float时,数字不会溢出,可能会被舍入。

当从int/float转换成double时,因为double有更大精度和范围,所以能准确保留精度。

当从double转换成float时,值可能溢出到

math?formula=%5Cinfty,也可能被舍入。因为float表示范围更小,且有效位数更小。

当从float/double 转换成int时,值会向0舍入。值也可能会溢出。c语言标准没有指出这种情况的固定结果,但是intel兼容的微处理器制定位模式为[100..000]字长为w时的

math?formula=Tmin_w。即不能为浮点数产生一个合理的近似的整数值。因此表达式(int)1e10会得到-21483648。从正值变成一个负值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值