谁定义了整数的符号反转模式?
Who defines the sign inversion pattern for integers?
二进制补码意味着简单地反转数字 i 的所有位得到 -i-1:
~0 是-1
~01000001 为 10111110
~65为-66
等要切换整数的符号,我必须使用实际的减号。
int i = 65; int j = -i;
cout << j; // -65
实际行为在哪里定义,谁负责确保遵循二进制补码模式(使数字为负,反转所有位并加 1)?我什至不知道这是硬件操作还是编译器操作。
通常由 CPU 硬件完成。
有些 CPU 有计算负数的指令。在 x86 架构中,它是 NEG
指令。
如果不是,可以使用乘法运算符将数字乘以 -1
来完成。但是很多程序员利用你发现的身份,补数然后加1。见
How to convert a positive number to negative in assembly
原因很简单:与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 个滴答来计算(例如,作为一个 XOR
或一个寄存器到自身以获得 0,然后一个 SUB
操作来计算 0-j
),它几乎不会成为瓶颈。加载 j
并将结果 i
存储在内存中的某处会花费很多很多。事实上,一些 CPUs (MIPS?) 甚至有一个始终为零的内置寄存器。然后你不需要特殊的否定指令,你只需在通常 1 个滴答中从 $zero
中减去 j。
目前的英特尔 CPUs 据说可以识别这种异或运算并在 0 个滴答中完成它们,并进行寄存器重命名优化(即它们让下一条指令使用一个新的零寄存器)。您在 amd64 上有 neg
,但快速 xor rax,rax
在其他情况下也很有用。
C 算法是根据值定义的。当代码为:
int i = 65;
int j = -i;
无论位表示形式如何,编译器都会发出为 j
提供 -65
值所需的任何 CPU 指令。
历史上,并非所有系统都使用 2 的补码。 C 编译器会根据 CPU 的功能选择负数系统,从而为目标 CPU 提供最有效的输出。
然而,2 的补码是一个非常常见的选择,因为它会导致最简单的算术算法。例如,相同的指令可用于 +
-
*
有符号和无符号整数。
二进制补码意味着简单地反转数字 i 的所有位得到 -i-1:
~0 是-1
~01000001 为 10111110
~65为-66
等要切换整数的符号,我必须使用实际的减号。
int i = 65; int j = -i;
cout << j; // -65
实际行为在哪里定义,谁负责确保遵循二进制补码模式(使数字为负,反转所有位并加 1)?我什至不知道这是硬件操作还是编译器操作。
通常由 CPU 硬件完成。
有些 CPU 有计算负数的指令。在 x86 架构中,它是 NEG
指令。
如果不是,可以使用乘法运算符将数字乘以 -1
来完成。但是很多程序员利用你发现的身份,补数然后加1。见
How to convert a positive number to negative in assembly
原因很简单:与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 个滴答来计算(例如,作为一个 XOR
或一个寄存器到自身以获得 0,然后一个 SUB
操作来计算 0-j
),它几乎不会成为瓶颈。加载 j
并将结果 i
存储在内存中的某处会花费很多很多。事实上,一些 CPUs (MIPS?) 甚至有一个始终为零的内置寄存器。然后你不需要特殊的否定指令,你只需在通常 1 个滴答中从 $zero
中减去 j。
目前的英特尔 CPUs 据说可以识别这种异或运算并在 0 个滴答中完成它们,并进行寄存器重命名优化(即它们让下一条指令使用一个新的零寄存器)。您在 amd64 上有 neg
,但快速 xor rax,rax
在其他情况下也很有用。
C 算法是根据值定义的。当代码为:
int i = 65;
int j = -i;
无论位表示形式如何,编译器都会发出为 j
提供 -65
值所需的任何 CPU 指令。
历史上,并非所有系统都使用 2 的补码。 C 编译器会根据 CPU 的功能选择负数系统,从而为目标 CPU 提供最有效的输出。
然而,2 的补码是一个非常常见的选择,因为它会导致最简单的算术算法。例如,相同的指令可用于 +
-
*
有符号和无符号整数。