【发布时间】:2012-01-21 23:25:54
【问题描述】:
是否有任何有效且可移植的方法来检查 C 中使用 int64_t 或 uint64_t 操作数的乘法运算何时溢出?
例如,添加 uint64_t 我可以这样做:
if (UINT64_MAX - a < b) overflow_detected();
else sum = a + b;
但我无法得到类似的简单乘法表达式。
我想到的就是将操作数分解为高和低 uint32_t 部分,并在检查溢出的同时执行这些部分的乘法,这确实很丑陋,也可能效率低下。
更新 1:添加了一些实现多种方法的基准代码
更新 2:添加了 Jens Gustedt 方法
基准测试程序:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#define N 100000000
int d = 2;
#define POW_2_64 ((double)(1 << 31) * (double)(1 << 31) * 4)
#define calc_b (a + c)
// #define calc_b (a + d)
int main(int argc, char *argv[]) {
uint64_t a;
uint64_t c = 0;
int o = 0;
int opt;
if (argc != 2) exit(1);
opt = atoi(argv[1]);
switch (opt) {
case 1: /* faked check, just for timing */
for (a = 0; a < N; a++) {
uint64_t b = a + c;
if (c > a) o++;
c += b * a;
}
break;
case 2: /* using division */
for (a = 0; a < N; a++) {
uint64_t b = a + c;
if (b && (a > UINT64_MAX / b)) o++;
c += b * a;
}
break;
case 3: /* using floating point, unreliable */
for (a = 0; a < N; a++) {
uint64_t b = a + c;
if ((double)UINT64_MAX < (double)a * (double)b) o++;
c += b * a;
}
break;
case 4: /* using floating point and division for difficult cases */
for (a = 0; a < N; a++) {
uint64_t b = a + c;
double m = (double)a * (double)b;
if ( ((double)(~(uint64_t)(0xffffffff)) < m ) &&
( (POW_2_64 < m) ||
( b &&
(a > UINT64_MAX / b) ) ) ) o++;
c += b * a;
}
break;
case 5: /* Jens Gustedt method */
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; }
if (b1 > 0xffffffff) o++;
else {
uint64_t a1l = (a1 & 0xffffffff) * b1;
uint64_t a1h = (a1 >> 32) * b1 + (a1l >> 32);
if (a1h >> 32) o++;
}
c += b1 * a1;
}
break;
default:
exit(2);
}
printf("c: %lu, o: %u\n", c, o);
}
到目前为止,在假设溢出非常不寻常的情况下,使用浮点过滤大多数情况的情况 4 是最快的,至少在我的计算机上它只比不做任何情况的情况慢两倍。
案例 5 比 4 慢 30%,但它始终执行相同的操作,没有任何特殊案例数字需要像 4 那样更慢的处理。
【问题讨论】:
-
如何使用浮点数,如果结果非常接近 2^64,则进行整数相乘?如果浮点结果高于 9.223370E+18,则精确乘积肯定大于 2^63,如果浮点结果小于 9.223374E+18,则精确乘积肯定小于 3^63。因此,如果浮点结果很接近并且无符号整数乘法的结果大于 1ULL
-
@supercat:这主要是案例 4 所做的。
-
案例四似乎使用除法来检查结果是否合适。在许多处理器上,无符号整数乘法本身会快得多(整数除法通常是最慢的指令之一)。
-
@supercat:在大多数架构上,浮点寄存器不能准确表示64位整数,所以有一小块区域是
a * b >= 2**64但是(double)a * (double)b < 2**64 -
确实如此,但在该区域内,不换行的
unsigned multiply的结果将大于 UINT64_MAX/2,而进行换行的结果将小于UINT64_MAX/2.
标签: c multiplication integer-overflow