为什么对带正负号的数字宁愿用二进制补码,也不用正负号?


201

我只是很好奇是否有理由为什么要以二进制形式表示-1,使用二进制补码:翻转位并加1?

-1由11111111(二进制补码)表示,而不是(对我来说更直观)10000001,后者是二进制1,第一位为负标志。

免责声明:我的工作不依赖于二进制算法!


6
FWIW,您偶尔使用符号位的“直观”方法-例如,大多数计算机在表示浮点数时都使用符号位。
Adisak

2
@Adisak这就是所谓的幅度
科尔·约翰逊

2
因为浮点数包含三个组成部分,所以我始终将符号和幅度表示与整数相关联:符号,指数和尾数(通常带有隐式“ 1”)。但是我想只要将指数和尾数视为严格线性就可以很容易地将其视为大小。
2013年

这是一篇文章,针对那些对@Adisak的言论感到好奇的人,他们讨论了如何以二进制形式存储浮点数。
GDP2

刚刚看了一段解释此youtube.com/watch?v=dHB7jFjESLY
-allenlinli

Answers:


333

这样做是为了使加法处理负数不需要任何特殊的逻辑。查看Wikipedia上的文章

假设您有两个数字2和-1。用“直观”表示数字的方式,它们分别是00101001(我坚持使用4位的大小)。以两者的互补方式,它们是00101111。现在,假设我要添加它们。

二进制补码非常简单。您通常会添加数字,并且末尾的任何进位都将被丢弃。因此,它们添加如下:

  0010
+ 1111
=10001
= 0001 (discard the carry)

0001 为1,这是“ 2 +(-1)”的预期结果。

但是在您的“直观”方法中,添加更为复杂:

  0010
+ 1001
= 1011

哪个是-3,对不对?在这种情况下,简单加法不起作用。您需要注意的是,其中一个数字为负数,如果是这种情况,请使用其他算法。

对于这种“直观”的存储方法,减法是与加法不同的操作,在加号之前需要对数字进行附加检查。由于您希望最基本的运算(加法,减法等)尽快完成,因此需要以一种允许使用最简单算法的方式存储数字。

此外,在“直观”存储方法中,有两个零:

0000  "zero"
1000  "negative zero"

直观上讲,它们是相同的数字,但存储时具有两个不同的值。每个应用程序都需要采取额外的步骤来确保非零值也不是负零。

通过这种方式存储整数还有另一个好处,那就是当您需要扩展寄存器的宽度时,将值存储在其中。使用二进制补码,将4位数字存储在8位寄存器中是重复其操作的问题。最重要的位:

    0001 (one, in four bits)
00000001 (one, in eight bits)
    1110 (negative two, in four bits)
11111110 (negative two, in eight bits)

只需查看较小单词的符号位,然后重复它直到填充较大单词的宽度即可。

使用您的方法,您需要清除现有位,这是除填充之外的额外操作:

    0001 (one, in four bits)
00000001 (one, in eight bits)
    1010 (negative two, in four bits)
10000010 (negative two, in eight bits)

在两种情况下,您仍然都需要设置这些额外的4位,但是在“直观”情况下,您还需要清除第5位。这是每个应用程序中存在的最基本,最常见的操作之一,这是额外的微小步骤。


13
我同意。2的补码作品。但是我们是如何首先到达的呢?如果假设我需要得出这个符号,那么思考的过程将是怎样的。我认为达​​到2的补码不仅是运气,不是吗?
Lazer

1
另外,为什么没有浮点数的2补码?
Lazer

6
@Lazer查看这篇文章,了解how we arrived at 2s compliment the first place. cs.cornell.edu/~tomf/notes/cps104/twoscomp.html
Ankit

1
据我所知,Java仅具有带符号整数类型,因此它始终在其二进制补码解释中对待它。在其他语言中,如何对待值取决于代码如何对待它。没有什么可告诉您的是,内存块是有符号或无符号整数,双精度或字符串或其他内容。原始数据是您选择将其解释为的任何类型。
韦尔博格

3
@Suraj,我建议查看Wikipedia上有关二进制补码的文章,以获取完整答案:en.wikipedia.org/wiki/Two%27s_complement。简短的回答是MSB 1表示-8,其余三个1小号表明421分别,所以-8+4+2+1 = -1
Welbog

18

维基百科说了一切:

二进制补码系统的优点是不需要加法和减法电路检查操作数的符号以确定是加还是减。此属性使系统不仅易于实现,而且能够轻松处理高精度算术。而且,零只有一个表示,从而消除了与负零相关的微妙之处,后者存在于补码系统中。

换句话说,无论数字是否为负,加法都相同。


先生,如果我写char = 12; 并且unsigned char b = 12,底层位模式是否相同,实际上会发生什么?
Suraj Jain

写入或读取时没有任何变化。仅在加法或减法时适用。
Talespin_Kit

12

即使这个问题老了,让我投入2美分。

在我解释这个之前,让我们回到基础。2'补码是1的补码+ 1。现在什么是1的补码,以及它的意义是什么。

任何n位数字及其1的补码之和为您提供了可以由这些n位表示的最大数字。例:

 0010 (2 in 4 bit system)
+1101 (1's complement of 2)
___________________________
 1111  (the highest number that we can represent by 4 bits)

现在,如果我们尝试将结果再加1,将会发生什么。这将导致溢出。

结果将是 1 0000是0(因为我们正在处理4位数字,(左侧的1是溢出)

所以,

Any n-bit number + its 1's complement = max n-bit number
Any n-bit number + its 1'complement + 1 = 0 ( as explained above, overflow will occur as we are adding 1 to max n-bit number)

然后有人决定将1的补码+ 1称为2'补码。因此,上面的语句变为:任何n位数字+它的2的补码= 0,这意味着一个数字的2的补码=-(该数字的)

所有这些都产生了一个问题,为什么我们只能使用n位的(n-1)来表示正数,为什么最左边的n位代表正负号(最左边的0代表+ ve数,而1则意味着-ve号)。例如,如果第32位为1,则为什么仅使用java中int的前31位表示正数,即它的a -ve数。

 1100 (lets assume 12 in 4 bit system)
+0100(2's complement of 12)
___________________________

1 0000(结果为零,进位1溢出)

因此(n + 2'n的补数)= 0的系统仍然有效。这里唯一的歧义是2的12的补码是0100,除了2s补码系统中表示-12之外,它也模糊地表示+8。

如果正数的最左位始终为0,则将解决此问题。在这种情况下,其2的补码在其最左边的位将始终为1,而我们不会出现表示2的补码数和+ ve数的同一位集合的歧义。


1
+1了。这是信息,但是最后我不确定您为什么要使用最高有效位来表示它是正数还是负数。它有很多问题,例如0将具有2个表示形式-0000(+)和1000(-)..同样,使用同一算法也无法进行加法和减法。当您说正常的0100时为+8,而当您说二进制补码0100则为-12 ..
hagrawal

8

二进制补码允许以正常方式进行加减(例如,您为无符号数字取整数)。它还可以防止-0(用普通的逐位比较数字的方式表示不等于0的0)。



5

该操作的通常实现是“翻转位并加1”,但是还有另一种定义它的方式可能使原理更清楚。如果采用通常的无符号表示形式,其中每个位控制2的下一个幂,并且仅使最高有效项为负,则2的补码就是您得到的形式。

取一个8位值a 7 a 6 a 5 a 4 a 3 a 2 a 1 a 0

通常的无符号二进制解释是:
2 7 * a 7 + 2 6 * a 6 + 2 5 * a 5 + 2 4 * a 4 + 2 3 * a 3 + 2 2 * a 2 + 2 1 * a 1 + 2 0 * a 0
11111111 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255

两者的补码解释是:
-2 7 * a 7 + 2 6 * a 6 + 2 5 * a 5 + 2 4 * a 4 + 2 3 * a 3 + 2 2 * a 2 + 2 1 * a 1 + 2 0 * a 0
11111111 = -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = -1

其他位都没有任何改变的含义,而加7则是“溢出”且不希望执行,因此几乎所有算术运算都无需修改即可工作(正如其他人所指出的那样)。符号幅度通常检查符号位并使用不同的逻辑。


4

二进制补码允许将负数和正数加在一起,而无需任何特殊逻辑。

如果您尝试使用方法
10000001(-1)
+00000001(1)加1和-1,
则会得到
10000010(-2)

相反,通过使用二进制补码,我们可以添加

11111111(-1)
+00000001(1)得到
00000000(0)

减法也是如此。

另外,如果尝试从6(两个正数)中减去4,则可以2的补数4并将两个数相加6 +(-4)= 6-4 = 2

这意味着正负数的减法和加法都可以通过cpu中的同一电路完成。


4

扩展其他答案:

互为补充

  • 加法与普通正整数加法的机制相同。
  • 减法也不会改变
  • 乘法也!

划分确实需要不同的机制。

所有这些都是正确的,因为二进制补码只是常规的模运算,我们选择通过减去模来将某些数视为负数。


不仅只有非扩宽乘法是相同的。但是由于大多数高级语言在没有显式强制转换的情况下都不支持扩展乘法,因此结果在这些语言中是相同的。
phuclv

@LưuVĩnhPhúc:扩展乘法通常是相同的,但是只有在结果适合于有符号整数的范围内时,才保证有符号和无符号乘法的结果是相同的。一些编译器(例如gcc)的名称类似于unsigned mul(unsigned short x, unsigned short y) { return x*y; }[16位short; 32位int]偶尔会产生的代码,将故障如果该产品是比较大2147483647
supercat

2

阅读该问题的答案,我发现了此评论[编辑]。

2的0100(4)的补码将是1100。如果我正常地说,现在1100是12。因此,当我说正常1100时,它是12,但是当我说2的补码1100时,它是-4?同样,在Java中,当存储1100(现在假设为4位)时,如何确定它是+12还是-4 ??– hagrawal 7月2日16:53

我认为,此评论中提出的问题非常有趣,因此我想首先重新表述一下,然后提供答案和示例。

问题–系统如何确定必须如何解释一个或多个相邻字节?特别是,系统如何确定给定的字节序列是纯二进制数还是2的补码?

答案–系统确定如何通过类型解释字节序列。类型定义

  • 必须考虑多少个字节
  • 这些字节如何解释

示例–下面我们假设

  • char的长度为1个字节
  • short的长度为2个字节
  • intfloat的长度为4个字节

请注意,这些尺寸特定于我的系统。尽管很常见,但它们在系统之间可能有所不同。如果您对它们在系统上的用途感到好奇,请使用sizeof运算符

首先,我们定义一个包含4个字节的数组,并将它们全部初始化为10111101对应于十六进制数的二进制数BD

// BD(hexadecimal) = 10111101 (binary)
unsigned char   l_Just4Bytes[ 4 ]   =   { 0xBD, 0xBD, 0xBD, 0xBD };

然后,我们使用不同的类型读取数组内容。

unsigned charsigned char

// 10111101 as a PLAIN BINARY number equals 189
printf( "l_Just4Bytes as unsigned char  -> %hi\n", *( ( unsigned char* )l_Just4Bytes ) );

// 10111101 as a 2'S COMPLEMENT number equals -67
printf( "l_Just4Bytes as signed char    -> %i\n", *( ( signed char* )l_Just4Bytes ) );

unsigned shortshort

// 1011110110111101 as a PLAIN BINARY number equals 48573
printf( "l_Just4Bytes as unsigned short -> %hu\n", *( ( unsigned short* )l_Just4Bytes ) );

// 1011110110111101 as a 2'S COMPLEMENT number equals -16963
printf( "l_Just4Bytes as short          -> %hi\n", *( ( short* )l_Just4Bytes ) );

unsigned intintfloat

// 10111101101111011011110110111101 as a PLAIN BINARY number equals 3183328701
printf( "l_Just4Bytes as unsigned int   -> %u\n", *( ( unsigned int* )l_Just4Bytes ) );

// 10111101101111011011110110111101 as a 2'S COMPLEMENT number equals -1111638595
printf( "l_Just4Bytes as int            -> %i\n", *( ( int* )l_Just4Bytes ) );

// 10111101101111011011110110111101 as a IEEE 754 SINGLE-PRECISION number equals -0.092647
printf( "l_Just4Bytes as float          -> %f\n", *( ( float* )l_Just4Bytes ) );

RAM(l_Just4Bytes[ 0..3 ])中的4个字节始终保持完全相同。唯一改变的是我们如何解释它们。

同样,我们告诉系统如何通过type解释它们。

例如,上面我们使用以下类型来解释l_Just4Bytes数组的内容

  • unsigned char:1个字节的纯二进制
  • signed char:2个补码中的1个字节
  • unsigned short:2字节,采用普通二进制表示法
  • short:2个补码中的2个字节
  • unsigned int:4字节,采用普通二进制表示法
  • int:2个补码中的4个字节
  • float:IEEE 754单精度表示法中的4个字节

[编辑]这篇文章在user4581301发表评论后已被编辑。感谢您抽出宝贵的时间写这几行有用的内容!


该代码块需要进行编辑,因此读者不必一直来回滚动。更好的是,顶部的大量注释应该变成纯文本,并允许渲染器处理格式。您还应该在讨论大小和格式的末尾处添加警告,因为大小不是固定的。
user4581301 2015年

+1。您可能要考虑做的一件事,就是@ mw215,单独使这个问题/答案对成为社区Wiki条目,因为它对于可能对二进制补码数学上下文之外的原始字节解释感兴趣的人很有用。
Welbog 2015年

我只想知道2的补码是否总是跟随着,我的意思是如果我有int x = -4,那么该printf("%d" , x)如何解释呢?还有unsigned intand和signed intand %d%u... 之间的区别是...这已经困扰了我很长时间了。谢谢。
Suraj Jain

@Suraj Jain使用int类型时,signed修饰符为默认值。这意味着intsigned int是完全相同的类型。因此,这两个定义int i = -4;signed int i = -4;具有相同的含义。
mw215 '16

@Suraj Jain系统确定如何通过类型解释字节序列。类型定义:必须考虑多少个字节以及必须如何解释这些字节。An int2的补码unsigned int形式的4字节,an 是4字节的纯二进制表示法(使用sizeof运算符检查系统上的实际类型大小)。
mw215 '16

1

您可以观看斯坦福大学的杰里·凯恩(Jerry Cain)教授在第二堂课(关于2的补码的解释大约在13:00左右开始)中讲解两者的补码,这一系列讲座可以从Standford的YouTube频道观看,并可以观看。这是讲座系列的链接:http : //www.youtube.com/view_play_list ? p= 9D558D49CA734A02


0

使用二进制补码是因为它在电路中更易于实现,并且也不允许负零。

如果有x位,则二进制补码的范围为+(2 ^ x / 2 + 1)至-(2 ^ x / 2)。一个人的补码将从+(2 ^ x / 2)到-(2 ^ x / 2),但允许一个负零(在4位1的补码系统中0000等于1000)。


0

好吧,您的意图并不是真正地反转二进制数字的所有位。实际上是从1减去其每个数字。这只是一个幸运的巧合,从1减去1得出0,从1减去0得到1。所以翻转位有效地实现了这种减法。

但是,为什么要找出每个数字与1的差呢?好吧,你不是。您的实际目的是计算给定的二进制数与另一个具有相同位数但只包含1的二进制数的差。例如,如果您的数字是10110001,那么当您翻转所有这些位时,您实际上在进行计算(11111111-10110001)。

这说明了计算Two's Complement的第一步。现在,让我们在图片中包括第二步-加1。

将1加到上述二进制方程式中:

11111111-10110001 +1

你得到了什么?这个:

100000000-10110001

这是最终方程式。通过执行这两个步骤,您将找到最终的区别:从另一个二进制数减去一个二进制数后再减去一个二进制数,该二进制数具有一个额外的数字,并且除了最高有效位位置之外,还包含零。

但是,为什么我们真的渴望这种差异呢?好吧,从现在开始,我想如果您阅读Wikipedia文章会更好。


0

我们仅对加法和减法执行加法运算。我们将第二个操作数添加到第一个操作数以进行加法运算。对于减法,我们将第二个操作数的2的补码添加到第一个操作数。

使用2的补码表示,我们不需要单独的数字分量进行减法运算,仅使用加法器和补码器。


0

值得注意的是,在一些早期添加的机器上,在数字计算机时代之前,减法运算是通过让操作员在每个键上使用不同颜色的图例集输入值(因此每个键将输入9减去要减去的数字)。减去),然后按特殊按钮将在计算中采用进位。因此,在六位数字的机器上,要从一个值中减去1234,操作员将按通常会指示“ 998,765”的键,然后按一个按钮以将该值加1进行计算。二进制补码算法只是先前的“十进制补码”算法的二进制等效项。


0

用补码法进行减法的优点是降低了硬件的
复杂性,不需要使用不同的数字电路进行加法和减法,加法和减法都仅由加法器执行。


0

此处尚未提及的二进制补码表示法的主要优点是,二进制补码的和,差或乘积的低位取决于操作数的相应位。其原因,对于8位有符号值-1是11111111是减去任何其最低8位是整数00000001从任何其他整数,其最低8位是0000000将产生一个整数,其最低8位是11111111。从数学上来说,值-1是一个无限的1字符串,但是在特定整数类型范围内的所有值都将是某个点之后的全1或全0,因此对于计算机“符号扩展”数字的最高有效位,就好像它代表1或0的无限个数一样。

二进制补码几乎是唯一一种在处理大于二进制机器自然字长的类型时能很好工作的带符号数表示形式,因为在执行加法或减法时,代码可以获取每个操作数的最低块,计算出结果,并将其存储,然后加载每个操作数的下一个块,计算结果的下一个块,并将其存储,依此类推。因此,即使是需要所有加法和减法都要通过单个8位寄存器的处理器可以合理有效地处理32位带符号数字(当然比32位寄存器要慢,但仍然可行)。

当使用C标准允许的任何其他带符号的表示形式时,结果的每一位都可能受到操作数的任何位的影响,因此有必要一次将整个值保存在寄存器中,否则必须进行额外的计算至少在某些情况下,需要读取,修改和重写结果的每个块的步骤。


请在段落中格式化您的答案,并将代码标记为code,这样可读性更好,您会获得很高的评价。
Suraj Jain

@SurajJain:更好吗?
超级猫

是的,比以前更好,我想问一件事,有符号字符a = 1和无符号字符a = 1有什么区别,它们在内存中的表示方式是什么。
Suraj Jain

@SurajJain:在“ char”小于“ int”的二进制补码系统(即绝大多数系统)上,有符号和无符号字符类型的行为相同,不同之处在于有符号类型在读取和无符号类型时将被符号扩展惯于。在这样的系统上,将值194或-62存储到有符号的char中将写入与将194或-62存储到无符号的char中相同的位模式(即11000010)。从签名的char读取该位模式将产生-62,而从一个未签名的char读取将产生194。–
supercat

符号扩展的手段?
Suraj Jain

0

有不同类型的表示形式,分别是:

  1. 无符号数字表示
  2. 签名号码表示
  3. 一个人的补语
  4. 补码表示

-无符号数字表示,仅用于表示正数

-带符号的数字表示形式,用于表示正数和负数。在带符号的数字表示中,MSB位表示符号位,其余的位表示数字。当MSB为0时,数字为正;当MSB为1时,数字为负。

有符号数字表示的问题是0有两个值。

一个补码表示的问题是0有两个值。

但是,如果我们使用二进制补码表示,那么0只会有一个值,这就是为什么我们用二进制补码表示负数的原因。

资料来源:为什么负数以二进制补码形式存储


-1

对于为什么使用Two2的补码而不是One的补码系统来表示负数的一个令人满意的答案是,Two的补码系统解决了在代表负数的One的补码系统中存在的0多重表示以及到位进位的问题。数字。

有关更多信息,请访问https://en.wikipedia.org/wiki/Signed_number_representations

对于“随身携带”,请访问 https://en.wikipedia.org/wiki/End-around_carry


实际上,如果您有一个小数点,并且明确说明了所有位是什么:“ 0..0000.1111..1”表示所有最左边的未声明位均为0,所有最右边的未声明位均为1,因此“ ..1”表示触发进位。因此(机械上)为“ 0.0001.0000..0”。这意味着“ 1..1111.1111..1”等于零!这也意味着要取整数,您实际上只需要翻转其位即可。但现在它适用于可表示的分数。
罗布
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.