整数表示

用位来编码数字,分为两种:无符号的和有符号的。

无符号的只能表示非负数,而有符号的能表示正数、零和负数。

下面介绍一下计算机中,整数是怎么表示的。

C中整数数据类型

我对C的整数类型不太了解,因为之前都是写Java,知道它有八大基本数据类型。

一般的使用,倒是没什么大问题,但是c中有一个无符号的类型,即unsigned,它是用无符号的位编码而成。

比如32位的unsigned int,它的范围是[0, 4294967295],因为不再需要最高位的符号位了,用它表示正数则是2^31,故范围比有符号int多。

而且,这也可以看成一种平移,我们通常看的4字节int是[-2147483548, 2147483547],相当于把负的往右移。

另外,它还有一个特殊的数据,就是long,在32位机子中,它是占4字节,但是在64位机子中,它是占8字节。

image-20220510174707341

image-20220510174716412

无符号数编码

和我们常见的二进制数一样,就是没有符号位的,全是计数位。

注:下面式子中,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$

一些性质

  1. 补码的范围是不对称的,负数能多表达一位,

    即$|TMIN|=|TMAX|+1$

  2. 最大无符号位表示的数正好是最大补码的两倍加一
    即$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语言中的有符号数和无符号数

一般情况下,声明的变量都是有符号有,只有在末尾添加Uu才是无符号。并且还有unsigned关键字。

并且,一般都是使用补码,而且转换的原则是底层的位保持不变

在标准输出printf的情况下,%d表示输出十进制数,%u表示输出无符号的十进制数,%x表示输出十六进制数。

在C的运算中,会隐式把有符号参数强制类型转换为无符号参数,并假设这两个数都是非负的,然后执行运算。

对于一般的算术运算是没什么问题的,但是对于>或<这种关系运算符来说,就可能有不太直观的结果。

(在草稿纸上写写吧!先看类型,如果类型相同,直接比较;类型不同,转换后,按照类型的值进行判断。注意一下带 *的地方)

image-20220510212157704

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。