文章目录
前言
本篇博客会介绍整数的最低最高有效位,c语言中无符号数的编码,有符号数的编码,补码的使用等等等等。内容会比较多 😐,请耐心看完哦。
来源:《深入理解计算机系统》
有效位
假设有一个w
位(bit
)的数值,可以表示为
[
x
w
−
1
,
x
w
−
2
,
.
.
.
.
,
x
1
,
x
0
]
[x_{w-1},x_{w-2},....,x_1,x_0]
[xw−1,xw−2,....,x1,x0],规定把
x
w
−
1
x_{w-1}
xw−1称为最高有效位,
x
0
x_{0}
x0称为最低有效位
整数4的二进制表示为100
对4而言,w = 3, 最高有效位为1 最低有效位为0
无符号数编码
下面给出无符号数编码的计算公式,其实就是二进制和十进制的互相转换。
(为了表示方便二进制串用[]括起来) 例如:
[ 1000 ] = 1 ∗ 2 3 + 0 ∗ 2 2 + 0 ∗ 2 1 + 0 ∗ 2 0 = 8 [1000] =1*2^3+0*2^2+0*2^1+0*2^0=8 [1000]=1∗23+0∗22+0∗21+0∗20=8
[ 1010 ] = 1 ∗ 2 3 + 0 ∗ 2 2 + 1 ∗ 2 1 + 0 ∗ 2 0 = 10 [1010] =1*2^3+0*2^2+1*2^1+0*2^0=10 [1010]=1∗23+0∗22+1∗21+0∗20=10
[ 1110 ] = 1 ∗ 2 3 + 1 ∗ 2 2 + 1 ∗ 2 1 + 0 ∗ 2 0 = 14 [1110] =1*2^3+1*2^2+1*2^1+0*2^0=14 [1110]=1∗23+1∗22+1∗21+0∗20=14
无符号数编码的取值范围
长度为w
位的二进制串能表示值的范围为
[
0
,
2
w
−
1
]
[0,2^{w}-1]
[0,2w−1]。最小值用二进制串
[
000...00
]
[000...00]
[000...00]表示,最大值用二进制串
[
111...11
]
[111...11]
[111...11]表示,可以这样计算
∑
i
=
0
w
−
1
2
i
=
2
w
−
1
\sum\limits_{{\rm{i}} = 0}^{{\rm{w - 1}}} {{2^{\rm{i}}} = {2^{\rm{w}}} - 1}
i=0∑w−12i=2w−1
无符号数的二进制表示还有一个特别的属性,就是每个在
[
0
,
2
w
−
1
]
[0,2^w-1]
[0,2w−1]的数都有唯一一个w
位数值编码。给定一个w
位的数值编码,也可以唯一确定一个
[
0
,
2
2
−
1
]
[0,2^2-1]
[0,22−1]的数。 即:将二进制数转为无符号数的函数是双射函数。
补码
上面的无符号数有一个明显的缺点,就是只能表示正数,但是在很多情况下我们希望能够表示负数,于是我们可以使用补码编码。
补码编码
与无符号数编码不同的是,补码编码最高有效位为符号位,权重为 − 2 w − 1 -2^{w-1} −2w−1
- 当符号位为
1
时,其值为 − 2 w − 1 -2^{w-1} −2w−1 - 当符号位为
0
时,其值为0
示例:
[ 0001 ] = − 0 ∗ 2 3 + 0 ∗ 2 2 + 0 ∗ 2 1 + 1 ∗ 2 0 = 1 [0001]=-0*2^3+0*2^2+0*2^1+1*2^0 =1 [0001]=−0∗23+0∗22+0∗21+1∗20=1
[ 1011 ] = − 1 ∗ 2 3 + 0 ∗ 2 2 + 1 ∗ 2 1 + 1 ∗ 2 0 = − 5 [1011]=-1*2^3+0*2^2+1*2^1+1*2^0 =-5 [1011]=−1∗23+0∗22+1∗21+1∗20=−5
补码编码的取值范围
w
位补码的取值范围为
[
−
2
w
−
1
,
2
w
−
1
−
1
]
[-2^{w-1},2^{w-1}-1]
[−2w−1,2w−1−1] ,最小值的二进制串为
[
1000...000
]
[1000...000]
[1000...000](因为只有最高有效位用于表示负,其它位都表示正)。最大值的二进制串为
[
0111...111
]
[0111...111]
[0111...111]
int类型w为32,可以由此推出int的取值范围 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [−231,231−1] ,即 [ − 2147483648 , 2147483547 ] [-2147483648,2147483547] [−2147483648,2147483547]
从补码转换到二进制的函数是一个双射函数。
c语言的有符号数
在大部分机器上,c语言都会使用补码来表示有符号整数。
大多数数字也是默认有符号的,要创建一个无符号常量,必须加上后缀字符U
或者u
如:1234U
0x1234u
补码的使用
对于非负数x
,我们用
2
w
−
x
2^w-x
2w−x做为-x
的补码表示
举个例子:
short x = 12345;
short mx = -x; // mx为-123452 w 2^w 2w的二进制表示为 1 00000000 00000000
x的二进制表示为 00110000 00111001
2 w − x 2^w-x 2w−x的二进制表示为 11001111 11000111
结果为 1+2+4+64+128+256+512+1024+2048+16384-32768 = -12345
有符号数的另外两种表示方法
反码
最高有效位权重为 − ( 2 w − 1 − 1 ) -(2^{w-1}-1) −(2w−1−1),其余和补码一样
原码
最高有效位作为符号位,权重为 − 1 -1 −1,其余和补码一样
有符号数和无符号数之间的转化
先介绍转化的规则
不改变位值,而是改变解释位的方式
分析一下上图的步骤
- 将有符号数转换为对应位值:
-12345
的补码编码为 [ 1100111111000111 ] [11001111 11000111] [1100111111000111] - 将该位值使用无符号数编码,结果为
53191
53191 = 1 + 2 + 4 + 64 + 128 + 256 + 512 + 1024 + 2048 + 16384 + 32768 53191 =1+2+4+64+128+256+512+1024+2048+16384+32768 53191=1+2+4+64+128+256+512+1024+2048+16384+32768
[11001111 11000111]的补码和无符号数编码如下:
-12345=1+2+4+64+128+256+512+1024+2048+16384 -32768
53191 =1+2+4+64+128+256+512+1024+2048+16384 +32768
当补码编码为负数时,设[0 ~ w-2]位的数值为A
补码对应值: A − 2 w − 1 A-2^{w-1} A−2w−1
无符号数对应值: A + 2 w − 1 A+2^{w-1} A+2w−1
相互转换只需 ± 2 w \pm2^w ±2w
补码到无符号数之间的转换公式
根据上面的推导,可以推出公式
补码
x
变成无符号数
=
{
x
+
2
w
,
x
<
0
x
,
x
≥
0
补码x变成无符号数=\left\{ \begin{array}{l}x + {2^w},x < 0\\x,x \ge 0\end{array} \right.
补码x变成无符号数={x+2w,x<0x,x≥0
相应地,可以推出无符号数变成补码的公式
无符号数
u
变成补码
=
−
u
w
−
1
2
w
+
u
无符号数u变成补码 = -u_{w-1}2^w+u
无符号数u变成补码=−uw−12w+u
u
w
−
1
为
u
的最高有效位
u_{w-1}为u的最高有效位
uw−1为u的最高有效位
什么时候会发生符号数和无符号数之间的转化
- 强制类型转换,如下面的代码
int x;
unsigned int ux =(unsigned int)x;
- 将一种类型的变量赋值给另外一种类型的变量时
int x;
unsigned int ux;
ux = x;
- 当有符号无符号数参与运算时,c语言会隐式的将有符号数转化为无符号数
#include <iostream>
int main()
{
if (-1 > 0U)
{
std::cout << "有符号数被转换为无符号数";
}
return 0;
} //结果成功输出,大家可以自行验证一下
c语言中INT_MIN
的写法
在c语言的<limits.h>
头文件里定义了一组常量,INT_MAX
和INT_MIN
确定了有符号整数的取值范围。
观察源码,发现INT_MIN
的定义有些奇怪
#define INT_MAX 2147483637
#define INT_MIN (-INT_MAX-1)
为什么不直接写成-2147483648
呢?原因如下
- 编译器在遇到形如
-x
的表达式时,会先确定x
的数据类型和值 2147483648
( 2 31 2^{31} 231)超出int的范围(int最高位为符号位,int最大值为 2 31 − 1 2^{31}-1 231−1)- 编译器会尝试找到一个数据类型可以正确表示这个值。不同标准的c找到的类型不同。在32位机器上的结果如下。
- 对于
ISO C90
版本,编译器从int
到long
再到unsigned
, 最终找到的数据类型是unsigned
,值为2147483648。 - 对于
ISO C99
版本,编译器从int
到long
再到long long
,它最终找到的数据类型是long long
,值为-2147483648。
为什么将2147483648扩展到long long
类型,值会变成-2147483648呢,在这就要介绍如何扩展数字的位了。
扩展数字位的表示
有时候,我们会将一个数据类型转化到更大的数字类型。
无符号数的扩展
将一个无符号数转化为更大的数据类型,只要在开头加上0就行了,这被称为无符号数的0扩展。
有符号数的扩展
我们希望扩展之后数依然不变
先观察个简单的例子
位 [ 101 ] [101] [101] ,w为3 表示值-4+1=3
位 [ 1101 ] [1101] [1101] ,w为4 表示值-8+4+1=3
位 [ 11101 ] [11101] [11101],w为5 表示值 -16+8+4+1=3
可以得出结论,因为篇幅问题这里就不给出证明了。主要是懒
[
x
w
−
1
,
.
.
.
,
x
w
−
1
⏟
k
次
,
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
]
=
[
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
]
[\underbrace {{x_{w - 1}},...,{x_{w - 1}}}_{k次},{x_{w - 1}},{x_{w - 2}},...,{x_0}] = [{x_{w - 1}},{x_{w - 2}},...,{x_0}]
[k次
xw−1,...,xw−1,xw−1,xw−2,...,x0]=[xw−1,xw−2,...,x0]
这就能解释为什么将2147483648扩展到
long long
类型,值会变成-2147483648
- 2147483648的二进制表达式 [ 10000000 00000000 00000000 00000000 ] [10000000 \quad 00000000\quad 00000000 \quad00000000] [10000000000000000000000000000000]
- 这个数字超出
int
的表示范围了,所以在C99
下被扩展为long long
格式,w
被扩充到64位。- 由于最高有效位是1,所以用1来补齐缺少的位。得到的64位值为 [ 11111111 11111111 11111111 11111111 10000000 00000000 00000000 00000000 ] [11111111\quad11111111\quad11111111\quad11111111\quad10000000\quad 00000000\quad 00000000 \quad 00000000] [1111111111111111111111111111111110000000000000000000000000000000]
- 其值就是-2147483648
本文结束
看完本文的样子 be like :
不会连这篇文章都看不懂吧,真是
然后被暴打
一些废话
花了两天时间把这篇博客整理出来。。。。 虽然大一自学c的时候也有学过,不过当时感觉很难就只没怎么仔细看。现在看来其实没那么难。
题外话:写算法题的时候数据过大会爆int , 所以用long long 应该是个好习惯吧。