检测负二进制

9
今天在课上,我的计算机老师正在向我们解释(或试图解释)如何使用二进制补码写负数。我的问题是:
最终用户如何确定11101100是236还是-20?我知道你可以始终检查最高有效位,但这总是100%准确的吗?负二进制数的惯例是使最高有效位表示符号吗?
附注: 为什么我们要学习二进制减法,当我们可以做以下操作: 将二进制转换为十进制->减去十进制->重新转换为二进制

1
答案中有很多好的信息,特别是来自@jerry。你还有什么不清楚的吗? - hvanbrug
Hvanbrug 的意思是,为什么你没有将你的收藏标记为答案呢?这会给那些为你花费时间的人带来声望点数,所以你应该采取这个微小的步骤来表示感激。 - Matt
1
@hvanbrug和Matt,非常感谢你们的称赞,但看起来OP自从我扩展了我的答案以来就没再出现过(因为OP在评论中提出的问题)。即使忽略这一点,我认为等到赏金到期再选择答案肯定是公平的。如果Kezz101决定不接受或奖励赏金,那就由他/她决定(虽然显然我会想知道如何让我的答案更好)。 - jerry
1
你已经涵盖了所有内容,但我担心这可能会是信息过载。很难说@Kezz101实际想要什么,以及他/她从我们所说的内容中理解了什么。 - hvanbrug
这与你的答案质量无关!它们都很出色!我一直在为我的最后三门考试做大量复习,SO并不是我的首要任务。 :) 编辑:这可能是我在SO上收到的最好的答案... - Kezz
4个回答

23

问题1:

“在负二进制数中,最高有效位是否表示符号的约定?”

用二进制表示负数有多种方法,其中最常见的是您正在学习的二进制补码表示法。在该系统中,如果0与正数分组,则最高位将表示数字的符号。

在标准无符号二进制中,数字由位置表示法中的比特序列表示(为简洁起见,我只使用三个比特):

b2b1b0 = 22b2 + 21b1 + 20b0 = 4b2 + 2b1 + b0

1112 = 710
1102 = 610
1012 = 510
1002 = 410
0112 = 310
0102 = 210
0012 = 110
0002 = 010

二进制补码
有多种方法可以看待二进制补码,我认为所有方法的答案都很明显。 获得此系统的一种方法是将无符号数字的顶半部分(所有数字的高位均设置)移至零以下:

0112 = 310
0102 = 210
0012 = 110
0002 = 010
1112 = -110
1102 = -210
1012 = -310
1002 = -410

您可以清楚地看到高位表示符号。再次说明,0占用了4个正数表示中的一个,这导致范围不对称:[3,-4](尽管有时最负值被认为是特殊的,使得可用范围对称)。同样,我们可以重新解释最高位作为负数:
b2b1b0=-(22)b2+21b1+20b0=-4b2+2b1+b0 显然,由于高位的权重(在绝对值意义上)比所有其他位的权重加起来还要大,如果它被设置,则结果为负数。如果没有设置,所有剩余的权重都是正数,因此结果也是正数。
从这个定义中,我们可以得出第三种解释:常用规则的知名公式 -a = ~a +1 (其中“-”表示算术否定,“~”表示按位取反,我们忽略溢出):
a+ ~a=-4b2+2b1+b0+ -4(~b2)+2(~b1)+~b0 a+ ~a=-4(b2+~b2)+2(b1+~b1)+(b0+~b0) a+ ~a=-4(1)+2(1)+(1) a+ ~a=-1 a=-(~a+1) -a=~a+1
在这里,我们看到否定会翻转高位,因此它指示了数的符号。注意,这不是严格正确的,因为加上一可以反转高位,如果所有其他位都设置,则可能重置高位。但是,这仅适用于0和最负数(在本例中为-410或1002),两者在取反时保持不变。使用二进制补码的好处在于,相同的硬件可以用于执行有符号和无符号加法。这个优点并不适用于过去使用过的其他负二进制表示法,其中一些我会简要介绍一下。由于这个事实,现代CPU几乎总是使用这种表示法进行整数运算(我不知道任何最近的商业反例,但可能存在)。这就是为什么你正在学习它(而不是像“将二进制转换为十进制->减去十进制->重新转换为二进制”):以了解操作在ALU的门级别上如何工作。

反码
反码与二进制补码密切相关。 取反只通过翻转位来完成(不加1)。 领导位仍然表示符号,但正零和负零有不同的表示。 我从未亲眼见过反码的实际用途,但它有历史意义。

b2b1b0 = -3b2 + 2b1 + b0
0112 = 310
0102 = 210
0012 = 110
0002 = 010
1112 = -010
1102 = -110
1012 = -210
1002 = -310


符号大小
符号大小最接近人们通常书写负数的方式。 低两位与上述系统中的权重相同,高位没有(加法)权重。 相反,它仅更改结果的符号。 这里,显然,领导位表示符号。 与1的补码一样,有两种表示方法。 它仍然在IEEE浮点数的尾数中使用(虽然指数位于符号和幅度之间)。

b2b1b0 = (-1)b2(2b1 + b0)

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

过量-n
过量-n实际上更像是一组系统。所有的值都会向上移动n个单位(被称为偏置),然后像无符号的情况一样表示。如果选择正确的偏置,那么前导位可能表示符号,尽管极性与上述系统不同(并且0可以与负数或正数中的任意一个分组)。这仍然用于IEEE浮点数的指数。对于n = 3,高位确实表示符号,并且0会与负数分组:

b2b1b0 = 4b2 + 2b1 + b0 - n

1112 = 410
1102 = 310
1012 = 210
1002 = 110
0112 = 010
0102 = -110
0012 = -210
0002 = -310

Others
还有其他更为晦涩的整数表示方法,例如平衡三进制、负二进制或(可以说是)二进制编码十进制(BCD缩写)。之所以说BCD是可以争议的,是因为现代处理器通常仍然支持它(尽管这不是数字的内部表示方式),而且许多计算器过去都是基于它的。在这些系统中,高位(或三进制位或基n位数)可能会或可能不会指示符号(或在某些情况下指示它,在其他情况下则不会)。

问题2:

"最终用户如何确定11101100是236还是-20?"

一般来说,没有办法确定存储在寄存器或存储器中的数字实际上是表示为二进制补码还是无符号数,正如其他人指出的那样。您必须跟踪它的使用方式才能确定这一点。

但是,如果该数字是直接存储在机器代码指令中的立即值,则操作码可以指示它是否有符号(具体取决于体系结构)。这可能会更改例如处理溢出的方式,或者是否执行符号扩展。

例如,可能会有单独的“加载立即”和“加载带符号立即”指令,用于将立即值复制到较大的寄存器中,第二个执行符号扩展,第一个则不执行。 “Branch”指令通常具有带符号立即数以指示跳跃的大小(以便前向和后向分支都可以使用单个指令)。可能存在不同的“添加立即”和“添加无符号立即”指令,用于根据加法类型设置适当的溢出标志。

符号扩展
符号扩展意味着复制高位以保留二进制补码数字的值。这将产生错误的结果,适用于一半的无符号数字。

未执行符号扩展:

1002 = 000001002
无符号数:410 = 410
有符号数:-410 = 410

进行符号扩展:

1002 = 111111002
有符号数:-410 = -410
无符号数:410 = 25210

0012 = 000000012
有符号数和无符号数:110 = 110

溢出(Overflow)
两个数字相加或相减的结果可能太大(绝对值)而无法正确表示。对于有符号数,相同的二进制序列相加可能会导致溢出,但对于无符号数则不会(反之亦然)。

有符号数溢出但无符号数不会:

0112 + 0112 = 1102
有符号数:310+310 = -210
无符号数:310+310 = 610

无符号数溢出但有符号数不会:

1112 + 0102 = 0012
无符号数:710 + 210 = 110
有符号数:-110 + 210 = 110


太棒了!您能详细说明一下“opcode”的含义吗?同时,我有点不明白您在最后三段所说的内容:s 我是否只是太菜了? - Kezz
@Kezz101 谢谢,但这只是一个概述。opcode 代表 operation code,指令的一部分,表示它执行的操作:加法、减法、分支等等。这些操作码(及其参数)由 指令集架构(或 ISA)定义。不理解最后三段话并没有太大意义,它们是表示负数的不常见方式。你只有在浮点数的上下文中才可能遇到其中的两个(符号-大小和过量-n)。我会在接下来的几天里扩展答案。 - jerry
好的,谢谢!当你准备好了就告诉我吧;我目前对二进制非常感兴趣。 - Kezz
@Kezz101 我更新了答案。我不确定你作为提问者是否会自动收到通知,所以我特地告诉你。 - jerry
非常透彻。好答案。 - hvanbrug
这应该在课堂上。 - Shark

4
  1. 在二进制补码表示法中,最高位始终表示符号。但是,您必须知道字段宽度,并且还必须知道是否使用了二进制补码表示法。例如,如果11101100是一个32位数字,则最高有效位为0,因此它是+236。如果它是一个无符号的8位整数,则是+236,因为无符号数字不使用二进制补码表示法(只有带符号的数字才使用)。

  2. 在计算机中,加减运算是以二进制形式进行的。因此了解二进制加减运算对于理解计算机工作原理非常有用。


3
终端用户无法仅从比特模式确定 11101100 是 236 还是 -20。必须有一些上下文信息来指示此字节是有符号还是无符号。在大多数编程语言中,通过跟踪类型来管理此上下文信息。因此,在 C 或 C++ 中,您有有符号字符和无符号字符 (普通的 char 可以是任何一个)。
二进制减法能够工作的原因是 (与其他一些操作一样),即使您使用了错误的类型,在比特模式上它也确切地执行相同的操作。这种情况的一种思考方式是,对于这些操作,你正在做模 256 算术,并且在该模数下,236 和-20 实际上是同一个数字的两个名称。

这是一个非常危险的看法。我曾经有一个数学专业的员工在我手下工作,他对我说符号是无关紧要的,只有大小才重要。虽然从技术上讲有时候这是正确的,但如果你没有考虑到它的实际数值,对数字进行操作可能会使你犯错。在你的情况下,将236和-20视为相同的数字在许多情况下都会破坏你的代码,例如循环到-20时,如果你将其存储在无符号类型中,则不会按预期到达那里。 - hvanbrug
1
我编辑了我的帖子并重复说明只有一些操作是模256发生的。在模256中,这些数字确实是相同的。但是8位int与moudlo-256整数并不相同:它们仅对于某些特定操作是等效的,并且这解释了这些操作的工作方式。 - Adrian Ratnapala
@hvanbrug 这有什么危险性,这是事实。如果你随机查看内存中的位,你无法确定它是否为有符号数。而且在计算机纯粹使用8位数字时,从技术上讲,它是等效的。二进制表示是相同的,如果你将其打印为有符号或无符号的8位整数,你将得到其中之一。程序员有责任确保他们的类型足以容纳它们所操作的值。 问题是,你所说的不关心类型的代码是什么意思? - UpAndAdam
1
@hvanbrug 第二个循环没有运行是因为它不应该运行。我从来不指望它会运行。如果你不关闭所有警告,你可能会得到一个编译器警告,提示从有符号转换为无符号时存在精度损失。 不关注你正在做什么是危险的,这是一个普遍的真理,毫无争议。但你仍然没有解释清楚他的解释有何危险之处;它是正确的。危险的是试图盲目地决定上下文应该是什么。发布者从未暗示过它是安全的或试图这样做。 - UpAndAdam
我同意你的观点。我认为危险的是这个说法:“从某种角度来看(对于这些操作),你正在进行模256算术运算,在该模数中,236和-20实际上是同一个数字。” 从技术上讲,这是正确的,知道这一点可能会在某些情况下有所帮助,但是以这种方式思考(它们是相同的数字)可能会导致程序员变得懒散。它们不是相同的数字,并且只在某些情况下表现出类似的行为。(待续...) - hvanbrug
显示剩余4条评论

1
简短的回答是它取决于你如何使用它。几乎所有现代编译器都将整数值表示为二进制补码形式。这是一种约定。如果你在汇编语言中编写代码,那么你必须更加关注存储器或寄存器中的内容,但在高级语言中,值的数据类型会告诉你。如果类型为有符号,则最高位是符号位,否则不是。数据类型还告诉你值中有多少个位,因此你将能够确定哪个位是最高位。例如,int8_t始终为8位,且为有符号,而uint8_t始终为8位,但为无符号。只要你知道如何识别数据类型,当你看到它以二进制形式表示时,你就知道如何准确地解释存储器中的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接