在我正在阅读的一本书中,以下函数用于确定2的补码整数乘法溢出。
int tmult_ok(int x, int y) {
int p = x*y;
return !x || p/x == y;
}
虽然这有效,但如何在所有情况下证明其正确性? 当出现溢出时,如何确保p!= x * y?
以下是我的理解:
答案 0 :(得分:4)
tmult_ok(x, y)
在x*y
溢出时随时int p = x*y;
失败,因为未定义的行为(UB)。
出于同样的原因,它也失败了像tmult_ok(INT_MIN, -1)
这样的角落案例
它不适合工作"。
替代(和others for /,-,+)不依赖于2的补码。请注意,这会返回tmult_ok()
的反面。
int is_undefined_mult1(int a, int b) {
if (a > 0) {
if (b > 0) {
return a > INT_MAX / b; // a positive, b positive
}
return b < INT_MIN / a; // a positive, b not positive
}
if (b > 0) {
return a < INT_MIN / b; // a not positive, b positive
}
return a != 0 && b < INT_MAX / a; // a not positive, b not positive
}
如果发生溢出,如何确保p!= x * y?
便携式代码不能。使用C中的有符号整数数学,溢出是UB。代码应该在没有先执行乘法的情况下检测潜在的溢出。 @Quentin @Eugene Sh.
如何在所有情况下证明其正确性?
使用2倍宽的数学形成参考测试。如果int
为32位,则使用64位数学运算将tmult_ok()
与乘法进行比较,并查看产品是否在范围内。 @rici
int tmult_ok_ll(int x, int y) {
long long prod = x;
prod *= y;
return (prod >= INT_MIN && prod <= INT_MAX);
}
尝试所有组合是一种蛮力方法 - 对于32位int
来说可能太长了。
尝试所有组合的子集,对于每个x,y
,尝试INT_MIN, INT_MIN-1, INT_MIN-2, -2,-1, 0, 1, 2, , INT_MAX-1, INT_MAX
。 (10 * 10测试)
也是sqrt(INT_MAX)
中每个值+/-的所有组合的子集。 (10 * 10测试)
int
范围内的几百万随机值也是谨慎的。
这可能还不够,但是如果代码通过了这个,那么剩下很少的角落情况 - 这非常依赖于您的源代码。
答案 1 :(得分:0)
应该检查可能溢出的操作是否会溢出
int tmult_ok(int x, int y) {
if (MAX_INT / y >= x)
//throw somthing
return x*y;
}
答案 2 :(得分:0)
您可以查看溢出标记(https://en.wikipedia.org/wiki/Overflow_flag)
在计算机处理器中,溢出标志(有时称为V标志)通常是系统状态寄存器中的单个位,用于指示何时在操作中发生算术溢出,表明带符号的二进制补码结果不适合用于操作的位数(ALU宽度)。