繁体   English   中英

用 C 检测 uint64_t 整数的乘法溢出

[英]detecting multiplication of uint64_t integers overflow with C

是否有任何有效且可移植的方法来检查在 C 中使用 int64_t 或 uint64_t 操作数的乘法运算何时溢出?

例如,添加 uint64_t 我可以这样做:

if (UINT64_MAX - a < b) overflow_detected();
else sum = a + b;

实际上,相同的原理可用于乘法:

uint64_t a;
uint64_t b;
...
if (b != 0 && a > UINT64_MAX / b) { // if you multiply by b, you get: a * b > UINT64_MAX
    < error >
}
uint64_t c = a * b;

对于类似的有符号整数,你可能需要为每个符号组合提供一个案例。

如果你想在Ambroz的答案中避免分裂:

首先你必须看到两个数字中较小的一个,比如a ,小于2 32 ,否则结果无论如何都会溢出。 b被分解为两个32位字,即b = c 2 32 + d

那么计算并不那么困难,我发现:

uint64_t mult_with_overflow_check(uint64_t a, uint64_t b) {
  if (a > b) return mult_with_overflow_check(b, a);
  if (a > UINT32_MAX) overflow();
  uint32_t c = b >> 32;
  uint32_t d = UINT32_MAX & b;
  uint64_t r = a * c;
  uint64_t s = a * d;
  if (r > UINT32_MAX) overflow();
  r <<= 32;
  return addition_with_overflow_check(s, r);
}

所以这是两次乘法,两次换档,一些加法和条件检查。 这可能比除法更有效,因为例如两个乘法可以并行流水线化。 您必须进行基准测试才能看到哪种方法更适合您。

与一些(希望)有用的答案相关的问题: 在C / C ++中检测整数溢出的最佳方法 另外它不包括uint64_t ;)

case 6:
    for (a = 0; a < N; a++) {
        uint64_t b = a + c;
        uint64_t a1, b1;
        if (a > b) { a1 = a; b1 = b; }
        else       { a1 = b; b1 = a; }
        uint64_t cc = b1 * a1;
        c += cc;
        if (b1 > 0xffffffff) o++;
        else {
            uint64_t a1l = (a1 & 0xffffffff) + (a1 >> 32);
            a1l = (a1 + (a1 >> 32)) & 0xffffffff;
            uint64_t ab1l = a1l * b1;
            ab1l = (ab1l & 0xffffffff) + (ab1l >> 32);
            ab1l += (ab1l >> 32);
            uint64_t ccl = (cc & 0xffffffff) + (cc >> 32);
            ccl += (ccl >> 32);
            uint32_t ab32 = ab1l; if (ab32 == 0xffffffff) ab32 = 0;
            uint32_t cc32 = ccl; if (cc32 == 0xffffffff) cc32 = 0;
            if (ab32 != cc32) o++;
        }
    }
    break;

该方法将正常乘法的结果(可能溢出)与乘法结果进行比较,该乘法结果不会溢出。 所有计算都是模数(2 ^ 32 - 1)。

它更复杂,并且(很可能)不比Jens Gustedt的方法快。

经过一些小的修改后,它可以乘以96位精度(但没有溢出控制)。 更有趣的是,该方法的思想可用于检查一系列算术运算(乘法,加法,减法)的溢出。

有些问题得到解答

首先,关于"your code is not portable" 是的,代码不可移植,因为它使用的是原始问题中请求的uint64_t 严格地说,你不能用(u)int64_t获得任何便携式答案,因为标准不要求它。

关于"once some overflow happens, you can not assume the result value to be anything" Standard表示无符号迭代不能溢出。 第6.2.5章,第9项:

涉及无符号操作数的计算永远不会溢出,因为无法通过生成的无符号整数类型表示的结果将以比结果类型可以表示的最大值大1的数量为模。

因此,无符号的64位乘法以2 ^ 64为模进行,没有溢出。

现在关于"logic behind""logic behind" “散列函数”在这里不是正确的单词。 我只使用模数(2^32 - 1) 乘法的结果可以表示为n*2^64 + m ,其中m是可见结果, n表示我们溢出多少。 由于2^64 = 1 (mod 2^32 - 1) ,我们可以计算[true value] - [visible value] = (n*2^64 + m) - m = n*2^64 = n (mod 2^32 - 1) 如果n计算值不为零,则存在溢出。 如果为零,则没有溢出。 只有在n >= 2^32 - 1之后才可能发生任何碰撞。 这将永远不会发生,因为我们检查其中一个被乘数小于2^32

它可能无法检测到确切的溢出,但通常您可以在对数刻度上测试乘法的结果:

if (log(UINT64_MAX-1) - log(a) - log(b) < 0) overflow_detected(); // subtracting 1 to allow some tolerance when the numbers are converted to double
else prod = a * b;

这取决于你是否真的需要将乘法运算到精确的UINT64_MAX,否则这是检查大数乘法的一种非常便携和方便的方法。

还可以考虑使用编译器的内置函数:

bool __builtin_mul_overflow (type1 a, type2 b, type3 *res)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM