整数表示
用位来编码数字,分为两种:无符号的和有符号的。
无符号的只能表示非负数,而有符号的能表示正数、零和负数。
下面介绍一下计算机中,整数是怎么表示的。
C中整数数据类型
我对C的整数类型不太了解,因为之前都是写Java,知道它有八大基本数据类型。
一般的使用,倒是没什么大问题,但是c中有一个无符号的类型,即unsigned
,它是用无符号的位编码而成。
比如32位的unsigned int,它的范围是[0, 4294967295],因为不再需要最高位的符号位了,用它表示正数则是2^31,故范围比有符号int多。
而且,这也可以看成一种平移,我们通常看的4字节int是[-2147483548, 2147483547],相当于把负的往右移。
另外,它还有一个特殊的数据,就是long
,在32位机子中,它是占4字节,但是在64位机子中,它是占8字节。
无符号数编码
和我们常见的二进制数一样,就是没有符号位的,全是计数位。
注:下面式子中,w表示位数。
以0、1取值,然后对值为1的下标的数进行2^i的加权,其实就是二进制转十进制那个嘛。
公式: $B2U(x)=\sum_x_i2i$
那么它的最大值是多少呢?那就是全1了,即$UMax_w=\sum_2i=2^w-1$。
而且,对于二进制位是全1的,等价于最高位再加一位为1,然后再减1,其实就是2^n-1。
补码编码
常见的有符号数,在计算机中都是以补码的形式出现。
在这个定义中,它的最高位的权重是负数,别的同无符号数,为正数。
公式: $B2T(x)=-x_2+\sum_x_i2^i$
举个例子,$B2T([1111])=-23*1+221+2^11+2^0*1=-1$
原来困扰我多时的补码就这,当时还想着什么取反加一,我的天,就是首位权重为负,其它不变!
那么它的最小值是多少呢?因为只有最高位权重为负,所以当最高位为负时,值最小,
最大值则是除最高位以外的所有位都为1。
因此,最小值的向量为[10...0]
即:$TMIN_w=-2^$
最大值的向量为[01...1]
,即$TMAX_w=2^-1$
一些性质
-
补码的范围是不对称的,负数能多表达一位,
即$|TMIN|=|TMAX|+1$
-
最大无符号位表示的数正好是最大补码的两倍加一
即$UMAX=2*TMAX+1$
从位向量来说,UMAX=[11...1],TMAX=[01...1],刚好是TMAX左移一位,后面补个1
原码和反码
在一开始学这些的时候,就是从原码反码开始搞,弄得真是不明不白。
原码是什么,最高有效位是符号位,其它的位正常加权求和,根据符号位确定最后结果的正负。
反码,和补码类似,就是补码-1,因为它的最高有效位的权重比补码多1。而且它还有+0和-0。
这块似乎真的不大重要,因为在这本书中,只有旁注提及这部分内容,而我之前的重点都在如何计算这些原码反码补码上了。
qwq
有符号数与无符号数的转换
对于一些数的转换,我总会想,比如一个负的有符号数转换为无符号数,会不会变成0(因为无符号数能表示最小的数就是0),或者一个很大的无符号数转换为有符号数,会不会变为UMAX?
其实在计算机中,它不会考虑数,而且考虑位。它原来是什么位,转换之后就是什么位,只是各个位的权重不同了,它表现出来的结果也就不同了。
下面这段代码是把有符号数转换为无符号数,可以看看结果是多少?
void test2()
{
short int v = -12345;
unsigned short uv = (unsigned short)v;
// v = -12345, uv = 53191
printf("v = %d, uv = %u", v, uv);
}
为什么会这样呢?
我们可以看看v的位向量:[1100 1111 1100 0111]
对于无符号数,它第一位权重是负的2^15,然后把它相加起来,即可算出-12345。
但是转换成有符号数之后,它的所有位权重都是正的,值为2^i,可以算得结果是53191。
再看看把无符号数转有符号数,也一样的
void test3()
{
// UMax
unsigned u = 4294967295u;
int tu = (int)u;
// u = 4294967295, tu = -1
printf("u = %u, tu = %d", u, tu);
}
u的位向量是[11...1],因为它是UMax嘛,这是无符号的,所有权重都是正数。
但是转成有符号之后,首位就是负了,后面的全部抵消,就成了-1。(怎么理解全部抵消了?因为后面全是1,而且,正数部分+1=负数的值)。
小结论
从上面的代码中,可以得到一个小结论:数值可能改变,但是位模式不会变
同时,书本上写了好多从代数上进行证明的式子,非常精妙,我觉得我缺少这种数学的语言。但是我暂时不将它记录在这里,而是多模拟,手写几遍。
当一个有符号数映射为它对应的无符号数时,负数被转换成了更大的正数,非负数不变。(即把最高位的$-2^$加回来!)
当一个无符号数映射为它对应的有符号数时,若其$\leq TMAX$,不变;若其$\gt TMAX$,转换为负数。(即把最高位的$2^$减回去!这个无符号数“太大”了,用了最高位)
C语言中的有符号数和无符号数
一般情况下,声明的变量都是有符号有,只有在末尾添加U
或u
才是无符号。并且还有unsigned
关键字。
并且,一般都是使用补码,而且转换的原则是底层的位保持不变。
在标准输出printf的情况下,%d表示输出十进制数,%u表示输出无符号的十进制数,%x表示输出十六进制数。
在C的运算中,会隐式把有符号参数强制类型转换为无符号参数,并假设这两个数都是非负的,然后执行运算。
对于一般的算术运算是没什么问题的,但是对于>或<这种关系运算符来说,就可能有不太直观的结果。
(在草稿纸上写写吧!先看类型,如果类型相同,直接比较;类型不同,转换后,按照类型的值进行判断。注意一下带 *的地方)
void check()
{
printf("%d\n", -2147483647 - 1 == 2147483648U);
printf("%d\n", -2147483647 - 1 < 2147483647);
printf("%d\n", -2147483647 - 1U < 2147483647);
printf("%d\n", -2147483647 - 1 < -2147483647);
printf("%d\n", -2147483647 - 1U < -2147483647);
}
1
1
0
1
1
(PS:今天看了一天了,终于搞懂了,还是得多动手,不能懒,汗。。。)
扩展与截断
文章中有很多数学性证明,但我似乎不太懂= =。
对于无符号的整数,扩展只需要往高位加0。
对于有符号的整数,扩展只需要往高位添加符号位的值。
比如有符号整数是[1011],要扩展两位,就变成[111011],加符号位1。
截断操作,其实跟mod取模操作有关。
对于无符号整数,截断位数,就是把高位直接干掉,它的值就是剩下位数的权重和。
对于有符号整数(即补码),同样是截断位数,然后把截断后数做U2T操作。
比如,4位的补码-1,位向量是[1111],截断成3位,位向量就变成[111],再转回补码,就是-1。