此技术适用于任何大小的整数,但我将使用 8 位字节大小的整数来解释,因为数字更小且更易于使用。
一个 8 位类型有 28 = 256 个可能的值。在低级别,这些只是位,有符号与无符号是我们如何解释这些位的问题。当解释为无符号整数时,它们的范围是 0 到 255。当解释为有符号 two's complement 整数时,它们的范围是 -128 到 +127。
类型的数字线如下所示:
请注意,从 0 到 127 的正数可以用有符号和无符号类型表示,它们用完全相同的位模式表示(00000000 到 01111111)。
在无符号解释中表示从 128 到 255 的大正数的位模式在有符号解释中被重用于数字 -128 到 -1。就好像有人拿走了无符号的数字线,把范围的上半部分切掉,然后粘在线的下端。
现在,让我们看看当我们比较一对整数时会发生什么。
案例 1:两个值都在有符号正数范围内
这两个值都在 0 到 127 的范围内,无论这些位被解释为有符号还是无符号,它们都具有相同的数值。
我们无条件地将 MIN_VALUE 添加到每个值。我们的有符号字节类型的 MIN_VALUE 是 -128,因此添加这意味着我们实际上是 减去 128。
一个例子:我们的比较函数,使用有符号类型,给定 x = 20 和 y = 60。加上 MIN_VALUE,我们得到 x' em> = 20 - 128 = -108 和 y' = 60 - 128 = -68:
将 MIN_VALUE 添加到正值将始终将其映射到负值。在范围的最末端,0 将变为 -128,而 127 将变为 -1。该操作不会改变 x 和 y 相对于彼此的顺序,因此 x' 和 之间的任何比较的结果y' 将与我们没有添加 MIN_VALUE 相同,这是正确的。
案例 2:两个值都在有符号负数范围内
在这种情况下,如果解释为有符号,则两个值都在 -128 到 -1 的范围内。如果解释为无符号,则它们在 128 到 255 的范围内(大于 256)。
当我们无条件地将 MIN_VALUE 添加到每个带符号的负值时,它总是会导致溢出和回绕到带符号的正值。在数值上,这种环绕与添加 256 相同。如果给定 x = -35 和 y = -80 进行比较,我们得到 x' = -35 - 128 + 256 = 93 和 y' = -80 - 128 + 256 = 48:
我们也可以用 -35 和 -80 的无符号解释来可视化这一点,即 221 和 176。当减去 128 时,我们得到 x' 和 y 完全相同的结果'。二进制补码的优点之一是,无论您将数据视为有符号还是无符号,加法和减法都会给出相同的结果,因此 CPU 可以使用相同的电路。
与案例 1 一样,该操作不会更改两个数字之间的任何比较结果。我们的 x 大于 y(负值较小),并且 x' 也大于 y'。所以这些输入之间的比较是正确的。
案例3:一个值在有符号正数范围内,另一个负数
这是一个有趣的案例。请注意,当我们添加 MIN_VALUE 时,它总是会更改数字的符号。正值映射到负值,负值映射到正值。
让我们比较 x = -35 和 y = 60。由于我们希望将它们作为无符号比较,我们真的希望 x被解释为 -35 + 256 = 221。所以 x 需要被解释为大于 y,即使我们的签名数据类型通常不会这样做。
由于数字有相反的符号,改变符号的 MIN_VALUE 操作将反转数字在数字行上的顺序。 x' = -35 - 128 + 256 = 93,y' = 60 - 128 = -68 .所以我们得到 x' 大于 y',这就是我们想要的:
概括
由于我们已经处理了正负的所有组合,我们知道该技术适用于所有可能的值。
对于 32 位整数,范围更大(有符号范围是 -2,147,483,648 (MIN_VALUE) 到 +2,147,483,647,无符号范围是 0 到 4,294,967,295),但它的工作原理是一样的。事实上,它适用于每种大小的整数,适用于每种编程语言,前提是:
有符号整数使用二进制补码表示(几乎是通用的)。
加法在溢出时回绕(而不是引发错误或提升为更大的数字类型或未定义)。
您也可以反过来:如果您只有一个无符号整数类型,并且想要进行二进制补码有符号比较,请将有符号最小值添加(无符号解释)到每个数字。
由于该技术只是两个无条件的加法运算,因此即使没有经过编译器或 VM 的特殊处理,它也非常有效。