【问题标题】:How do you detect 2's complement multiplication overflow?如何检测 2 的补码乘法溢出?
【发布时间】:2018-06-04 15:37:13
【问题描述】:

在我正在阅读的一本书中,以下函数用于确定 2 的补码整数乘法溢出。

int tmult_ok(int x, int y) {
int p = x*y;
return !x || p/x == y;
}

虽然这可行,但我如何在所有情况下证明其正确性? 溢出时如何确保 p != x*y ?

这是我的理解:

  1. 当您将 2 个大小为“w-bits”的整数相乘时,结果可能是 2w 位。
  2. 计算截断高阶 w 位。所以我们剩下的是低阶 w 位。 3.假设 P = 最低 w-bits
  3. 然后,我们需要证明 (P/x != y) 和 (P/y !=x) 存在溢出时。
  4. 我的困惑就在这里。如果没有溢出,你怎么能说 (P/x !=y)? P的位模式除以x时是否有可能产生y,即使发生溢出?

【问题讨论】:

标签: c twos-complement


【解决方案1】:

tmult_ok(x, y) 在int p = x*y; 溢出的任何时候都会失败,因为这是未定义的行为 (UB)。
出于同样的原因,它也会像tmult_ok(INT_MIN, -1) 这样的极端情况失败。
它不能便携地“工作”。

不依赖于 2 的补码的替代方案(和 others for /,-,+)。请注意,这将返回 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 位,则将 tmult_ok() 与使用 64 位数学的乘法进行比较,并查看乘积是否在范围内。 @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) 的 2 以内。 (10*10 次测试)

int 范围内的几百万个随机值也是谨慎的。

这可能还不够,但如果代码通过了这个,那么剩下的极端情况就很少了——这非常依赖于您的源代码。

另见@Eric Postpischil

【讨论】:

  • 您可以用 16 位整数进行详尽的测试,这将是 32 位整数算法的合理练习。但我希望这种简单的算法容易得到形式证明。
【解决方案2】:

类似下溢的溢出是未定义的行为,这意味着编译器可以认为它们永远不会发生并简化依赖它的代码。因此您无法检测是否已经发生溢出

应该检查一个可能溢出的操作是否会溢出

int tmult_ok(int x, int y) {
  if (MAX_INT / y >= x)
    //throw somthing
  return x*y;
}

【讨论】:

  • 仅适用于同一标志 x 和 y。也应该是&gt;=(但应该检查一些极端情况......)。
【解决方案3】:

您可以检查溢出标志(https://en.wikipedia.org/wiki/Overflow_flag)

在计算机处理器中,溢出标志(有时称为 V 标志)通常是系统状态寄存器中的单个位,用于指示运算中何时发生算术溢出,指示带符号的二进制补码结果不适合用于操作的位数(ALU 宽度)。

如何访问它的示例:https://www.linuxquestions.org/questions/programming-9/c-how-to-check-the-overflow-flag-930420/#post4612830

【讨论】:

  • 即使程序在带有这样一个标志的系统上执行,也不能保证编译器执行与设置溢出标志的指令的乘法。这个想法还有其他问题,例如确保溢出标志的读取立即跟随乘法指令(因为编译器可能会重新排序)。
  • 要对此进行扩展,@yoones 应该意识到他链接的示例也使用程序集来添加数字。如果你开始混合编译后的代码和汇编代码,你永远不知道你是否得到了你认为没有经过严格测试的东西,即使这样,新的编译器也可能会做出不同的决定,从而破坏你的旧解决方案。
  • 解决方案是使用编译器特定的函数,例如 GCC 的overflow-checking intrinsics。
猜你喜欢
  • 2011-02-12
  • 2015-12-24
  • 1970-01-01
  • 1970-01-01
  • 2014-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多