您在加法中检测到溢出x + yComplement
,而不是在整体减法中
-INT_MIN
本身在 2 的补码中溢出;INT_MIN == -INT_MIN
. 这是2 的补码异常1。
INT_MIN
您应该对任何负数(除)减号进行快速正溢出检测INT_MIN
。结果加法将有符号溢出。例如-10 + INT_MIN
溢出。
http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt有一个用于加法和减法的输入/输出符号表。溢出的情况是输入符号相反但结果符号匹配y
。
SUBTRACTION SIGN BITS (for num1 - num2 = sum)
num1sign num2sign sumsign
---------------------------
0 0 0
0 0 1
0 1 0
*OVER* 0 1 1 (subtracting a negative is the same as adding a positive)
*OVER* 1 0 0 (subtracting a positive is the same as adding a negative)
1 0 1
1 1 0
1 1 1
您可以直接将其与原始x
and一起y
使用,并且仅用yComplement
作获取minusResult
. 调整你的逻辑以匹配这个真值表。
或者您可以使用int ySign = (~y) >> 31;
并保留其余代码未修改。(使用 tmp 保持~y
,因此您只执行一次操作,为此和yComplement
)。1 的补码逆 ( ~
) 不会受到 2 的补码异常的影响。
脚注 1:符号/幅度和一个补码有两种冗余方式来表示 0,而不是没有逆向的值。
有趣的事实:如果你做一个整数绝对值函数,你应该考虑结果unsigned
来避免这个问题。 int
不能代表绝对值INT_MIN
。
效率提升:
如果您使用unsigned int
,则在班次后不需要& 1
,因为逻辑班次不会符号扩展。(作为奖励,它将避免 C 签名溢出未定义行为+
:http ://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html )。
然后(如果您使用uint32_t
或sizeof(unsigned) * CHAR_BIT
代替 31)您将拥有 2 的补码比较的安全且可移植的实现。(负数的符号移位语义是在 C 中实现定义的。)我认为您将 C 用作位操作的一种伪代码,并且对实际编写可移植实现不感兴趣,这很好。您做事的方式将适用于普通 CPU 上的普通编译器。
或者您可以使用& 0x80000000
将高位保留在原位(但是您必须将!
结果左移)。
这只是实验室的限制,你不能使用无符号或任何大于 0xff(255) 的常数
好的,所以您无法访问逻辑右移。不过,您最多需要一个&1
. 可以使用您只关心低位但其余部分包含垃圾的数字。
你最终会这样做& !ZF
,要么&0
或 &1 . Thus, any high garbage in
OF` 被抹去。
您还可以延迟>> 31
直到将两个数字异或之后。
这是一个我想优化自己的有趣问题:
// untested, 13 operations
int isGreater_optimized(int x, int y)
{
int not_y = ~y;
int minus_y = not_y + 1;
int sum = x + minus_y;
int x_vs_y = x ^ y; // high bit = 1 if they were opposite signs: OF is possible
int x_vs_sum = x ^ sum; // high bit = 1 if they were opposite signs: OF is possible
int OF = (x_vs_y & x_vs_sum) >> 31; // high bits hold garbage
int SF = sum >> 31;
int non_zero = !!sum; // 0 or 1
return (~(OF ^ SF)) & non_zero; // high garbage is nuked by `& 1`
}
请注意使用~
代替!
来反转具有高垃圾的值。
看起来在与 SF 分开计算 OF 时仍然存在一些冗余,但实际上两次 sum 的 XORing 并没有抵消。 x ^ sum
是 的输入&
,然后我们与 sum 进行异或。
不过,我们甚至可以推迟轮班,并且通过避免额外的反转,我发现了更多优化。 这是11个操作
// replace 31 with sizeof(int) * CHAR_BIT if you want. #include <limit.h>
// or use int32_t
int isGreater_optimized2(int x, int y)
{
int not_y = ~y;
int minus_y = not_y + 1;
int sum = x + minus_y;
int SF = sum; // value in the high bit, rest are garbage
int x_vs_y = x ^ y; // high bit = 1 if they were opposite signs: OF is possible
int x_vs_sum = x ^ sum; // high bit = 1 if they were opposite signs: OF is possible
int OF = x_vs_y & x_vs_sum; // low bits hold garbage
int less = (OF ^ SF);
int ZF = !sum; // 0 or 1
int le = (less >> 31) & ZF; // clears high garbage
return !le; // jg == jnle
}
我想知道是否有任何编译器可以通过本手册比较并将其优化为cmp edi, esi
/ setg al
,但没有这样的运气:/ 我猜这不是他们寻找的模式,因为本来可以编写的代码x > y
往往是这样编写的:P
但无论如何,这里是来自 gcc 的 x86 asm 输出和 Godbolt 编译器资源管理器上的 clang。