跳到主要内容

有符号数和无符号数

在计算机硬件中,数是以一串或高或低的电信号来体现的,这恰好可以被认为是基为 2 的 数 (与基为 10 的数称为十进制数一样,基为 2 的数称为二进制数)。

所有信息都由二进制数位 (binary digit) 或位 (bit) 组成,因此二进制数运算基本单位是 bit, 取值可以是两种状态之一: 高或低,开或关,真或假,1 或 0。

二进制数的重要性

  • 简洁性: 二进制只有两个数字 (0 和 1),这使得它成为计算机内部信息存储和处理的理想选择。计算机中的所有逻辑操作,如与(AND)、或(OR)、非(NOT)、异或(XOR)等,都可以直接对应于二进制数的操作。

  • 直接映射: 计算机内部的逻辑电路的开关状态可以直接映射到二进制数的0和1上。例如,一个开启的晶体管可以代表1,而一个关闭的晶体管可以代表0。

  • 易于扩展: 二进制数的算术运算规则相对简单,可以直接扩展到更复杂的数制,如八进制(octal)和十六进制(hexadecimal)。

  • 内存效率: 由于二进制数的表示方式非常紧凑,因此在存储和传输数据时非常高效。

在计算机系统中,信息的最小单位是比特(bit),它只能表示两种状态: 0 或 1。为了存储和处理更大的数值或更复杂的数据结构,多个比特被组合在一起形成字节(byte),通常一个字节由8个比特组成。

就书写的一般形式来说,采用最低有效位 (least significant bit) 表示最右边的一位,最高有效位(most significant bit) 表示最左边的一位。 MIPS 的字有 32 位 ,如果我要存储1011。那就是:

0000 0000 0000 0000 0000 0000 0000 1011 (高位补0)

数的二进制表示法对于理解计算机如何进行算术和逻辑运算至关重要。

请学习: 进制转换

附录: 10以内的二的幂

20=121=222=423=824=1625=3226=6427=12828=25629=512210=10242^0 = 1 \\ 2^1 = 2 \\ 2^2 = 4 \\ 2^3 = 8 \\ 2^4 = 16 \\ 2^5 = 32 \\ 2^6 = 64 \\ 2^7 = 128 \\ 2^8 = 256 \\ 2^9 = 512 \\ 2^{10} = 1024

区分正数和负数: 原码

为了表示正数和负数,计算机程序使用了一种称为“符号和幅值”(sign and magnitude)的表示法,即原码:

  • 高位(通常是第一位或最左边的位)用作符号位,0表示正数,1表示负数
  • 其余位表示数值的幅值(即绝对值)。

例如: 占8位的二进制:

+9D: 0000 1001B
-9D: 1000 1001B
C++

固定位数( n+1n+1 位)的原码范围:

[(2n1),(2n1)][-(2^n-1), (2^n-1)]

00 的原码表示有两种: 000000000000 0000100000001000 0000

当运算的结果超出了该数据类型能够表示的范围时,就会发生溢出。(计算机内部一般用补码存储数据,稍后讨论)

原码加减法

与十进制一样,考虑进位和借位: (下面先不考虑符号位)

  101
+ 110
------
1011
C++
  101
- 110
------
011
- 100
------ (不知道怎么办了)
C++

注: 在原码加减运算中,对于两个不同符号数的加法(或同符号数的减法),先要比较两个数的绝对值大小,然后用绝对值大的数减去绝对值小的数,最后还要为结果选择合适的符号。(反正就是很麻烦)

原码表示法的优点

原码表示的优点是,与真值的对应关系直观、方便,因此与真值的转换简单,并且用原码 实现乘除运算比较简便。

原码表示法的缺点

0的表示不唯一,给使用带来不便;加减运算比较复杂,要考虑溢出、负借位等等问题。

二进制补码

为了简化硬件设计并解决这些问题,现代计算机更倾向于使用二进制补码(two's complement)的方式来表示有符号整数。二进制补码不仅解决了正负数的区分问题,还使得加减运算能够通过相同的硬件电路实现,同时有效地处理了溢出情况,并消除了正负零的问题。

补码表示正数和负数:

  1. 正数: 与其原码相同,即最高位(符号位)为0,剩余各位表示数值的绝对值。

    • 例如,十进制正数5,在8位二进制补码中的表示为: 0000 0101
  2. 负数: 通过取其绝对值的二进制反码(将所有1变为0,0变为1),然后再加1得到的。

    • 例如,十进制正数5,在8位二进制补码中的表示为: 1111 1011

注:

  • 反码:
    1. 正数的反码与原码相同。
    2. 负数的反码是将对应正数(绝对值)的二进制码按位取反。

二进制补码表示的整数转换为十进制数:

  1. 正数: 正常的二进制转十进制的转换规则。

    • 例如,8位二进制补码 0000 1010 表示正数 10。
  2. 负数: 先转换为对应原码(除符号位外,按位取反 再+1),首位是1表示负数,其余按照正常的二进制转十进制的转换规则。

    • 例如,8位二进制补码 1000 1010 表示负数 -118。

总结:

补码原码{它本身 if 正数非符号位按位取反+1 if 负数 原码十进制补码 \rightleftharpoons 原码\begin{cases} 它本身 & \text{ if } 正数 \\ 非符号位按位取反+1 & \text{ if } 负数 \end{cases} \\ \ \\ 原码 \rightleftharpoons 十进制

对于为什么是非符号位按位取反 + 1(并且是 原码补码原码 \rightleftharpoons 补码 双向转化的), 以下是可能的推理:

对于一个负数 xx, 它的原码是: 1..1.. (符号位为1), 有: x+x=xx=11..11logx+11=1 (性质)x + \sim{x} = x | \sim{x} = \underbrace{11..11}_{\left \lfloor logx \right \rfloor + 1 个1} = -1 \ (性质) 则有 x+x+1=0x + \sim{x} + 1 = 0

x+1=x,(x>0)\sim{x} + 1 = -x, (-x > 0)

(当 x>0x > 0 同样成立), 故可: 原码补码原码 \rightleftharpoons 补码


n+1n+1 位的二进制可以表示的范围: [(2n),(2n1)][-(2^n), (2^n-1)]

其中,几个特殊数据的补码表示:

  1. [+0]=[0]=000...000(含符号位共n+10)0[+0]_补=[-0]_补=000...000(含符号位共n+1个0),0补码唯一
  2. [2n]=100...000(n0)n+1[-2^n]_补=100...000(n个0),n+1 位补码表示的最小整数
  3. [2n1]=011...111(n1)n+1[2^n-1]_补=011...111(n个1),n+1 位补码表示的最大整数

补码的优势

  • 简化加减运算: 补码表示法的优点在于它使得加法和减法运算可以使用相同的硬件电路来实现。在补码表示法中,减法运算可以通过加上减数的补码来实现,从而简化了计算机的设计和实现过程。

  • 无额外的零表示: 在补码表示法中,只有一个零,不存在正零和负零的区别

  • 溢出处理: 当两个数相加时,如果发生溢出(最高有效位产生进位),结果仍会落在有效范围内,保持循环不变性,如在8位系统中从最大正值+1271会自动变成最小负值-128

因为补码表示法具有以上优势: 目前大多数现代计算机系统在进行数值的存储和运算时,普遍采用二进制补码表示法。对于整数运算来说,补码是主导的表示法。

值得注意的是,浮点数在计算机内部通常不是以补码形式存储的,而是遵循IEEE 754标准,该标准使用一种不同的编码方案(符号位、指数和尾数)来表示浮点数。这个会在后面的章节讲解。

溢出

关于补码溢出的讨论: 补码表示法的优点之一是它可以很容易地检测到溢出,并且对于加法运算,溢出只可能发生在正溢出(两个正数相加得到负数)或负溢出(两个负数相加得到正数)的情况下。

  • 正溢出: 当两个正数相加的结果超过了正数的最大表示范围时,就会发生正溢出。

  • 负溢出: 当两个负数相加的结果超过了负数的最小表示范围时,就会发生负溢出。

  • 检测溢出: 在补码表示法中,可以通过检查加法运算中的进位来检测溢出。如果两个正数相加时最高位(符号位)产生了进位,或者两个负数相加时最高位没有产生进位,那么就可以确定发生了溢出。

  • 处理溢出: 处理溢出的方法取决于特定的应用程序和编程环境。在某些情况下,程序员可能希望捕获溢出并采取相应的措施,如设置错误标志、抛出异常或采取其他错误处理机制。在其他情况下,溢出可能被视为未定义行为,可能导致不可预测的结果。

符号扩展

在进行算术运算和逻辑运算时,需要确保操作数的位数一致。那如果位数不一致怎么办?

答: 符号拓展

符号扩展(Sign Extension) 是一种将较短位数的二进制数转换成较长位数二进制数的方法,同时保持数的数值不变。

在符号扩展中,我们观察原始数的最高有效位,即符号位,然后将其复制到新数的高位部分

例如:

  • 16位的二进制补码数 10000000 00000001
    • 拓展为32位: 111111111 11111111 10000000 00000001
  • 16位的二进制补码数 00000000 00000001
    • 拓展为32位: 00000000 00000000 00000000 00000001

注: 这种方法之所以正确,是因为二进制补码表示的正数实际上在左侧有无限多个0,而负数在左侧有无限多个1,只是为了适应硬件的位数宽度,数的前导位被隐藏了,符号扩展只是简单地恢复了其中一部分。

请作者喝奶茶:
Alipay IconQR Code
Alipay IconQR Code
本文遵循 CC CC 4.0 BY-SA 版权协议, 转载请标明出处
Loading Comments...