【问题标题】:How do I detect overflow while multiplying two 2's complement integers?如何在将两个 2 的补码整数相乘时检测溢出?
【发布时间】:2011-02-12 10:14:25
【问题描述】:

我想将两个数字相乘,并检测是否存在溢出。最简单的方法是什么?

【问题讨论】:

标签: algorithm overflow integer-overflow twos-complement


【解决方案1】:

两个 32 位数字相乘得到 64 位结果,两个 8 得到 16,等等。二进制乘法只是移位和加法。因此,如果您在操作数 A 中设置了两个 32 位操作数和第 17 位,并且在操作数 b 中设置了高于 15 或 16 的任何位,您将溢出 32 位结果。第 17 位左移 16 位是第 33 位加到 32 上。

所以问题再次是您的输入的大小和结果的大小,如果结果大小相同,那么您必须找到两个操作数中最重要的 1,如果结果更大,则添加这些位位置比你的结果空间会溢出。

编辑

是的,如果加法中有进位,则将两个 3 位数相乘将得到 5 位数或 6 位数。同样,2 位和 5 位可能会导致 6 位或 7 位等。如果此问题发布者问题的原因是查看您的结果变量中是否有空间来回答,那么此解决方案将起作用,并且对于大多数人来说相对较快大多数处理器上的语言。它可以在某些方面明显更快,而在其他方面则明显更慢。仅查看操作数中的位数通常很快(当然取决于它的实现方式)。如果您可以在您的语言或处理器中做到这一点,那么将最大操作数的大小加倍是一个安全的选择。除法是彻头彻尾的昂贵(缓慢),并且大多数处理器在操作数大小任意加倍时并没有少得多。最快的当然是放到汇编程序中进行乘法并查看溢出位(或将结果寄存器之一与零进行比较)。如果您的处理器无法在硬件中进行乘法运算,那么无论您做什么,它都会变慢。我猜想 asm 不是这篇文章的正确答案,尽管它是迄今为止最快的并且具有最准确的溢出状态。

与十进制相比,二进制使乘法变得微不足道,例如取二进制数

0b100 * 0b100

就像学校里的十进制数学一样,你(可以)从低位操作数的最低有效位开始,并将它与高位操作数中的所有位置相乘,除了二进制,只有两个选择你乘以零意味着你不必须添加到结果中,或者您乘以一,这意味着您只需移位和添加,不需要像十进制那样实际乘法。

000 : 0 * 100 000 : 0 * 100 100 : 1 * 100

将列加起来,答案是 0b10000

与十进制数学相同,百列中的 1 表示复制顶部数字并添加两个零,在任何其他基数中也一样。所以 0b100 乘以 0b110 是 0b1000,第二列中的一个 1 复制并在第三列中添加一个零 + 0b10000 一个 1,因此复制并添加两个零 = 0b11000。

这导致查看两个数字中的最高有效位。 0b1xx * 0b1xx 保证将 1xxxx 添加到答案中,这是添加中的最大位位置,最终添加中没有其他单个输入填充了该列或更重要的列。从那里你只需要更多的位,以防其他位相加导致进位。

这发生在最坏的情况下,全1乘以全1,0b111 * 0b111

0b00111 + 0b01110 + 0b11100

这会在加法中产生一个进位位,从而产生 0b110001。 6 位。 3 位操作数乘以 3 位操作数 3+3=6 6 位最坏情况。

因此,使用最高有效位的操作数的大小(而不是保存值的寄存器的大小)决定了最坏情况下的存储要求。

好吧,假设操作数为正,这是正确的。如果您认为其中一些数字是负数,它会改变一些事情,但不会改变太多。

减 4 乘以 5,0b1111...111100 * 0b0000....000101 = -20 或 0b1111..11101100

表示负 4 需要 4 位,表示正 5 需要 4 位(不要忘记符号位)。如果去掉所有符号位,我们的结果需要 6 位。

让我们看看 4 位的极端情况

-8 * 7 = -56 0b1000 * 0b0111 = 0b1001000 -1 * 7 = -7 = 0b1001 -8 * -8 = 64 = 0b01000000 -1 * -1 = 2 = 0b010 -1 * -8 = 8 = 0b01000 7 * 7 = 49 = 0b0110001

假设我们将正数计为最重要的 1 加一,负数计为最重要的 0 加一。

-8 * 7 是 4+4=8 位实际 7 -1 * 7 是 1+4=5 位,实际是 4 位 -8 * -8 是 4+4=8 位,实际是 8 位 -1 * -1 是 1+1=2 位,实际是 3 位 -1 * -8 是 1+4=5 位,实际是 5 位 7 * 7 是 4+4=8 位,实际是 7 位。

所以这条规则是有效的,除了-1 * -1,你可以看到我称a减一一位,对于加一的东西找到零加一。无论如何,我认为如果这是定义的 4 位 * 4 位机器,您至少会有 4 位结果,我将问题解释为我需要多于 4 位才能安全地存储答案。因此,此规则用于回答 2s 补码数学的问题。

如果您的问题是准确确定溢出,然后速度是次要的,那么,对于某些系统来说,对于您所做的每一次乘法,它都会非常慢。如果这是您要问的问题,要恢复一些速度,您需要针对语言和/或处理器对其进行更好的调整。如果可以的话,将最大的操作数加倍,并检查结果大小以上的非零位,或使用除法和比较。如果您不能将操作数大小加倍,请除以比较。在除法之前检查零。

实际上,您的问题也没有指定您正在谈论的溢出大小。好旧的 8086 16 位乘以 16 位给出 32 位结果(硬件),它永远不会溢出。一些 ARM 有一个乘法,32 位乘以 32 位,32 位结果,容易溢出。这个问题的操作数的大小是多少,它们是相同的大小还是输入大小的两倍?您是否愿意执行硬件无法执行的乘法(不溢出)?您是否正在编写编译器库并试图确定是否可以将操作数提供给硬件以提高速度,或者您是否必须在没有硬件乘法的情况下执行数学运算。如果你转换操作数,你会得到什么样的东西,编译器库会在进行乘法之前尝试将操作数转换回去,当然这取决于编译器及其库。并且它将使用计数位技巧确定使用硬件乘法还是软件乘法。

我的目标是展示二进制乘法如何以易于理解的形式工作,这样您就可以通过查找每个操作数中单个位的位置来了解您需要多少最大存储空间。现在,您可以多快找到每个操作数中的那个位就是诀窍。如果您正在寻找最小存储要求而不是最大存储要求,那是另一回事,因为涉及两个操作数中的每一个有效位,而不仅仅是每个操作数一位,您必须进行乘法运算以确定最小存储空间。如果您不关心最大或最小存储空间,则只需进行乘法并查找超出定义的溢出限制的非零值,或者如果您有时间或硬件,请使用除法。

您的标签暗示您对浮点不感兴趣,浮点是完全不同的野兽,您不能将这些定点规则中的任何一个应用于浮点,它们不起作用。

【讨论】:

  • 您不能仅通过查看每个数字的最高有效 1 位来判断溢出:100_2 * 100_2 = 10000_2 不会溢出 5 位值,但 111_2 * 111_2 = 110001_2 会溢出。
  • true,在添加之前您很容易检测到溢出。如果有进位,则添加可以再创建一个非零位,因此如果 msbit 的东西几乎不适合,那么仍然有机会。
【解决方案2】:

检查一个是否小于除以另一个的最大值。 (所有值均视为绝对值)。

2 的互补性几乎与它无关,因为如果 x*(2n - x)>2M 等于 (x *2n - x2)>2M,或 x2 n - 2M),所以无论如何你都必须比较溢出的数字(x2 可能会溢出,而结果可能不会)。

【讨论】:

  • 2 的补码会在一个因子为负而另一个为正时产生差异,因为结果可能是 MIN_VALUE,其绝对值比 MAX_VALUE 大一。因此,您需要对每个符号组合进行单独比较。而且我看不到您的x*(2**n-x)>2**M 示例来自哪里。
  • @Christian,我的示例来自尝试将所有可以用位筛选计算的东西归为不等式的一部分,而将其他所有内容归为另一部分。
【解决方案3】:

如果您的数字不是来自最大的整数数据类型,那么您可以将它们向上转换,乘以并与数字的原始类型的最大值进行比较。例如。在 Java 中,当将两个 int 相乘时,您可以将它们转换为 long 并将结果与​​ Integer.MAX_VALUEInteger.MIN_VALUE(取决于符号组合)进行比较,然后再将结果转换为 int

如果类型已经是最大的,则检查一个是否小于最大值除以另一个。但不要取绝对值!相反,您需要对每个符号组合 negneg、pospos 和 posneg 进行单独的比较逻辑(negpos 显然可以简化为 posneg 和 pos pos 可能会简化为 neg*neg)。首先测试 0 个参数以允许安全划分。

实际代码见commons-math 2的MathUtils类的Java源码,或commons-math 3ArithmeticUtils。寻找public static long mulAndCheck(long a, long b) a 和 b 的情况是

// check for positive overflow with positive a, positive b
if (a <= Long.MAX_VALUE / b) {
    ret = a * b;
} else {
    throw new ArithmeticException(msg);
}

【讨论】:

  • “你可能只是把它们扔了”——假设每个整数类型被定义为至少是前一个整数类型的两倍。在(例如)C 或 C++ 中不保证,但通常是正确的。
  • 不是处理各种符号组合,而是测试操作是否可逆(即测试 b != 0 之后的 (a * b) / b == a)是否不够?
  • @james 在 C/C++ 中,您将无法做到这一点,因为溢出的结果没有定义并且取决于硬件(它可能会回绕或抛出异常),所以你不能便携(!)检测乘法后的溢出。在 Java 中,定义了结果,乍一看,您的建议应该有效。但是,不幸的是,它不适用于a=Long.MIN_VALUE, b=-1L
  • 谢谢,@Barry。更新了链接。
【解决方案4】:

Pavel Shved 解决方案的替代方案 ...

如果您选择的语言是汇编语言,那么您应该能够检查溢出标志。如果没有,您可以编写一个自定义汇编程序,如果设置了溢出标志,则设置一个变量。

如果这是不可接受的,您可以找到两个值(绝对值)中最重要的设置位。如果总和超过整数(或无符号)的位数,那么如果将它们相乘,就会出现溢出。

希望这会有所帮助。

【讨论】:

  • 最后一个似乎不对。给定无符号数,4 是 100(以 2 为基数)或 3 位,但 4 * 4 不会溢出 5 位寄存器,尽管位之和为 6。反之亦然 - 如果位之和小于大于n,不会溢出。
  • 添加位索引只能提供线索,因为正如@Phil 指出的那样,100_2 * 100_2 = 10000_2 不会溢出 5 位值,但 111_2 * 111_2 = 110001_2 会溢出。
  • 你说得对——位索引可以提供的最好的信息就是一个线索。在提供它作为解决方案之前,我应该更加小心和专心,但事实并非如此。
【解决方案5】:

在 C 语言中,这里有一些经过成熟优化的代码,可以处理各种极端情况:

int
would_mul_exceed_int(int a, int b) {
  int product_bits;

  if (a == 0 || b == 0 || a == 1 || b == 1) return (0); /* always okay */
  if (a == INT_MIN || b == INT_MIN) return (1); /* always underflow */

  a = ABS(a);
  b = ABS(b);

  product_bits  = significant_bits_uint((unsigned)a);
  product_bits += significant_bits_uint((unsigned)b);

  if (product_bits == BITS(int)) { /* cases where the more expensive test is required */
    return (a > INT_MAX / b); /* remember that IDIV and similar are very slow (dozens - hundreds of cycles) compared to bit shifts, adds */
  }
  return (product_bits > BITS(int));
}

Full example with test cases here

上述方法的好处是它不需要强制转换为更大的类型,因此该方法可以适用于更大的整数类型。

【讨论】:

  • 如果不测试您的代码,我认为缺少一个极端情况,即当 a 是 2 的幂而 b 是 2 的幂的负数时,它们的乘积是 INT_MIN。例如。 a=2, b=-INT_MIN/2.
【解决方案6】:

我想将两个(2 的补码)数字相乘,并检测是否存在溢出。最简单的方法是什么?

各种语言没有指定对溢出发生后的有效检查,因此需要事先进行测试。

对于某些类型,可能不存在更广泛的整数类型,因此通用解决方案应将自身限制为单一类型。

以下 (Ref) 只需要对整数范围进行比较和已知限制。如果产品溢出,则返回1,否则返回0

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
}

这是最简单的方法吗? IDK,但它是完整的,可以处理我知道的所有情况。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-21
    • 2015-12-24
    • 1970-01-01
    • 2014-01-14
    • 2016-03-01
    相关资源
    最近更新 更多