今天在课堂上,我的计算机老师正在向我们解释(或试图解释)如何使用二进制补码写一个负二进制数。我的问题是这样的:
最终用户如何确定 11101100 为 236 和 -20 之间的差异?我知道您总是可以检查最重要的位,但这总是 100% 准确吗?负二进制数的惯例是让最高有效位表示符号吗?
旁注:
为什么我们可以学习二进制减法:
将二进制转换为 denary -> 减去 denary -> 重新转换为二进制
今天在课堂上,我的计算机老师正在向我们解释(或试图解释)如何使用二进制补码写一个负二进制数。我的问题是这样的:
最终用户如何确定 11101100 为 236 和 -20 之间的差异?我知道您总是可以检查最重要的位,但这总是 100% 准确吗?负二进制数的惯例是让最高有效位表示符号吗?
旁注:
为什么我们可以学习二进制减法:
将二进制转换为 denary -> 减去 denary -> 重新转换为二进制
“负二进制数的惯例是让最高有效位表示符号吗?”
有多种方法可以用二进制表示负数。最常见的是您正在学习的二进制补码表示。在那个系统中,是的,最高位将指示数字的符号(如果 0 与正数分组)。
在标准的无符号二进制中,数字由位置符号中的位序列表示(为简洁起见,我将只使用三位):
b 2 b 1 b 0 = 2 2 b 2 + 2 1 b 1 + 2 0 b 0 = 4b 2 + 2b 1 + b 0
111 2 = 7 10
110 2 = 6 10
101 2 = 5 10
100 2 = 4 10
011 2 = 3 10
010 2 = 2 10
001 2 = 1 10
000 2 = 0 10
二进制补码
有几种方法可以查看 2 码补码,我认为答案在所有这些方法中都很明显。获得这个系统的一种方法是获取无符号数的上半部分(它们都设置了高位)并将它们移动到零以下:
011 2 = 3 10
010 2 = 2 10
001 2 = 1 10
000 2 = 0 10
111 2 = -1 10
110 2 = -2 10
101 2 = -3 10
100 2 = -4 10
您可以清楚地看到高位表示符号。同样,0 占据了 4 个正表示之一,这导致范围不是对称的:[3, -4](尽管有时最负的值被认为是特殊的,从而使可用范围对称)。等效地,我们可以将最高位重新解释为负数:
b 2 b 1 b 0 = -(2 2 ) b 2 + 2 1 b 1 + 2 0 b 0 = -4 b 2 + 2b 1 + b 0
显然,由于高位具有比所有其他位组合更大的权重(在绝对值的意义上),如果设置了,则结果为负。如果未设置,则所有剩余权重都是正数,因此结果也是如此。
从这个定义,我们可以得出第三种解释:众所周知的规则-a = ~a + 1
(其中-
表示算术否定,~
表示按位补码,我们忽略溢出):
a + ~a = -4b 2 + 2b 1 + b 0 + -4(~b 2 ) + 2(~b 1 ) + ~b 0
a + ~a = -4(b 2 +~b 2 ) + 2 (b 1 +~b 1 ) + (b 0 +~b 0 )
a + ~a = -4(1) + 2(1) + (1)
a + ~a = -1
a = -(~a + 1)
-a = ~a + 1
在这里,我们看到否定翻转了高位,因此它表示数字的符号。请注意,这并非严格意义上的正确,因为如果所有其他位都已设置,则加一可以将高位翻转回来。但是,这仅适用于 0 和最负数(在本例中为 -4 10或 100 2),两者在取反时保持不变。
使用 2s-complement 的好处是可以使用相同的硬件进行有符号和无符号加法。这个很好的属性不适用于过去使用过的其他负二进制表示,我将简要介绍其中的一些。由于这个事实,现代 CPU 几乎总是使用这种表示来进行整数运算(我不知道最近有任何商业反例,但它们可能就在那里)。这就是您学习它的原因(而不是Convert binary to denary -> subtract denary -> reconvert into binary
):了解操作如何在 ALU 的门级工作。
One's-complement
1s-complement 与 2s-complement 密切相关。求反仅通过反转位来执行(不加 1)。前导位仍然表示符号,但正零和负零有不同的表示。我个人从未遇到过 1s 补码的实际使用,但它具有历史意义。
b 2 b 1 b 0 = -3b 2 + 2b 1 + b 0
011 2 = 3 10
010 2 = 2 10
001 2 = 1 10
000 2 = 0 10
111 2 = -0 10
110 2 = -1 10
101 2 = -2 10
100 2 = -3 10
符号和大小
符号和大小最接近人类通常写负数的方式。低两位具有与上述系统相同的权重,高位没有(附加)权重。相反,它只会改变结果的符号。显然,这里的前导位表示符号。与 1 的补码一样,0 也有两种表示形式。它今天仍在 IEEE 浮点数的尾数中使用(尽管指数位于符号和幅度之间)。
b 2 b 1 b 0 = (-1) b 2 (2b 1 + b 0 )
0 11 2 = + 3 10
0 10 2 = + 2 10
0 01 2 = + 1 10
0 00 2 = + 0 10
1 00 2 = - 0 10
1 01 2 = - 1 10
1 10 2 = - 2 10
1 11 2 = - 3 10
Excess-n
Excess-n 更像是一个系统家族。所有值都上移 n (称为偏差),然后表示为无符号情况。如果选择了正确的偏置,则前导位可以指示符号,尽管极性与上述系统不同(并且 0 可以与负或正分组)。这仍然用于 IEEE 浮点数的指数。对于 n = 3,高位确实表示符号,并且 0 与负数分组:
b 2 b 1 b 0 = 4b 2 + 2b 1 + b 0 - n
111 2 = 4 10
110 2 = 3 10
101 2 = 2 10
100 2 = 1 10
011 2 = 0 10
010 2 = -1 10
001 2 = -2 10
000 2 = -3 10
其他
还有其他更深奥的整数表示,例如平衡三进制、base-negative-2 或(可以说)二进制编码的十进制(或简称 BCD)。我说 BCD 有争议的原因是现代处理器通常仍然支持它(尽管它不是数字在内部表示的方式)并且许多计算器曾经基于它。在这些系统中,前导位(或 trit 或 base-n 数字)可能表示也可能不表示符号(或在某些情况下可能表示符号但在其他情况下不表示)。
“最终用户如何确定 11101100 为 236 和 -20 之间的差异?”
正如其他人指出的那样,一般来说,没有办法判断存储在寄存器或内存中的数字实际上是 2s 补码还是无符号数。您基本上必须跟踪它所做的事情才能确定这一点。
但是,如果数字是直接存储在机器代码指令中的立即数,则操作码可以指示它是否已签名(取决于架构)。例如,这可能会改变处理溢出的方式,或者是否执行符号扩展。
例如,可能有单独的“加载立即数”和“加载有符号立即数”指令将立即数值复制到更大的寄存器中,第二个执行符号扩展而第一个不执行符号扩展。“分支”指令通常有一个带符号的立即数来指示跳转的大小(以便向前和向后分支都可以使用单个指令)。可能有不同的“添加立即数”和“添加无符号立即数”指令,它们为加法类型适当地设置溢出标志。
符号扩展
符号扩展意味着复制高位以保留 2s 补数的值。这将对一半的无符号数产生不正确的结果。
未执行符号扩展:
100 2 = 00000100 2
无符号:4 10 = 4 10
有符号:-4 10 = 4 10
执行符号扩展:
100 2 = 11111100 2
有 符号:-4 10 = -4 10
无符号:4 10 = 252 10001 2 = 00000001 2
有符号和无符号:1 10 = 1 10
溢出
添加或减去两个数字可能会产生太大(绝对意义上)无法正确表示的结果。相同的两个二进制序列相加可能会导致有符号数溢出,但不会导致无符号数溢出(反之亦然)。
有符号溢出但无符号不会:
011 2 + 011 2 = 110 2
有符号:3 10 +3 10 = -2 10
无符号:3 10 +3 10 = 6 10
无符号溢出但有符号不会:
111 2 + 010 2 = 001 2
无符号:7 10 + 2 10 = 1 10
有符号:-1 10 + 2 10 = 1 10
在二进制补码表示法中,最高位始终表示符号。但是,您必须知道字段宽度,并且您还必须知道是否使用了二进制补码表示法。例如,如果 11101100 是 32 位数字,则最高有效位为 0,因此为 +236。如果它是一个无符号8 位整数,则为 +236,因为无符号数不使用二进制补码表示法(只有有符号数才使用)。
二进制的加法和减法是计算机的工作方式。因此,了解计算机的工作原理很有用。
最终用户如何确定 11101100 为 236 和 -20 之间的差异?
用户不能仅从位模式确定它。一些上下文必须告诉这个字节是有符号的还是无符号的。在大多数编程语言中,通过跟踪类型来跟踪此上下文。因此,在 C 或 C++ 中,您拥有signed char
and unsigned char
。(普通的旧char
可能是其中之一)。
二进制减法起作用的原因是它(像其他一些操作一样)恰好与位模式完全相同,即使您的类型“错误”。想到这一点的一种方法是(对于这些操作)你正在做算术模 256,并且模 236 和 -20 实际上是同一个数字的两个名称。
简短的回答是,这取决于您如何使用它。几乎所有现代编译器都将它们的整数值表示为二进制补码。这是一个约定。如果您在汇编中编写代码,那么您必须更加注意内存或寄存器中的内容,但在高级语言中,值的数据类型会告诉您。如果类型是有符号的,则 MSB 是符号位,否则不是。数据类型还告诉您值中有多少位,因此您将能够识别哪个位是 MSB。例如,int8_t 始终为 8 位且有符号,而 uint8_t 始终为 8 位但无符号。只要你知道如何识别数据类型,当你看到它以二进制表示时,你就知道如何解释内存中的值。