整数表示

146 阅读10分钟

一、整型数据类型

C 语言支持多种整型数据类型——表示有限范围的整数。

  • 每种类型都能用关键字来指定大小,这些关键字包括 charshortlong ,同时还可以指示被表示的数字是非负数(声明为 unsigned ),或者可能是负数(默认)。
  • 以下为 64 程序上 C 语言整型数据类型的典型取值范围:
C 数据类型最小值最大值
[signed] char-128127
unsigned char0255
short-32 76832 767
unsigned short065 535
int-2 147 483 6482 147 483 647
unsigned04 294 967 295
long-9 223 372 036 854 775 8089 223 372 036 854 775 807
unsigned long018 446 744 073 709 551 615
int32_t-2 147 483 6482 147 483 647
uint32_t04 294 967 295
int64_t-9 223 372 036 854 775 8089 223 372 036 854 775 807
uint64_t018 446 744 073 709 551 615

二、无符号数的编码

  • 假设有一个整数数据类型有 ww 位。我们可以将位向量写成 x\vec{x} ,表示整个向量,或者写成 [ xw1x_{w-1} , xw2x_{w-2} , ... , x0x_{0} ],表示向量中的每一位。把 x\vec{x} 看做一个二进制表示的数,就获得了x\vec{x} 的无符号表示。

  • 在这个编码中,每个位 xix_{i} 都取值为 0 或 1,后一种取值意味着数值 2i2^{i} 应为数字值的一部分。我们用一个函数 B2UwB2U_{w}Binary to Unsigned 的缩写,长度为 ww )来表示:

  • 原理无符号数编码的定义

  • 对向量 x\vec{x}=[ xw1x_{w-1} , xw2x_{w-2} , ... , x0x_{0} ] :

    B2Uw(x)=˙i=0w1xi2iB2U_{w}(\vec{x}) \dot{=} \sum_{i=0}^{w-1}x_{i}2^{i}

  • 在这个等式中,符号 “=˙\dot{=}” 表示左边被定义为等于右边。函数 B2UwB2U_{w} ,将一个长度为 ww 的 0、1 串映射到非负整数。

  • 举一个示例,下面几种情况下 B2UB2U 给出的从位向量到整数的映射:

    • B2U4([0001])=023+022+021+120=0+0+0+1=1B2U_{4}([0001]) = 0 \cdot 2^{3} + 0 \cdot 2^{2} + 0 \cdot 2^{1} + 1 \cdot 2^{0} = 0 + 0 + 0 + 1 = 1
    • B2U4([0101])=023+122+021+120=0+4+0+1=5B2U_{4}([0101]) = 0 \cdot 2^{3} + 1 \cdot 2^{2} + 0 \cdot 2^{1} + 1 \cdot 2^{0} = 0 + 4 + 0 + 1 = 5
    • B2U4([1011])=123+022+121+120=8+0+2+1=11B2U_{4}([1011]) = 1 \cdot 2^{3} + 0 \cdot 2^{2} + 1 \cdot 2^{1} + 1 \cdot 2^{0} = 8 + 0 + 2 + 1 = 11
    • B2U4([1111])=123+122+121+120=8+4+2+1=15B2U_{4}([1111]) = 1 \cdot 2^{3} + 1 \cdot 2^{2} + 1 \cdot 2^{1} + 1 \cdot 2^{0} = 8 + 4 + 2 + 1 = 15
  • ww=4 的无符号数示例,当二进制表示中位 ii 为 1,数值就会相应加上 2i2^{i}

  • 让我们来考虑一下 ww 位所能表示的值的范围:

    • 最小值是用位向量 [000][00…0] 表示,也就是整数值0。
    • 最大值是用位向量 [111][11…1] 表示,也就是整数值 UMaxw=˙limitsi=0w12i=2w1UMax_{w} \dot{=} \sum_limits{i=0}^{w-1}{2^i} = 2^{w}-1 ,以 4 位数情况为例,UMax4=B2U4([1111])=241=15UMax_{4} = B2U_{4}([1111]) = 2^{4}-1 = 15 。因此,函数 B2UwB2U_{w} 能够被定义为一个映射 B2U:{0,1}w{0,...,2w1}B2U:\{0,1\}^{w} \rightarrow \{0, ...,2^{w}-1\}
  • 无符号数的二进制表示有一个很重要的属性,也就是每个介于 02w10 \sim 2^{w}-1 之间的数都有唯一一个 ww 位的值编码。例如,十进制值 11 作为无符号数,只有一个 4 位的表示,即 [1011][1011]

  • 我们用数学原理来重点讲述它,先表述原理再解释。

  • 原理:无符号数编码的唯一性

  • 函数 B2UwB2U_{w} 是一个双射。

  • 数学术语 双射 是指一个函数 ff 有两面:它将数值 xx 映射为数值 yy ,即 y=f(x)y=f(x),但它也可以反向操作,因为对每一个 yy 而言,都有唯一一个数值 xx 使得 f(x)=yf(x)=y 。这可以用 反函数 f1f^{-1} 来表示,在本例中,即 x=f1(y)x = f^{-1}(y)。函数 B2UwB2U_{w} ,将每一个长度为 ww 的位向量都映射为 02w10 \sim 2^{w}-1 之间的一个唯一值;反过来,我们称其为 U2BwU2B_{w} ,(即“无符号数到二进制”),在 02w10 \sim 2^{w}-1 之间的每一个整数都可以映射为一个唯一的长度为 ww 的位模式。

三、补码编码

对于许多应用,我们还希望表示负数值。

  • 最常见的有符号数的计算机表示方式就是补码two ' s - complement )形式。在这个定义中,将字的最高有效位解释为负权negative weight )。我们用函数 B2TwB2T_{w} 。( Binary to Two ' s - complement 的缩写,长度为 ww )来表示:
  • 原理:补码编码的定义
  • 对向量 x\vec{x}=[ xw1x_{w-1} , xw2x_{w-2} , ... , x0x_{0} ] :

B2Tw(x)=˙xw12w1+limitsi=0w2xi2iB2T_{w}(\vec{x}) \dot{=} -x_{w-1}{2^{w-1}} + \sum_limits{i=0}^{w-2}x_{i}2^{i}

  • 最高有效位 xw1x_{w-1} 也称为符号位,它的“权重“为 2w1-2_{w-1},是无符号表示中权重的负数。符号位被设置为 1 时,表示值为负,而当设置为 0 时,值为非负。

  • 这里来看一个示例,下面几种情况下 B2TB2T 给出的从位向量到整数的映射:

    • B2T4([0001])=023+022+021+120=0+0+0+1=1B2T_{4}([0001]) = - 0 \cdot 2^{3} + 0 \cdot 2^{2} + 0 \cdot 2^{1} + 1 \cdot 2^{0} = 0 + 0 + 0 + 1 = 1
    • B2T4([0101])=023+122+021+120=0+4+0+1=5B2T_{4}([0101]) = -0 \cdot 2^{3} + 1 \cdot 2^{2} + 0 \cdot 2^{1} + 1 \cdot 2^{0} = 0 + 4 + 0 + 1 = 5
    • B2T4([1011])=123+022+121+120=8+0+2+1=5B2T_{4}([1011]) = -1 \cdot 2^{3} + 0 \cdot 2^{2} + 1 \cdot 2^{1} + 1 \cdot 2^{0} = -8 + 0 + 2 + 1 = -5
    • B2T4([1111])=123+122+121+120=8+4+2+1=1B2T_{4}([1111]) = -1 \cdot 2^{3} + 1 \cdot 2^{2} + 1 \cdot 2^{1} + 1 \cdot 2^{0} = -8 + 4 + 2 + 1 = -1
  • ww=4 的补码示例,把位 3 作为符号位,因此当它为 1 时,对数值的影响是 23=8-2^{3}=-8 。(这个权重在图中用带向左箭头的条表示):

  • 让我们来考虑一下 ww 位补码所能表示的值的范围。它能表示的最小值是位向量 [10...0][10...0](也就是设置这个位为负权,但是清除其他所有的位),其整数值为 TMinw=˙2w1TMin_{w} \dot{=} -2^{w-1} 。而最大值是位向量 [01...1][01...1](清除具有负权的位,而设置其他所有的位),其整数值为 TMax_{w} \dot{=} \sum_\limits{i=0}^{w-2}2^{i}=2^{w-1}-1 。以长度为 4 为例,我们有 TMin4=B2T4([1000])=23=8TMin_{4}=B2T_{4}([1000])=-2^{3}=-8 ,而 TMax4=B2T4([0111])=22+21+20=4+2+1=7TMax_{4}=B2T_{4}([0111])=2^{2}+ 2^{1}+2^{0}=4+2+1=7

  • 我们可以看出 B2TwB2T_{w} 是一个从长度为 w{w} 的位模式到 TMinwTMin_{w} ,和 TMaxwTMax_{w} 之间数字的映射,写作 B2TwB2T_{w}{0,1}w{Tminw,...,TMaxw}\{0,1\}^{w} \to \{Tmin_{w},...,TMax_{w}\}。同无符号表示一样,在可表示的取值范围内的每个数字都有一个唯一的 w{w} 位的补码编码。这就导出了与无符号数相似的补码数原理:

    • 原理:补码编码的唯一性
    • 函数 B2TwB2T_{w} 是一个 双射
  • 我们定义函数 T2BwT2B_{w} (即“补码到二进制”)作为 B2TwB2T_{w} 的反函数。也就是说,对于每个数 x{x} ,满足 TMinwxTMaxwTMin_{w} \leq x \leq TMax_{w} ,则 T2Bw(x)T2B_{w}(x)x{x} 的(唯一的)w{w} 位模式。

  • 下表展示了不同字长,几个重要数字的位模式和数值(前三个给出的是可表示的整数的范围):

字长 ww
8163264
UMaxwUMax_{w}0xFF
255
0×FFFF
65 535
0xFFFFFFFF
4 294 967 295
0xFFFFFFFFFFFFFFFF
18 446 744 073 709 551 615
TMinwTMin_{w}0x80
-128
0x8000
-32 768
0x80000000
-2 147 483 648
0x8000000000000000
-9223 372 036 854775 808
TMaxwTMax_{w}0x7F
127
0x7FFF
32 767
0x7FFFFFFF
2 147 483 647
0x7FFFFFFFFFFFFFFF
9 223 372 036 854 775 807
-1
0
0xFF
0×00
0xFFFF
0×0000
0xFFFFFFFF
0x00000000
0xFFFFFFFFFFFFFFFF
0x0000000000000000
  • 关于这些数字,有几点值得注意:

    • 第一,补码的范围是不对称的TMin=TMax+1|{TMin}|=|{TMax}|+1,也就是说, TMinTMin 没有与之对应的正数。正如我们将会看到的,这导致了补码运算的某些特殊的属性,并且容易造成程序中细微的错误。之所以会有这样的不对称性,是因为一半的位模式(符号位设置为 1 的数)表示负数,而另一半(符号位设置为 0 的数)表示非负数。因为 0 是非负数,也就意味着能表示的整数比负数少一个
    • 第二,最大的无符号数值刚好比补码的最大值的两倍大一点UMaxw=2TMaxw1UMax_{w}=2TMax_{w}十1 。 补码表示中所有表示负数的位模式在无符号表示中都变成了正数。上表也给出了常数 -1 和 0 的表示。注意 -1 和 UMaxUMax 有同样的位表示 —— 一个全 1 的串。数值 0 在两种表示方式中都是全 0 的串。
    • 如果希望代码具有最大可移植性,能够在所有可能的机器上运行,那么除了所示的那些范围之外,我们不应该假设任何可表示的数值范围,也不应该假设有符号数会使用何种特殊的表示方式。
    • 关于整数数据类型的取值范围和表示, Java 标准是非常明确的。它要求采用补码表示。举个例子,在 Java 中,单字节数据类型称为 byte ,而不是 char 。这些非常具体的要求都是为了保证无论在什么机器上运行, Java 程序都能表现地完全一样

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

C 语言允许在各种不同的数字数据类型之间做强制类型转换。

  • 示例代码
short intv = -12345;
unsigned short uv = (unsigned short)v;
printf("v = %d, uv = %u\n", v, uv);
  • 在一台采用补码的机器上,上述代码会产生如下输出:
ν=-12345, uν = 53191
  • 对于大多数 C 语言的实现,处理同样字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,但是位模式不变

  • 我们考虑无符号与补码表示之间互相转换的结果:

  • 对于在范围 0xTMaxw0 \leq x \leq TMax_{w} 之内的值 xx 而言,我们得到 T2Uw(x)=xT2U_{w}(x)=xU2Tw(x)=xU2T_{w}(x)=x。也就是说,在这个范围内的数字有相同的无符号和补码表示。

  • 对于这个范围以外的数值,转换需要加上或者减去 2w2^{w}。例如,我们有 T2Uw(1)=1+2w=UMaxwT2U_{w}(-1)=-1+2^{w}=UMax_{w} —— 最靠近 0 的负数映射为最大的无符号数。

  • 在另一个极端,我们可以看到 T2Uw(TMinw)=2w1+2w=2w1=TMaxw+1T2U_{w}(TMin_{w})=-2^{w-1}+2^{w}=2^{w-1}=TMax_{w}+1 —— 最小的负数映射为一个刚好在补码的正数范围之外的无符号数。

  • 根据下图所示,我们能看到 T2U16(12345)=65563+(12345)=53191T2U_{16}(-12 345)=65 563+(-12 345)=53 191

  • 12 345 和 -12 345 的补码表示,以及 53 191 的无符号表示。注意后面两个数有相同的位表示。

五、扩展一个数字的位表示

一个常见的运算是在不同字长的整数之间转换,同时又保持数值不变。当然,当目标数据类型太小以至于不能表示想要的值时,这根本就是不可能的。然而,从一个较小的数据类型转换到一个较大的类型,应该总是可能的。

  • 要将一个无符号数转换为一个更大的数据类型,我们只要简单地在表示的开头添加 0 这种运算被称为零扩展zero extension ),表示原理如下:

  • 原理:无符号数的零扩展

  • 定义宽度为 ww 的位向量 u=[uw1,uw2,...,u0]\vec{u} = [ u_{w-1} , u_{w-2} , ... , u_{0} ] 和宽度为 ww\prime 的位向量 u=[0,...,0,uw1,uw2,...,u0]\vec{u}\prime = [ \color{SkyBlue}0 , \color{Black}... , \color{SkyBlue}0 , \color{Black}u_{w-1} , u_{w-2} , ... , u_{0} ] ,其中 w>ww\prime > w。则 B2Uw(u)=B2Uw(u)B2U_{w}(\vec{u})=B2U_{w\prime}(\vec{u\prime})

  • 要将一个补码数字转换为一个更大的数据类型,可以执行一个符号扩展sign extension ),在表示中添加最高有效位的值,表示为如下原理。我们用蓝色标出符号位 xw1\color{SkyBlue}x_{w-{1}} 来突出它在符号扩展中的角色。

  • 原理:补码数的符号扩展

  • 定义宽度为 ww 的位向量 x=[xw1,xw2,...,x0]\vec{x} = [ \color{SkyBlue}x_{w-1} , \color{Black}x_{w-2} , ... , x_{0} ] 和宽度为 ww 的位向量 x=[xw1,...,xw1,xw1,xw2,x0]\vec{x}\prime = [ \color{SkyBlue}x_{w-1} , \color{Black}... , \color{SkyBlue}x_{w-1} , \color{SkyBlue}x_{w-1} , \color{Black}x_{w-2} ,x_{0} ] ,其中 w>ww\prime > w。则 B2Tw(x)=B2Tw(x)B2T_{w}(\vec{x})=B2T_{w\prime}(\vec{x\prime})

六、截断数字

  • 假设我们不用额外的位来扩展一个数值,而是减少表示一个数字的位数。例如下面代码中这种情况:
int x = 53191;
short sx= (short) x; /* -12345 */
int y = sx;			/* -12345 */
  • 当我们把 × 强制类型转换为 short 时,我们就将 32 位的 int 截断为了 16 位的 short int

  • 就像前面所看到的,这个 16 位的位模式就是 -12345 的补码表示。当我们把它强制类型转换回 int 时,符号扩展把高 16 位设置为 1,从而生成 -12345 的 32 位补码表示。

  • 当将一个 ww 位的数 x=[xw1,xw2,...,x0]\vec{x}=[x_{w-1} , x_{w-2} , ... , x_{0}] 截断为一个 kk 位数字时,我们会丢弃高 wkw-k 位,得到一个位向量 x=[xk1,xk2,...,x0]\vec{x}\prime=[ x_{k-1} , x_{k-2} , ... , x_{0} ] 。截断一个数字可能会改变它的值 —— 溢出的一种形式。对于一个无符号数,我们可以很容易得出其数值结果。

  • 原理:截断无符号数

  • x\vec{x} 等于位向量 [xw1,xw2,...,x0][x_{w-1} , x_{w-2} , ... , x_{0}] ,而 x\vec{x}\prime 是将其截断为 kk 位的结果: x=[xk1,xk2,...,x0]\vec{x}\prime=[x_{k-1} , x_{k-2} , ... , x_{0}] 。令 x=B2Uw(x)x=B2U_{w}(\vec{x}) , x=B2Uk(x)x\prime=B2U_{k}(\vec{x}\prime) 。则 x=xmod2k\vec{x}\prime=x \bmod 2^{k} 。该原理背后的直觉就是所有被截去的位其权重形式都为 2i2^{i} ,其中 iki \geq k,因此,每一个权在取模操作下结果都为零。

  • 原理:截断补码数值

  • x\vec{x} 等于位向量 [xw1,xw2,...,x0][x_{w-1} , x_{w-2} , ... , x_{0}] ,而 x\vec{x}\prime 是将其截断为 kk 位的结果: x=[xk1,xk2,...,x0]\vec{x}\prime=[x_{k-1} , x_{k-2} , ... , x_{0}]

  • x=B2Uw(x)x=B2U_{w}(\vec{x}) , x=B2Tk(x)x\prime=B2T_{k}(\vec{x}\prime) 。则 x=U2Tk(xmod2k)x\prime=U2T_{k}(x \bmod 2^{k})

  • 在这个公式中,xmod2kx \bmod 2^{k} 将是 0 到 2k12^{k}-1 之间的一个数。对其应用函数 U2TkU2T_{k} ,产生的效果是把最高有效位 xk1x_{k-1} 的权重从 2k12^{k-1} 转变为 2k1-2^{k-1}

  • 举例来看,将数值 x=53191x=53 191int 转换为 short 。由于 216=65536x2^{16}=65 536 \geq x ,我们有 xmod216=xx \bmod 2^{16}=x 。但是,当我们把这个数转换为 16 位的补码时,我们得到 x=5319165536=12345\vec{x}\prime=53 191-65 536=-12 345

六、结束语

“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶