考虑一个十进制整数值,例如 5623。我们直观地理解这些数字表示 (5 * 1000) + (6 * 100) + (2 * 10) + (3 * 1)。因为有 10 个十进制数字,所以每个后续向左的数字的值都会增加 10 倍。
二进制数的工作方式相同,只是因为只有 2 个二进制数字(0 和 1),所以每个数字的值都增加 2 倍。就像逗号通常用于使大十进制数易于阅读一样(例如 1,427,435),我们通常将二进制数写成 4 位一组,以便更容易阅读(例如 1101 0101)。
下表以十进制和二进制计数到 15
十进制值 | 二进制值 |
---|---|
0 | 0 |
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
9 | 1001 |
10 | 1010 |
11 | 1011 |
12 | 1100 |
13 | 1101 |
14 | 1110 |
15 | 1111 |
将二进制转换为十进制
在以下示例中,我们假设处理的是无符号整数。
考虑 8 位(1 字节)二进制数 0101 1110。二进制 0101 1110 表示 (0 * 128) + (1 * 64) + (0 * 32) + (1 * 16) + (1 * 8) + (1 * 4) + (1 * 2) + (0 * 1)。如果我们将所有这些部分相加,我们得到十进制数 64 + 16 + 8 + 4 + 2 = 94。
这是表格格式的相同过程。我们将每个二进制数字乘以其数字值(由其位置决定)。将所有这些值相加即可得到总数。
将 0101 1110 转换为十进制
二进制数字 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 0 |
* 数字值 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= 总计 (94) | 0 | 64 | 0 | 16 | 8 | 4 | 2 | 0 |
让我们将 1001 0111 转换为十进制
二进制数字 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 1 |
* 数字值 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= 总计 (151) | 128 | 0 | 0 | 16 | 0 | 4 | 2 | 1 |
1001 0111 二进制 = 十进制 151。
这可以通过添加更多列轻松扩展到 16 位或 32 位二进制数。请注意,最简单的方法是从右端开始,然后向左工作,每次都将数字值乘以 2。
将十进制转换为二进制的方法 1
从十进制转换为二进制有点棘手,但仍然相当简单。有几种好的方法可以做到这一点。
在第一种方法中,您不断地除以 2,并记下余数。二进制数最终由余数从下到上构建。
将 148 从十进制转换为二进制(用 r 表示余数)
148 / 2 = 74 r0
74 / 2 = 37 r0
37 / 2 = 18 r1
18 / 2 = 9 r0
9 / 2 = 4 r1
4 / 2 = 2 r0
2 / 2 = 1 r0
1 / 2 = 0 r1
从下到上写下所有余数:1001 0100
十进制 148 = 二进制 1001 0100。
您可以通过将二进制数转换回十进制来验证此答案
(1 * 128) + (0 * 64) + (0 * 32) + (1 * 16) + (0 * 8) + (1 * 4) + (0 * 2) + (0 * 1) = 148
这种方法最适合人类,因为它只涉及除以 2。它对机器不太好,因为它需要存储所有计算出的位,以便以后可以反向打印。
将十进制转换为二进制的方法 2
在剩下的两种方法中,我们将向前工作,在计算每个位时进行计算,这样我们就不必在最后重新构造二进制数。
再次考虑十进制数 148。小于 148 的最大 2 的幂是 128,所以我们从这里开始。
148 >= 128 吗?是的,所以 128 位必须是 1。148 - 128 = 20,这意味着我们需要找到价值为 20 的更多位。
20 >= 64 吗?不是,所以 64 位必须是 0。
20 >= 32 吗?不是,所以 32 位必须是 0。
20 >= 16 吗?是的,所以 16 位必须是 1。20 - 16 = 4,这意味着我们需要找到价值为 4 的更多位。
4 >= 8 吗?不是,所以 8 位必须是 0。
4 >= 4 吗?是的,所以 4 位必须是 1。4 - 4 = 0,这意味着所有其余的位都必须是 0。
148 = (1 * 128) + (0 * 64) + (0 * 32) + (1 * 16) + (0 * 8) + (1 * 4) + (0 * 2) + (0 * 1) = 1001 0100
表格格式
二进制数 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
* 数字值 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= 总计 (148) | 128 | 0 | 0 | 16 | 0 | 4 | 0 | 0 |
当数字很小(例如 8 位二进制数)时,这种方法对人类来说非常容易。它对机器也相当高效,因为每个位都需要比较、减法和赋值。
将十进制转换为二进制的方法 3
此方法是方法 2 的变体,使用整数除法。再次考虑十进制数 148。小于 148 的最大 2 的幂是 128,所以我们从这里开始。
148 / 128 = 1,有一些余数。因为 1 是奇数,所以这个位是 1。
148 / 64 = 2,有一些余数。因为 2 是偶数,所以这个位是 0。
148 / 32 = 4,有一些余数。因为 4 是偶数,所以这个位是 0。
148 / 16 = 9,有一些余数。因为 9 是奇数,所以这个位是 1。
148 / 8 = 18,有一些余数。因为 18 是偶数,所以这个位是 0。
148 / 4 = 37,有一些余数。因为 37 是奇数,所以这个位是 1。
148 / 2 = 74,有一些余数。因为 74 是偶数,所以这个位是 0。
148 / 1 = 148,有一些余数。因为 148 是偶数,所以这个位是 0。
148 = (1 * 128) + (0 * 64) + (0 * 32) + (1 * 16) + (0 * 8) + (1 * 4) + (0 * 2) + (0 * 1) = 1001 0100
这种方法对人类来说不太好,因为它需要大量的除法。它对机器也效率较低,因为除法是一种低效的操作。但是它很容易用代码编写,因为它不需要 if 语句。
另一个例子
我们用方法 1 将 117 转换为二进制
117 / 2 = 58 r1
58 / 2 = 29 r0
29 / 2 = 14 r1
14 / 2 = 7 r0
7 / 2 = 3 r1
3 / 2 = 1 r1
1 / 2 = 0 r1
从余数反向构造数字,117 = 二进制 111 0101
并使用方法 2
小于 117 的最大 2 的幂是 64。
117 >= 64 吗?是的,所以 64 位必须是 1。117 - 64 = 53。
53 >= 32 吗?是的,所以 32 位必须是 1。53 - 32 = 21。
21 >= 16 吗?是的,所以 16 位必须是 1。21 - 16 = 5。
5 >= 8 吗?不是,所以 8 位必须是 0。
5 >= 4 吗?是的,所以 4 位必须是 1。5 - 4 = 1。
1 >= 2 吗?不是,所以 2 位必须是 0。
1 >= 1 吗?是的,所以 1 位必须是 1。
十进制 117 = 二进制 111 0101。
二进制加法
在某些情况下(我们稍后会看到一个),能够将两个二进制数相加很有用。二进制数相加出奇地容易(甚至可能比十进制数相加更容易),尽管一开始可能看起来很奇怪,因为您不习惯它。
考虑两个小的二进制数
0110(十进制 6)+
0111(十进制 7)
让我们将这些相加。首先,将它们对齐,就像我们上面做的那样。然后,从右到左,我们对每一列数字进行相加,就像我们对十进制数进行相加一样。但是,由于二进制数字只能是 0 或 1,所以只有 4 种可能性
- 0 + 0 = 0
- 0 + 1 = 1
- 1 + 0 = 1
- 1 + 1 = 0,向下一列进位 1
我们来做第一列
0110 (6 in decimal) + 0111 (7 in decimal) ---- 1
0 + 1 = 1。简单。
第二列
1 0110 (6 in decimal) + 0111 (7 in decimal) ---- 01
1 + 1 = 0,向下一列进位 1
第三列
11 0110 (6 in decimal) + 0111 (7 in decimal) ---- 101
这个有点棘手。通常,1 + 1 = 0,向下一列进位 1。但是,我们已经从上一列进位了 1,所以我们需要加 1。因此,我们这一列最终得到 1,并向下一列进位 1。
最后一列
11 0110 (6 in decimal) + 0111 (7 in decimal) ---- 1101
0 + 0 = 0,但有进位 1,所以我们加 1。1101 = 十进制 13。
现在,我们如何将 1 添加到任何给定的二进制数(例如 1011 0011)?与上面相同,只是底部的数字是二进制 1。
11 (carry column) 1011 0011 (original binary number) 0000 0001 (1 in binary) --------- 1011 0100
有符号数和补码
在上述示例中,我们只处理了无符号整数。在本节中,我们将了解如何处理有符号数(可以是负数)。
有符号整数通常使用一种称为**补码**的方法存储。在补码中,最左边(最高有效)的位用作符号位。0 符号位表示数字是正数(或零),1 符号位表示数字是负数。
正有符号数在二进制中表示方式与正无符号数相同(符号位设置为 0)。
负有符号数在二进制中表示为正数的按位反码加 1。
将十进制转换为二进制(补码)
例如,以下是我们如何在二进制补码中表示 -5
首先我们找出 5 的二进制表示:0000 0101
然后我们反转所有位:1111 1010
然后我们加 1:1111 1011
将 -76 转换为二进制
正 76 的二进制:0100 1100
反转所有位:1011 0011
加 1:1011 0100
我们为什么要加 1?考虑数字 0。如果负值仅仅表示为正数的反码(称为“反码”),那么 0 将有两种表示:0000 0000(正零)和 1111 1111(负零)。通过加 1,1111 1111 会故意溢出并变为 0000 0000。这可以防止 0 有两种表示,并简化了进行负数算术所需的一些内部逻辑。
将二进制(补码)转换为十进制
要将补码二进制数转换回十进制,首先看符号位。
如果符号位是 0,则像上面所示的无符号数一样转换该数字。
如果符号位是 1,则我们反转位,加 1,然后转换为十进制,然后将该十进制数变为负数(因为符号位最初是负数)。
例如,将 1001 1110 从补码转换为十进制数
给定:1001 1110
反转位:0110 0001
加 1:0110 0010
转换为十进制:(0 * 128) + (1 * 64) + (1 * 32) + (0 * 16) + (0 * 8) + (0 * 4) + (1 * 2) + (0 * 1) = 64 + 32 + 2 = 98
由于原始符号位是负数,所以最终值为 -98。
还有另一种更易于手动计算的方法。在这种方法中,符号位表示负值,所有其他位表示正值。
给定:1001 1110
转换为十进制:(1 * -128) + (0 * 64) + (0 * 32) + (1 * 16) + (1 * 8) + (1 * 4) + (1 * 2) + (0 * 1) = -128 + 16 + 8 + 4 + 2 = -98
类型为何重要
考虑二进制值 1011 0100。这个值代表什么?您可能会说是 180,如果这是一个无符号二进制数,您就对了。
但是,如果此值使用补码存储,则为 -76。
如果该值以其他方式编码,则可能完全是其他值。
那么 C++ 如何知道是将包含二进制 1011 0100 的变量打印为 180 还是 -76 呢?
如果本节标题没有泄露,这就是类型发挥作用的地方。变量的类型决定了变量的值如何编码为二进制,以及如何解码回值。因此,如果变量类型是无符号整数,它就会知道 1011 0100 是标准二进制,应该打印为 180。如果变量是有符号整数,它就会知道 1011 0100 是使用补码编码的(现在 C++20 保证了这一点),应该打印为 -76。
如何将浮点数转换为二进制或从二进制转换?
浮点数如何转换为二进制或从二进制转换要复杂得多,而且您可能永远不需要知道。但是,如果您好奇,请参阅此网站,它很好地详细解释了该主题。
小测验时间
问题 #6
编写一个程序,要求用户输入一个介于 0 到 255 之间的数字。将此数字打印为 8 位二进制数(形式为 #### ####)。不要使用任何位运算符。不要使用 std::bitset
。
提醒:std::uint8_t
通常被视为 char,而不是 int。这在使用输入或输出时可能会导致意外行为。