c语言中关于数字的知识点有一些是非常有用的,但是又容易忘,我在这里就想简单的总结一下(这里没有给出详细证明)。
一、c语言整数表示
(一)、有符号数和无符号数之间的转化
c语言允许不同的数字类型之间做强制类型转换,那转换的规则就是我们需要知道的,将一个有符号数t(int)转化成无符号数u(unsigned int),或者相反,会得到什么结果呢?我们想要的结果当然是数值保持不变。
请先记住关键的一点,强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。
为了简单假设int_类型只有四个比特,举个例子:
1. int -----> unsigned int
int_ a = -8; // 在内存中的实际存储形式,补码:1000 (假设就是使用补码的机器)
unsigned int_ b = (unsigned int_)a; // b所对应的内存中还是1000,以无符号数的规则解释就是8
2. unsigned int -------> int
unsigned int_ a = 15; // 1111
int_ b = (int_)a; // 1111以有符号数规则解析就是负数-1
C 标准没有精确规定应如何进行这种转换,但大多数系统遵循的原则是底层的位表示保持不变
还有一点要注意就是:当执行一个运算时,如果一个操作数是有符号数,另一个是无符号数,则会隐式的将有符号数转化为无符号数。
(二)、扩展数字的位
当一个数据类型转换为更大的数据类型是会发生位的拓展。
1.无符号数的扩展:
要将一个无符号数转换为一个更大的数据类型, 我们只要简单地在表示的开头添加 0。这种运算被称为零扩展。
2.有符号数的扩展:
要将一个补码数字转换为一个更大的数据类型,可以执行一个符号扩展, 在表示中添加最高有效位的值。
(三)、截断数字
将一个较大的数据类型转化为较小的数据类型也是很常见的,但是截断可能会改变数字的值(这也是溢出的一种形式)
1.截断无符号数:
假设a是一个w位的无符号数,把它截断为k位的数字b,则b = a % (2^k)。
原理很简单,所有被截去的位的权重都是2^i,i >= k,每一位的值取模之后都是零。
2.截断有符号数:
有符号数截断跟无符号数的行为几乎一样,至需要按照无符号数的截断方式截断,最后把截断之后的最高位转化成符号位就可以了。
(四)、整数运算溢出(这里是加法的例子)
1.无符号数溢出
设x,y都是无符号数,w为位数,当x + y < 2^w时,x + y = x + y;当x + y >= 2 ^ w时,x + y = x + y - 2 ^ w(就是正常按位相加,最后截断超过w的位)。
还有一个判定是否溢出的方法:设x + y = s,如果s < x(或x < y),则发生了溢出。
简单证明:观察得x + y >= x,所以如果未溢出则s >= x,而如果溢出了则s = x + y - 2^w,又因为y - 2 ^ w < 0,所以s < x。
2.有符号数溢出:
设x,y都是无符号数,w为位数,当x + y < 2^(w-1)时,x + y = x + y + 2^w;当-2^(w-1)<=x+y<2^(w-1)时,x + y = x + y;当x + y >= 2 ^ (w-1)时,x + y = x + y - 2 ^ w。
补码加法跟无符号数加法有着相同的位级表示,所以可以将操作数转化为无符号数,进行加法操作,最后将计算结果转化为有符号数。
判定是否溢出:设Min是有符号数最小值,Max为最大值,根据上面公式可知,前提Min<=x,y<=Max,x + y = s,当且仅当x > 0,y > 0,但s<=0时,发生了正溢出;当且仅当x < 0,y < 0,但s>=0时,发生了正溢出。
这个就不给证明了,显而易见。
3.补码的非
Min<=x<=Max范围的每个数字都有自己的加法逆元,对于Min的非(也就是逆元)是自己,对于其他的数x的非是-x。
证明:设w为位数,Min+Min = -2^(w-1)+(-2^(w-1)) = -2^w,这将会发生溢出,有前面可知,负溢出需要加个2^w,所以Min+Min = -2^w + 2^w = 0;对于非Min的数,x + (-x) = 0。
二、浮点数表示
(一)、IEEE(电气和电子工程师协会)浮点数表示
IEEE浮点标准用V = (-1)^s * M * 2^E的形式来表示一个浮点数。
符号:s用来标识符号位,除了数值为0的数字外,s用来标识一个数是正数(s=0)还是负数(s=1);
尾数:M是一个二进制小数,范围是1~2-ε或0~1-ε;
阶码:E是对浮点数加权,这个权重是2的ε次幂。
一个单独的位s编码符号s,k位的阶码字段e编码E,n位的尾数字段m编码尾数M。
在c语言中float(单精度浮点数)的s=1,k=8,n=23,double(双精度浮点数)的s=1,k=11,n=52。
根据阶码字段e的不同,可以分成三种情况:
1.规格化的
1)即阶码域位模式既全不为0,又全不为1。
2)阶码字段e被解释为以偏置形式表示的有符号整数,定义为E=e-bias,bias为2^(k-1)-1的偏置值。
3)尾数字段m被解释为小数值b,且0<=b<1,在这种情况下,尾数定义为M=b+1,即隐含的以1开头的表示,因为这种情况下小数总是以1开头,所以不需要显示的表示。
2.非规格化的
1)即阶码域位模式全为0。
2)阶码值定义为E=1-bias。
3)尾数值M=m,不隐式的包含1,所以0<=M<1。
3.特殊值
1)即阶码域位模式全为1。
2)当小数域m全为0时表示无穷大,当s=0时,表示正无穷大;当s=1时,表示负无穷大。
3)当小数域m全为1时为NaN,即Not a Number(不是一个数)。
(二)、浮点运算
浮点运算不具有结合性。例如,使用单精度浮点, 表达式(3.14+1e10)-1e10求值得到 0.0— 因为舍入,值 3.14 会丢失。另一方面, 表达式 3.14+(1e10-1e10)得出值 3.14。
浮点乘法在加法上不具备分配性。例如,单精度浮点情况下, 表达式1e20*(1e20-1e20)求值为 0.0, 而 1e20*1e20-1e20*1e20 会得出NaN(+∞-∞=NaN)。
浮点加法满足了单调性属性:如果 那么对于任何 a >= b,那么对于任何a、b以及x的值,除了NaN,都有x+a>=x+b(即使相加都为无穷大)。无符号或补码加法不具有这个实数(和整数)加法的属性。
(三)、int、float 和 double 格式之间进行强制类型转换
1.从 int 转换成 floatÿ 数字不会溢出,但是可能被舍入。
2.从 int 或 float 转换成 double,因为 double 有更大的范围,也有更高的精度,所以能够保留精确的数值。
3.从 double 转换成 float,因为范围要小一些,所以值可能溢出成+∞或-∞。另外,由于精确度较小,它还可能被舍人。4.从 float 或者 double 转换成 int,值将会向零舍入。