C语言— —进制

本文主要介绍进制、进制转换、原码反码补码的知识。

文章目录

1. 进制

本章主要介绍什么是进制。现有数字 6179,从右到左分别为个位、十位、百位、千位,这是我们小学的知识了(所以不用害怕知识有多难,我们小学就接触了)。

C语言— —进制

6179 就是一个十进制数,而我们在平时的生活中接触到的也是十进制数。现在我们来说说十进制数的特点:

  • 从右到左,分别为个位、十位、百位、千位、万位…;
  • 每一位上的数字可以为0-9之间的任意一个数字;
  • 逢十进一;

在编程世界中,最基础的是二进制,那么什么是二进制呢?类比十进制,二进制有如下特点:

  • 我们也可以将二进制的数从右到左分为每一位;
  • 每一位上的数字要么是0,要么是1;
  • 逢二进一;

例如,二进制下的数字 10010。

除了二进制,我们还常用八进制、十六进制。再次总结一下进制的知识:

  • 对于n进制,每一位上的数字可以为[0,n-1]
  • 对于n进制,低位的数字逢n进一。

以十六进制为例,每一位上的数字可以为[0,15],例如:

C语言— —进制

可是,这种表示方法有歧义,我们用一个方框表示一位,可是在实际使用中是没有这个方框的,那么就会显示如下图,可以有两种以上的理解:

C语言— —进制

这就造成了歧义,原因是[10,15]的数字我们用了两位来表示,为了消除歧义,我们用A-F分别表示10-15:

C语言— —进制

所以十六进制的每一位上的数字可以为[0,15],但是为了消除歧义,10-15分别用A-F来表示。

2. 进制转换

本节主要介绍同一个整数不同进制之间的转换。对于小数的进制转换,感兴趣的同学可以自行查阅资料。我们以角标 X ( n ) X_{(n)} X(n)​表示X是n进制数。

2.1 十进制转换为其它进制

我们分别以二进制、八进制为例,介绍十进制是如何转换为其它进制的。

其实十进制转换为其他进制所采用的方法是一样的,假设十进制转换为n进制,那么该方法可以称为“除n取余,逆序排列”。更准确地描述为,现有一个十进制数x,将其转换为n进制数,则转换方法为:

  • 首先计算 x ÷ n = p … q,得到商为p,余数为 q;
  • 再根据上一步得到的商 p 继续同样的步骤 p ÷ n = p 1 . . . q 1 p_1 ... q_1 p1​...q1​,得到商为 p 1 p_1 p1​,余数为 q 1 q_1 q1​;
  • 直到商为0;
  • 最后将余数逆序从左至右排列 p n p n − 1 . . . p 1 p p_np_{n-1}...p_1p pn​pn−1​...p1​p,则 p n p n − 1 . . . p 1 p p_np_{n-1}...p_1p pn​pn−1​...p1​p就是转换后的n进制数;

以十进制数 10 0 ( 10 ) 100_{(10)} 100(10)​转换为二进制数为例:
第 一 步 : 100 ÷ 2 = 50...0 第 二 步 : 50 ÷ 2 = 25...0 第 三 步 : 25 ÷ 2 = 12...1 第 四 步 : 12 ÷ 2 = 6...0 第 五 步 : 6 ÷ 2 = 3...0 第 六 步 : 3 ÷ 2 = 1...1 第 七 步 : 1 ÷ 2 = 0...1 第一步: 100 \div 2 = 50 ... 0 \\ 第二步: 50 \div 2 = 25 ... 0 \\ 第三步: 25 \div 2 = 12 ... 1 \\ 第四步: 12 \div 2 = 6 ... 0 \\ 第五步: 6 \div 2 = 3 ... 0 \\ 第六步: 3 \div 2 = 1 ... 1 \\ 第七步: 1 \div 2 = 0 ... 1 \\ 第一步:100÷2=50...0第二步:50÷2=25...0第三步:25÷2=12...1第四步:12÷2=6...0第五步:6÷2=3...0第六步:3÷2=1...1第七步:1÷2=0...1
最后将余数逆序排列,得到 10 0 ( 10 ) 100_{(10)} 100(10)​的二进制形式为 110010 0 ( 2 ) 1100100_{(2)} 1100100(2)​。

在实际的转换中,我们可以按照如下的写法进行简化:

C语言— —进制

同理,十进制数 10 0 ( 10 ) 100_{(10)} 100(10)​转换为八进制数的步骤如下:

C语言— —进制

即十进制数 10 0 ( 10 ) 100_{(10)} 100(10)​转换为八进制数为 14 4 ( 8 ) 144_{(8)} 144(8)​。

2.2 其它进制转换为十进制

现有一个n进制数 p x − 1 . . . p 1 p 0 p_{x-1}...p_1p_0 px−1​...p1​p0​,共有x位,则将其转换位十进制数的公式为:
C语言— —进制

例如,现有二进制数 1110110 0 ( 2 ) 11101100_{(2)} 11101100(2)​,转换为十进制数如下:

C语言— —进制

2.3 二进制、八进制、十六进制之间的转换

由于二进制、八进制和十六进制之间存在特殊的对应关系,所以二进制和八进制、十六进制之间的互相转换存在简便方法。

首先,我们先确定三位二进制数的对应关系:

000 001 010 011 100 101 110 111
0 1 2 3 4 5 6 7

三位二进制数所对应的十进制数为0—7,这就是八进制中的八个数字。

二进制数转换为八进制数的方法如下:对于二进制数,从右到左,以三位二进制数为一组,若最后一组不足三位,则高位补零。然后依次将每组的二进制数按照对应关系转换为八进制数,得到的结果就是八进制数。

例如,现有二进制数 1100110101001 0 ( 2 ) 11001101010010_{(2)} 11001101010010(2)​,将其转换为八进制数的步骤如下:

首先从右到左以三位二进制数为一组:
11 001 101 010 010 11 \quad 001 \quad 101 \quad 010 \quad 010 11001101010010
可以看到最左边的一组(最后一组)不足三位,则最高位补零:
011 001 101 010 010 011 \quad 001 \quad 101 \quad 010 \quad 010 011001101010010
然后按照对应关系,将每组转换为八进制数:
011 001 101 010 010 = 3 1 5 2 2 = 3152 2 ( 8 ) 011 \quad 001 \quad 101 \quad 010 \quad 010 = 3\quad 1\quad 5 \quad 2\quad 2 = 31522_{(8)} 011001101010010=31522=31522(8)​
即二进制数 1100110101001 0 ( 2 ) 11001101010010_{(2)} 11001101010010(2)​转换为八进制数为 3152 2 ( 8 ) 31522_{(8)} 31522(8)​。

同理,二进制数转换为十六进制数,就是四位二进制数与十六进制数的对应关系:

0000 0001 0010 0011 0100 0101 0110 0111
0 1 2 3 4 5 6 7
1000 1001 1010 1011 1100 1101 1110 1111
8 9 A B C D E F

转换方法类似:对于二进制数,从右到左,以四位二进制数为一组,若最后一组不足四位,则高位补零。然后依次将每组的二进制数按照对应关系转换为十六进制数,得到的结果就是十六进制数。

例如,现有二进制数 1110111101001 0 ( 2 ) 11101111010010_{(2)} 11101111010010(2)​,将其转换为十六进制数的步骤如下:

首先从右到左以四位二进制数为一组:
11 1011 1101 0010 11 \quad 1011 \quad 1101 \quad 0010 11101111010010
可以看到最左边的一组(最后一组)不足四位,则最高位补零:
0011 1011 1101 0010 0011 \quad 1011 \quad 1101 \quad 0010 0011101111010010
然后按照对应关系,将每组转换为十六进制数:
0011 1011 1101 0010 = 3 B D 2 = 3 B D 2 ( 16 ) 0011 \quad 1011 \quad 1101 \quad 0010 = 3 \quad B \quad D \quad2 = 3BD2_{(16)} 0011101111010010=3BD2=3BD2(16)​
即二进制数 1110111101001 0 ( 2 ) 11101111010010_{(2)} 11101111010010(2)​转换为十六进制数为 3 B D 2 ( 16 ) 3BD2_{(16)} 3BD2(16)​。

3. 二进制中的存储单位

对于二进制中的一位,我们称之为比特位(bit),即一位=1bit,在此基础上,有如下单位:

  • 1字节=8位,即1byte=8bit,缩写为1B = 8b
  • 1千字节 = 1024字节,即 1 K B = 2 10 B 1KB = 2^{10}B 1KB=210B
  • 1兆字节 = 1024千字节,即 1 M B = 2 20 B 1 MB = 2^{20}B 1MB=220B
  • 1吉字节 = 1024兆字节,即 1 G B = 2 30 B 1GB = 2^{30}B 1GB=230B
  • 1太字节 = 1024吉字节,即 1 T B = 1024 G B = 2 40 B 1 TB = 1024GB = 2^{40}B 1TB=1024GB=240B

还有更高的单位,但是平时也涉及不到,因此此处只罗列常见的。

4. 原码、反码、补码

4.1 原码、反码和补码

本文介绍整数的几种编码方式。

**原码:**原码将数字的二进制分为两部分,符号位和数字的绝对值二进制表示;对于正数来说,符号位为0;对于负数来说,符号位为1。例如,用8位二进制位来存储整数,10和-10的原码表示如下:

10:  0000 1010
-10: 1000 1010

反码:对于正数来说,反码就是原码;对于负数来说,反码就是在原码的基础上,符号位不变,其余位反转。例如,10和-10的反码如下:

10:  0000 1010
-10: 1111 0101

补码:对于正数来说,补码就是原码;对于负数来说,补码就是在反码的基础上,加1。例如,10和-10的补码如下:

10:  0000 1010
-10: 1111 0110

在C语言中,存储整数都是按照补码的形式来存储的。

从十进制整数到二进制补码的转换我们已经了解了,那么如何从补码快速得到十进制整数呢?
对 于 一 个 n 位 的 二 进 制 补 码 { x n − 1 , x n − 2 . . . x 1 , x 0 } 来 说 , 将 其 转 换 为 对 应 的 十 进 制 整 数 方 法 如 下 : { x n − 1 , x n − 2 . . . x 1 , x 0 } = − x n − 1 ∗ 2 n − 1 + ∑ k = 0 n − 2 x k 2 k 对于一个n位的二进制补码\{x_{n-1}, x_{n-2} ... x_1, x_0\}来说,将其转换为对应的十进制整数方法如下:\\ \{x_{n-1}, x_{n-2} ... x_1, x_0\} = -x_{n-1}*2^{n-1} + \sum_{k = 0}^{n-2}x^{k}2^k 对于一个n位的二进制补码{xn−1​,xn−2​...x1​,x0​}来说,将其转换为对应的十进制整数方法如下:{xn−1​,xn−2​...x1​,x0​}=−xn−1​∗2n−1+k=0∑n−2​xk2k

4.2 为什么用补码存储整数?

首先我们来看原码的不足之处:

  • 原码计算涉及负数的减法存在问题。例如,计算 1 - 1,1 - 1 也可以看成 1 + ( -1 ) ,则转换为原码(以8位二进制数来存储整数)为:

    1 + (-1) = 00000001 + 10000001 = 10000010 = -2
    

    这样的计算结果显然是不对的。

  • 在原码表示中,0有两种表示方式。

    00000000: +0
    10000000: -0
    

    在原码表示中,0分为了+0和-0,这显然是不符合我们的习惯的,0是不分正负的。

再来看看反码有何不足之处:

  • 反码对于两个负数的加法计算存在问题。例如,计算 - 1 + (-1):

    -1 + (-1) = 11111110 + 11111110 = 1 00000000 = 00000000 = 0
    

    由于只用8位二进制位来存储数据,所以最高位的1省去。最后结果为0,结果显然不对。

  • 在反码表示中,0仍然有两种表示方式。

    00000000: +0
    11111111: -0
    

为了解决原码和反码存在的问题,提出了补码。

参考资料

[1] 进制:https://zh.wikipedia.org/wiki/%E8%BF%9B%E4%BD%8D%E5%88%B6

上一篇:五、最优性理论


下一篇:线性同余方程