二进制补码意味着简单地反转一个数字的所有位我得到了-i-1:
~0是-1
~01000001是10111110
~65是-66
等。要切换整数的符号,我必须使用实际的减号。
int i = 65; int j = -i;
cout << j; // -65
在哪里定义了实际行为,并且其责任是确保二进制补码模式(使数字为负反转所有位并加1)?我甚至不知道这是硬件还是编译器操作。
答案 0 :(得分:4)
通常由CPU硬件完成。
某些CPU具有计算数字负数的指令。在x86架构中,它是NEG
指令。
如果不是,可以使用乘法运算符,将数字乘以-1
来完成。但是,许多程序员利用您发现的身份,补充数字,然后添加1.参见
答案 1 :(得分:3)
原因很简单:与0和添加的一致性。
你想要添加对正数和负数的工作相同没有特殊情况......特别是,递增-1乘1必须得0。
经典溢出增量产生值0的唯一位序列是全1位序列。如果增加1,则全部为零。这就是你的-1:全1,即0的按位否定。 现在我们有(假设8位整数,每行递增1)
-2: 11111110 = ~1
-1: 11111111 = ~0
0: 00000000 = ~-1
+1: 00000001 = ~-2
如果你不喜欢这种行为,你需要另外处理特殊情况,你将拥有+0和-0。最有可能的是,这样的CPU会慢得多。
如果您的问题是如何
int i = -j;
实现了,这取决于您的编译器和CPU以及优化。通常它将与您指定的其他操作一起进行优化。但如果最终以
的形式执行,请不要感到惊讶int i = 0 - j;
因为这可能需要1-2个cpu ticks来计算(例如,作为一个XOR
或一个寄存器到自己得到0,然后一个SUB
操作来计算0-j
),它几乎不会成为瓶颈。加载j
并将结果i
存储在内存中的位置将要贵很多。实际上,有些CPU(MIPS?)甚至还有一个始终为零的内置寄存器。然后你不需要一个特殊的否定指令,你只需从$zero
中减去j,通常是1个刻度。
据说当前的英特尔CPU识别这样的xor opersions并以0刻度执行它们,具有寄存器重命名优化(即它们让下一条指令使用一个零的新寄存器)。您在amd64上有neg
,但快速xor rax,rax
在其他情况下也很有用。
答案 2 :(得分:2)
C算术是根据值定义的。当代码是:
int i = 65;
int j = -i;
无论位代表如何,编译器都会发出所需的任何CPU指令,以j
-65
的值。
历史上,并非所有系统都使用了2的补充。 C编译器将根据CPU的功能选择一个负数系统,从而为目标CPU提供最有效的输出。
然而2的补码是一个非常常见的选择,因为它导致了最简单的算术算法。例如,相同的指令可用于带有符号和无符号整数的+
-
*
。