二进制码

二进制码#

在计算机的世界中,一切都是使用二进制的数字进行表示的,约定所讨论到的二进制数字都是存储在 「机器字长」 为 8 位(即 1 Byte)的计算机中。也就是说一个存储单元最多只能够存储 8 个二进制数字,多出来的部分都将会被抛弃掉,只保留低 8 位的数据。

备注

  • 机器数 是数在计算机中的二进制表示形式。机器数是带符号的,在计算机用机器数的最高位存放符号,正数为 \(0\),负数为 \(1\)

  • 机器数的 真值 是将带符号位的机器数转换为真正数值。

而且从硬件的角度上看,只有正数加负数才算减法,正数与正数相加,负数与负数相加,其实都可以通过 加法器 直接相加。计算机做减法,需要引入原码、反码、补码的概念。

#include <iostream>
#include <sstream>
#include <string>

原码#

  • 原码 (true form)是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为 \(0\),负数该位为 \(1\)\(0\) 有两种表示:\(+0\)\(-0\)),其余位表示数值的大小。

  • 原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。

比如:如果是 \(8\) 位二进制:

\[ +11_{(10)} = 2^3 + 2^1 + 2^0 = +1011_{(2)} \]

这里 1011 代表真值,需要转换为 int8_t,即表示为 \(0000\ 1011\)

因而,int8_t 的取值范围为 \([1111\ 1111, 0111\ 1111]\),即 \([-127, 127]\)

原码的特点:

  1. 原码表示直观、易懂,与真值转换容易。

  2. 原码中 \(0\) 有两种不同的表示形式,给使用带来了不便。 通常 \(0\) 的原码用 \(+0\) 表示,若在计算过程中出现了 \(-0\),则需要用硬件将 \(-0\) 变成 \(+0\)

  3. 原码表示加减运算复杂。 利用原码进行两数相加运算时,首先要判别两数符号,若同号则做加法,若异号则做减法。在利用原码进行两数相减运算时,不仅要判别两数符号,使得同号相减,异号相加;还要判别两数绝对值的大小,用绝对值大的数减去绝对值小的数,取绝对值大的数的符号为结果的符号。可见,原码表示不便于实现加减运算。

反码#

原码最大的问题就在于一个数加上它的相反数不等于 \(0\),于是反码的设计思想就是冲着解决这一点,既然一个负数是一个正数的相反数,那干脆用一个正数按位取反来表示负数。

反码的表示方法是:

  1. 正数的反码是其本身;

  2. 负数的反码是在其原码的基础上,符号位不变,其余按位取反。

比如:

\[ -11_{(10)} = -(2^3 + 2^1 + 2^0) = -1011_{(2)} \]

这里 -1011 代表真值,需要转换为 int8_t,原码表示为 \(1000\ 1011\),反码为 \(1111\ 0100\)

反码的特点:

  1. 在反码表示中,用符号位表示数值的正负,形式与原码表示相同,即 \(0\) 为正;\(1\) 为负。

  2. 在反码表示中,数值 \(0\) 有两种表示方法。

  3. 反码的表示范围与原码的表示范围相同。

补码#

补码:正数的补码等于它的原码;负数的补码等于反码+1(这只是一种算补码的方式,多数书对于补码就是这句话)。

其实负数的补码等于反码+1只是补码的求法,而不是补码的定义,很多人以为求补码就要先求反码,其实并不是,那些计算机学家并不会心血来潮的把反码+1就定义为补码,只不过补码正好就等于反码+1而已。

补码的特点:

  1. 在补码表示中,用符号位表示数值的正负,形式与原码的表示相同,即 \(0\) 为正,\(1\) 为负。但补码的符号可以看做是数值的一部分参加运算。 正数的补码表示就是其本身,负数的补码表示的实质是把负数映像到正值区域,因此加上一个负数或减去一个正数可以用加上另一个数(负数或减数对应的补码)来代替。 从补码表示的符号看,补码中符号位的值代表了数的正确符号,\(0\) 表示正数,\(1\) 表示负数;而从映像值来看,符号位的值是映像值的一个数位,因此在补码运算中,符号位可以与数值位一起参加运算。

  2. 在补码表示中,数值 \(0\) 只有一种表示方法。

  3. 负数补码的表示范围比负数原码的表示范围略宽。纯小数的补码可以表示到-1,纯整数的补码可以表示到 \(-2^n\)

由于补码表示中的符号位可以与数值位一起参加运算,并且可以将减法转换为加法进行运算,简化了运算过程,因此计算机中均采用补码进行加减运算。

为什么负数的补码的求法是反码+1

因为负数的反码加上这个负数的绝对值正好等于1111,在加1,就是10000,也就是四位二进数的模,而负数的补码是它的绝对值的同余数,可以通过模减去负数的绝对值得到它的补码,所以负数的补码就是它的反码+1。

\(w\) 位补码的数学定义为:

\[ - x_{w-1} 2^{w-1} + \sum_{i=0}^{-2} x_i 2^i \]

“二进制上丢弃溢出的位,等价于使用溢出值取模”。

int8_t a = 11;
-100 % 2
0
std::string bin_uint8(uint8_t num)
{
  std::ostringstream oss;  // 用于记录信息
	int k;
	uint8_t *p = (uint8_t*)&num;
	for (int k = 7; k >= 0; k--) //处理 8 个位
	{
		if (*p & (1 << k))
			oss << 1;
		else
			oss << 0;
	}
	return oss.str();
}
bin_uint8(11)
"00001011"