如何检查A+B是否超过long long的范围?(A和B都是long long类型)

22
我有两个数字: AB,需要在我的代码中计算A+B。这两个数都是long long类型的,可以是正数或负数
我的代码运行错误,我怀疑问题出现在计算A+B时。我只想检查A+B是否超过了long long类型的范围。所以任何方法都可以接受,因为我只用它来进行调试。

如果((A <0 && B <0 && A + B> 0)||(A> 0 && B> 0 && A + B <0)){/溢出/} 只有当A和B具有相同的符号时,才可能发生溢出。如果它们确实如此,并且A + B没有相同的符号,则存在溢出问题。另外,由于它在恒定时间内执行并且仅执行一次加法,因此这基本上是最快的方法。 - FrankieTheKneeMan
2
你应该将其发布为答案。顺便说一下,当A=B=0x8000000000000000时,存在一个边界情况,此时A+B=0,而您的代码无法正常工作:p - riv
1
@FrankieTheKneeMan 整数溢出是未定义行为。 - john
@John 这就是为什么我将其提交为评论的原因。我的答案适用于大多数编译器,并且足以进行第一步调试。 - FrankieTheKneeMan
1
@AlexeyFrunze,你提到的所谓重复问题涉及无符号类型,在所有情况下都有明确定义的行为。而有符号整数在某些情况下并没有明确定义的行为。 - Oswald
@Oswald,“被指称的重复链接”指向IntSafeSafeInt,它们包含了无符号和有符号类型的函数。 - Alexey Frunze
4个回答

31

只有两个数字的符号相同才可能发生溢出。如果两个数字都是正数,那么在数学上,当 A + B > LLONG_MAX 或等价的 B > LLONG_MAX - A 时会发生溢出。由于右边是非负数,后者已经意味着 B > 0。类似的论证表明,在负情况下我们也不需要检查B的符号(感谢Ben Voigt指出无需检查B的符号)。然后你可以检查

if (A > 0) {
    return B > (LLONG_MAX - A);
}
if (A < 0) {
    return B < (LLONG_MIN - A);
}
return false;

为了检测溢出,这些计算因为初始的检查不会发生溢出。

检查 A + B 的结果的符号可以适用于带有溢出整数计算的保证环绕语义。但是,有符号整数溢出是未定义行为,即使在实现了环绕的CPU上,编译器也可能假设没有未定义的行为发生,并在实现时完全删除溢出检查。因此,在问题的评论中建议的检查非常不可靠。


4
仅仅检查符号并不能保证其有效性,而且优化器会利用这种不确定性(例如,我刚刚检查了gcc将a + 42 > a 优化为 true)。 - AProgrammer
如果你将它包装在一个单一的表达式中,而不是到处抛出return,那么这将更易读。例如,像return a < 0 != b < 0 || (a < 0 ? b > LLONG_MIN - a : b < LLONG_MAX - a);这样的东西。 - James Kanze
16
我觉得将其分解为不同情况更易读,但这当然只是我的偏好。 - Daniel Fischer
10
就我所知,我同意Daniel的样式选择。我认为在一个7行函数中,if语句中的返回值并不是“隐式”的,特别是在这种情况下并没有隐藏什么。 - Sebastian Redl
1
我认为将其分解为几个单独的步骤更清晰,因为它明确地分离了每种情况。因为出于对多个返回值的敌意而武断地将所有内容塞入一个语句中从来没有任何好处;而用retVal = blah替换所有中间的return blah行也同样糟糕。 - Dan Is Fiddling By Firelight
显示剩余3条评论

7

以下是类似以下内容的事项:

long long max = std::numeric_limits<long long>::max();
long long min = std::numeric_limits<long long>::min();

if(A < 0 && B < 0) 
    return B < min - A;
if(A > 0 && B > 0)
    return B > max - A;

return false;

我们可以这样推理:
  • 如果AB符号相反,则它们不会溢出 - 大于零的数必须大于max,小于零的数必须小于min

  • 在其他情况下,简单的代数就足够了。A + B > max => B > max - A将在它们都是正数时溢出。否则,如果它们都是负数,则A + B < min => B < min - A


B > 0B < 0 的测试是多余的。 - Ben Voigt

3

此外,如果您只是用于调试,您可以使用以下“hack”直接从最后一个操作中读取溢出位(假设您的编译器/ CPU支持此功能):

int flags;
_asm {
    pushf       // push flag register on the stack
    pop flags   // read the value from the stack
}
if (flags & 0x0800) // bit 11 - overflow
    ...

进位标志对于无符号数很有意思。对于有符号数,您必须检查溢出标志。 - fredoverflow

2

掩码标记符号,转换为无符号值,然后进行加法运算。如果结果大于1 << (sizeof(int) * 8 - 1),则发生溢出。

int x, y;
if (sign(x) == sign(y)){
    unsigned int ux = abs(x), uy = abs(y);    
    overflow = ux + uy >= (1 << (sizeof(int) * 8 - 1));
}

更好的方式是,我们编写一个模板:
template <typename T> 
bool overflow(signed T x, signed T y){
    unsigned T ux = x, uy = y;
    return ( sign(x) == sign(y) && (ux + uy >= (1 << (sizeof(T) * 8 - 1)));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接