整数表示

无符号数的编码

C/C++支持有符号(默认)和无符号数,Java只支持有符号数

一个$\omega$位的整数看作$\vec{x}$表示整个向量,或者写成$[x_{\omega -1}, x_{\omega -2}, …, x_0]$ ,表示向量中的每一位,可得出无符号数编码的定义

$B2U_\omega$(Binary to Unsighed),对于向量$\vec{x}=[x_{\omega -1}, x_{\omega -2}, …, x_0]$:
$$
B2U_\omega \doteq \sum^{\omega -1}_{i=0}x_{i}2^{i}
$$
其中,$\omega$能表示的范围,最小值为向量$[000···0]$也就是整数0,最大值为向量$[111···1]$也就是整数值$UMax_{\omega} \doteq \sum^{\omega -1}_{i=0}2^{i}=w^{\omega}-1$

补码编码

补码可以表示负数,补码(two’s-complement)将最高有效位解释为负权(negative weight),用函数$B2T_\omega$(Binary to Two’s-complent),补码的定义:

对向量$\vec x=[x_{\omega -1}, x_{\omega -2}, …, x_0]$:
$$
B2T_{\omega}(\vec x)\doteq-x_{\omega-1}2^{\omega-1}+\sum_{i=0}^{\omega -2}x^{i}2^{i}
$$
最高有效位$x_{\omega-1}$也叫符号位,它的“权重”为$-2^{\omega-1}$。符号位被表示为1时,表示值为负,设置为0时,值为非负。

$\omega$位补码所能表示的最小值是向量$[10···0]$(也就是标志位为负,其他所有位为0),其整数值为$TMin_{\omega}\doteq-2^{\omega-1}$,最大值是位向量$[01···1]$,其整数值为$TMax_{\omega}\doteq\sum_{i=0}^{\omega-2}=2^{\omega-1}-1$。

补充:有符号数的其他表示方法

反码(One’s Complement)

除了最高有效位的权是$-(2^{\omega-1}-1)$而不是$-2^{\omega-1}$,它和补码是一样的:
$$
B2O_{\omega}(\vec x)\doteq-x_{\omega-1}(2^{\omega-1}-1)+\sum^{\omega-2}{i=0}x{i}2^{i}
$$

原码(Sign-Magnitude)

最高有效位是符号位,用来确定剩下的位应该取负权还是正权:
$$
B2S_{\omega}(\vec x)\doteq(-1)^{x_{\omega-1}}\cdot(\sum_{i=0}^{\omega-2}x_{i}2^{i})
$$

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

C语言允许在不同的数字数据类型质检做强制转换。例如变量x声明为int,表达式(unsigned)x会将x的值转换成一个无符号数值,从数学的角度来说,对于在两种形式都能表示的值,我们想要保持不变。另一方面,将负数转换为无符号数可能会得到0,如果无符号数太大超过补码能够表示的范围,可能会得到$TMax$,对于C语言来说,这个问题的回答要从位级角度来看。

1
2
3
short int v = -12345;
unsigned short uv = (unsigned short) v;
printf("v = %d, uv = %u\n", v, uv);

在采用补码的机器上,会产生如下输出

v = -12345, uv = 53191

可以看到强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。-12345的补码表示和53191的无符号表示是完全一样的。

1
2
3
unsigned u = 4294967295u;  //UMax
int tu = (int)u;
printf("u = %u, tv = %d\n", u, tu);

在采用补码的机器上,会产生如下输出

u = 4294967295, tv = -1

对于int32来说,无符号形式的4294967295($UMax_{32}$)和补码形式的-1的位模式是完全一样的。

对于大多数C语言的实现,处理同样字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,但是位模式不变

补码转换为无符号数

通过前面两个公式,可以发现对于位模式$\vec x$,如果计算$B2U_{\omega}(\vec x)-B2T_{\omega}(\vec x)$之差,从0到$\omega-2$的位的加权和将互相抵消掉,剩下一个值:$B2U_{\omega}(\vec x)-B2T_{\omega}(\vec x)=x_{\omega-1}(2^{\omega-1}-(-2^{\omega-1}))=x_{\omega-1}2^{\omega}$。这就得到一个关系$B2U_{\omega}(\vec x)=x_{\omega-1}2^{\omega}+B2T_{\omega}(\vec x)$。因此就有:
$$
B2U_{\omega}(T2B_{\omega}(x))=T2U_{\omega}(x)=x+x_{\omega-1}2^{\omega}
$$
在$x$的补码表示中,位$x_{\omega-1}$决定了$x$是否为负。

无符号数转为补码

对满足$0\leq u\leq UMax_{\omega}$的$u$有:
$$
U2T_{\omega}(u)=\left{\begin{align} u, u\le TMax_{\omega}\u-2^{\omega}, u\ge TMax_{\omega}\end{align}\right.
$$

C语言中的表示

C语言支持所有整形数据类型的有符号无符号运算。C语言标准并没有要求有符号数如何表示,但是几乎所有即使都使用补码

通常大多数数字都会认为是有符号的。例如12345和0x1A2B这样的常量 都认为是有符号的。要创建一个无符号数,需要加上后缀字符’U’或者’u’。

C语言标准并没有要求要用补码表示有符号整数,但是几乎所有机器都是这么做的。在<limits.h>中定义了一组常量,来限定编译器运行的这台机器的不同整形的数据类型的取值范围。比如INT_MAXINT_MIN等。

C语言允许有符号数和无符号数之间的转换。C语言标准也没有规定如何进行转换,大多数系统遵循的原则是底层的位级表示不变。因此,在一台采用补码的机器上,当从无符号数转为有符号数时,效果就是应用函数$U2T_{\omega}$,有符号数转换为无符号数时,就是应用函数$T2U_{\omega}$。

例如如下代码

1
2
3
4
int x = -1;
unsigned u = 2147483648;
printf("x = %u = %d\n", x, x);
printf("u = %u = %d\n", u, u);

运行之后,得到的输入如下

x = 4294967295 = -1
u = 2147483648 = -2147483648

分析:printf函数首先将这个数字当作无符号数输出,然后再当作一个有符号数输出。实际运行中的转换函数是:$T2U_{32}(-1)=UMax_{32}=2^{32}-1$和$U2T_{32}(2^{31})=2^{31}-2^{32}=-2^{31}=TMin_{32}$。

如果C语言执行一个运算时,一个操作数时有符号数另一个是无符号数,那么C语言会隐式的将有符号数转换为无符号数并假设两个数都是非负的。

不同字长的整数转换

无符号数零扩展

要将一个无符号数转换为一个更大的数据类型,我们只需要简单的再表示的开头添加0,这种运算称为零扩展(zero extension)

补码数的符号扩展

将一个补码数字转换为一个更大的数据类型,可以执行一个符号扩展(sign extension),再表示中添加最高有效位的值。

截断数字

当一个大的数据类型转换为一个小的数据类型时,会进行一个截断数组的操作。

如将一个int类型转换为一个short类型,会将这个数字的位模式下的高16位阶段来表示,当从short强制转换为int时,符号扩展把高16位设置为1.