目录
1 进制
计算机世界中只有二进制,所以计算机中存储和运算的所有数据都要转为二进制。包括数字、字符、图片、声音、视频等。
1.1 常见的进制
二进制:0、1,满 2 进 1。
八进制:0 - 7,满八进一。
十进制:0 - 9,满 10 进 1。
十六进制:0 - 9 及 A-F(或 a-f),满 16 进 1。十六进制中,除了 0 到 9 十个数字外,还引入了字母,以便表示超过 9 的值。字母 A 对应十进制的 10,字母 B 对应十进制的 11,字母 C、D、E、F 分别对应十进制的 12、13、14、15。
二进制 | 八进制 | 十进制 | 十六进制 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 |
10 | 2 | 2 | 2 |
11 | 3 | 3 | 3 |
100 | 4 | 4 | 4 |
101 | 5 | 5 | 5 |
110 | 6 | 6 | 6 |
111 | 7 | 7 | 7 |
1000 | 10 | 8 | 8 |
1001 | 11 | 9 | 9 |
1010 | 12 | 10 | A |
1011 | 13 | 11 | B |
1100 | 14 | 12 | C |
1101 | 15 | 13 | D |
1110 | 16 | 14 | E |
1111 | 17 | 15 | F |
10000 | 20 | 16 | 10 |
10001 | 21 | 17 | 11 |
1.2 不同进制的整数在 C 语言中的写法
C 语言中可以使用不同进制表示来整数,规则如下:
二进制:以 0b 或 0B 开头表示。
八进制:以数字 0 开头表示。
十进制:正常数字表示。
十六进制:以 0x 或 0X 开头表示,此处的 A-F 不区分大小写。
#include <stdio.h>
int main()
{
int num1 = 0b1010101; // 二进制(0b 前缀),表示 85(十进制)
int num2 = 0B1010101; // 二进制(0B 前缀),表示 85(十进制)
int num3 = 0127; // 八进制(数字 0 前缀),表示 87(十进制)
int num4 = 210; // 十进制,直接表示 210(十进制)
int num5 = 0x1f; // 十六进制(0x 前缀),表示 31(十进制)
int num6 = 0X1f; // 十六进制(0X 前缀),表示 31(十进制)
// 以 %d 十进制形式输出
printf("num1=%d \n", num1); // 85
printf("num2=%d \n", num2); // 85
printf("num3=%d \n", num3); // 87
printf("num4=%d \n", num4); // 210
printf("num5=%d \n", num5); // 31
printf("num6=%d \n", num6); // 31
return 0;
}
1.3 printf 以不同进制形式输出整数
不同的进制只是整数的书写方法不同,不会对整数的实际存储方式产生影响。不同进制可以混合使用,比如 10 + 015 + 0x20 是一个合法的表达式。
使用格式占位符可以将整数以不同进制形式输出,相关的格式占位符如下:
%d :十进制整数。
%o :八进制整数。
%x 或 %X:十六进制整数。
显示前缀的格式占位符:
%#o :显示前缀 0 的八进制整数。
%#x :显示前缀 0x 的十六进制整数。
%#X :显示前缀 0X 的十六进制整数。
注意:二进制没有专门的格式占位符。
#include <stdio.h>
int main()
{
int num1 = 0b1010101; // 二进制(0b 前缀),表示 85(十进制)
int num2 = 0B1010101; // 二进制(0B 前缀),表示 85(十进制)
int num3 = 0127; // 八进制(数字 0 前缀),表示 87(十进制)
int num4 = 210; // 十进制,直接表示 210(十进制)
int num5 = 0x1f; // 十六进制(0x 前缀),表示 31(十进制)
int num6 = 0X1f; // 十六进制(0X 前缀),表示 31(十进制)
// 二进制没有专门的格式占位符
printf("num1=%d \n", num1); // 85
printf("num2=%d \n", num2); // 85
// 输出八进制
printf("num3=%o \n", num3); // 127
// 输出含前缀 0 的八进制
printf("num3=%#o \n", num3); // 0127
// 输出十进制
printf("num4=%d \n", num4); // 210
// 输出十六进制
printf("num5=%x \n", num5); // 1f
printf("num6=%X \n", num6); // 1F
// 输出含前缀 0x 的十六进制
printf("num5=%#x \n", num5); // 0x1f
// 输出含前缀 0X 的十六进制
printf("num6=%#X \n", num6); // 0X1F
return 0;
}
1.4 案例:使用不同的进制形式输出同一整数
#include <stdio.h>
int main()
{
int x = 100; // 定义变量 x 并初始化为 100
// 打印 x 的十进制表示
printf("十进制:%d \n", x); // 100
// 打印 x 的八进制表示,不包含 0 前缀
printf("八进制:%o \n", x); // 144
// 打印 x 的十六进制表示,不包含 0x 前缀
printf("十六进制:%x \n", x); // 64
// 打印 x 的八进制表示,包含 0 前缀
printf("八进制(0开头):%#o \n", x); // 0144
// 打印 x 的十六进制表示,包含 0x 前缀(小写)
printf("十六进制(0x开头):%#x \n", x); // 0x64
// 打印 x 的十六进制表示,包含 0X 前缀(大写)
printf("十六进制(0X开头):%#X \n", x); // 0X64
return 0;
}
2 进制的转换
2.1 二进制与十进制转换
2.1.1 二进制转换成十进制
规则:从最低位开始,将每个位上的数提取出来,乘以 2 的权(位数 - 1)次方,然后求和。
案例:请将二进制 1011 转成十进制的数。
2.1.2 十进制转换成二进制
规则:【除基取余,逆序排序】将该数不断除以 2,直到商为 0 为止,然后将每步得到的余数倒过来,就是对应的二进制。
案例:请将 56 转成二进制。
2.2 八进制和十进制转换
2.2.1 八进制转换成十进制
规则:从最低位开始,将每个位上的数提取出来,乘以 8 的权(位数 - 1)次方,然后求和。
案例:请将二进制 101 转成十进制的数。
2.2.2 十进制转换成八进制
规则:【除基取余,逆序排序】将该数不断除以 8,直到商为 0 为止,然后将每步得到的余数倒过来,就是对应的二进制。
案例:请将 156 转成八进制。
2.3 十六进制和十进制转换
2.3.1 十六进制转换成十进制
规则:从最低位开始,将每个位上的数提取出来,乘以16 的权(位数 - 1)次方,然后求和。
案例:请将 0x34A 转成十进制的数。
2.3.2 十进制转换成十六进制
规则:【除基取余,逆序排序】将该数不断除以 16,直到商为 0 为止,然后将每步得到的余数倒过来,就是对应的十六进制。
案例:请将 356 转成十六进制。
2.4 二进制和八进制转换
2.4.1 二进制转换成八进制
规则:【取三合一法】低位开始,将二进制数每三位一组,转成对应的八进制数即可。
2.4.2 八进制转换成二进制
规则:【取一分三法】将八进制数每 1 位,转成对应的 3 位的一个二进制数即可。
2.5 二进制和十六进制转换
2.5.1 二进制转换成十六进制
规则:【取四合一法】低位开始,将二进制数每四位一组,转成对应的十六进制数即可。
案例:请将 1011011 转成十六进制。
2.5.2 十六进制转换成二进制
规则:【取一分四法】将十六进制数每 1 位,转成对应的 4 位的一个二进制数即可。
案例:请将 0x23B 转成二进制。
2.6 总结:十进制转换其他进制
2.6.1 转换为整数
方法:除基取余(直到商为 0 停止),逆序排序(余数)
以十进制转二进制为例:
2.6.2 转换为小数
方法:乘基取整(直到小数部分为 0,或取到对应有效位停止),顺序排序(整数部)
提示: 十进制转换为八进制或十六进制需要除以 8 或 16,相对来说麻烦一些,可以先转换为二进制(除 2),然后通过二进制在转换为八进制或十六进制。
很多时候都可以将二进制作为“中间件”,进而再去转换为目标进制。
3 原码、反码、补码
计算机底层存储数据时使用的是二进制数字,但是计算机在存储一个数字时并不是直接存储该数字对应的二进制数字,而是存储该数字对应二进制数字的补码。
3.1 机器数和真值
3.1.1 机器数
一个数在计算机的存储形式是二进制数,我们称这些二进制数为机器数。机器数可以是有符号的,用机器数的最高位存放符号位,0 表示正数,1 表示负数。
3.1.2 真值
因为机器数带有符号位,所以机器数的形式值不等于其真实表示的值(真值),以机器数 10000001 为例,其真正表示的值(首位为符号位,负数)为 -1,而形式值(不考虑符号,首位就是代表 1)为 2 的 7 次方加 1 为129;因此将带符号的机器数的真正表示的值称为机器数的真值。
3.2 原码、反码、补码的概念
3.2.1 原码
原码的表示与机器数真值表示的一样,即用第一位表示符号,其余位表示数值。
正数的原码:就是它对应的二进制数【三码合一】。
负数的原码:它的绝对值对应的二进制数,且最左边位变为 1。
0 的原码:仍然是 0(正 0 负 0)。
十进制的正负 1,用 8 位二进制的原码表示如下:
+1 原码:0000 0001
-1 原码:1000 0001
正0的原码是 0000 0000
负0的原码是 1000 0000
3.2.2 反码
正数的反码:和原码相同【三码合一】。
负数的反码:在其原码的基础上,符号位不变,其余各位取反。
0 的反码:仍然是 0(正 0 负 0)。
十进制的正负 1,用 8 位二进制的反码表示如下:
+1 原码: 0000 0001 反码: 0000 0001
-1 原码: 1000 0001 反码: 1111 1110
正0 原码:0000 0000 反码: 0000 0000
负0 原码:1000 0000 反码: 1111 1111
需要注意的是,反码通常是用来由原码求补码或者由补码求原码的过渡码。
3.2.3 补码
正数的补码:和原码、反码相同。
负数的补码:反码的基础上加 1。
0 的补码:仍然是 0(唯一0,无负 0)。
十进制的正负 1,用 8 位二进制的补码表示如下:
+1 原码: 0000 0001 反码: 0000 0001 补码: 0000 0001
-1 原码: 1000 0001 反码: 1111 1110 补码: 1111 1111
因为负 0 的反码:1111 1111 + 1 = 1 0000 0000
所以补码中的 0 是唯一的,没有负 0 的说法,0 的补码为:0000 0000
3.2.4 总结
正数的原码、反码、补码都一样,三码合一。
负数的反码:它的原码符号位不变,其它位取反(0 -> 1,1 -> 0);负数的补码:它的反码 + 1。
0 的原码、反码、补码都是 0,原码、反码有正负之分,补码唯一,没有负 0 的说法。
3.3 补码名称的由来
补码(Two's complement)的英文名称直译过来是 “2的补数”。这个命名虽然没有直接描述补码的定义,但它揭示了补码的一个重要特性:一个补码可以通过被 2 的位长次幂(2w,其中 w 是位长)减去,来得到它的相反数。即,对于任意补码 x,其相反数可以通过表达式 2w - x 计算得出。这个特性使得补码在计算机内部运算中非常有用,尤其是在执行加减运算时。
3.4 计算机为什么用补码
补码是为了解决原码在运算中的问题而提出的一种改进的表示方式。这种表示方式使得计算机在进行加减运算时,可以统一使用加法运算来实现,从而简化了电路设计和提高了运算效率。
2 - 2 可以写成 2 + (-2),计算机内部在处理减法计算的时候会将其转换为加法计算的形式,以简化硬件设计和提高计算效率。
最高位表示符号位,由于符号位的存在,如果使用原码表示,会导致计算结果不正确。
补码的设计可以工巧妙地让符号位也参与运算,并且可以得到正确的计算结果。
3.5 案例:补码计算 10-12
需求:计算 10 – 12,使用补码描述计算机内部的计算过程。
提示: 计算机中存储和计算数据使用的是补码,要想直到原码(真值),可以将补码符号位不变,数值位取反,末尾加一得到原码。
对于负数的原码:符号位不变,数值位取反,末尾加一 可以得到补码;
对于负数的补码:符号位不变,数值位取反,末尾加一 可以得到原码。
4 测试题
1. 将十进制数 42 转换为二进制表示。
【答案】101010
【解析】
(1)使用除以 2 法,将 42 除以 2,得到商 21 和余数 0。
(2)将商 21 再次除以 2,得到商 10 和余数 1。
(3)重复此过程,直到商为 0。最终得到的余数反向排列就是二进制表示。
(4)42 的二进制表示为 101010。
2. 将二进制数 110110 转换为十进制表示。
【答案】54
【解析】
(1)从右往左,分别为每位赋予 2 的幂次,从 0 开始。
(2)对每位乘以对应的 2 的幂次,并求和。
(3)计算:0*2^0 + 1*2^1 + 1*2^2 + 0*2^3 + 1*2^4 + 1*2^5 = 0 + 2 + 4 + 0 + 16 + 32 = 54。
(4)110110 的十进制表示为 54。
3. 将二进制数 1101 转换为十六进制表示。
【答案】D
【解析】
(1)先将二进制数转换为十进制数,得到 13。
(2)再将十进制数 13 转换为十六进制,得到 D。
(3)1101 的十六进制表示为 D。
4. 将十六进制数 1A3 转换为二进制表示。
【答案】000110100011 或者 110100011
【解析】
(1)十六进制数的 1 对应十进制的 1,A 对应十进制的 10,3 对应十进制的 3。
(2)将每个部分分别转换为二进制数,得到 0001(1)、1010(A)、0011(3)。
(3)连接这三个部分,得到 000110100011。
(4)1A3 的二进制表示为 000110100011。
5. 有十进制数 -8,请用八位二进制小数表示的它的原码、反码、补码。
【答案】原码:1000 1000 反码:1111 0111 补码:1111 1000