обнаружение переполнения умножения целых чисел uint64_t на C

Есть ли какой-либо эффективный и переносимый способ проверить, когда операции умножения с операндами int64_t или uint64_t переполняются в C?

Например, для добавления uint64_t я могу сделать:

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

Но я не могу найти аналогичное простое выражение для умножения.

Все, что мне приходит в голову, - это разбивать операнды на старшие и младшие части uint32_t и выполнять умножение этих частей, пока проверка на переполнение, что-то действительно уродливое и, вероятно, тоже неэффективное.

Обновление 1 : добавлен некоторый тестовый код, реализующий несколько подходов

Обновление 2 : добавлен метод Йенса Густедта

тестовая программа:

#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 на 30% медленнее, чем 4, но он всегда выполняется одинаково, нет каких-либо специальных номеров случаев, которые требуют более медленной обработки, как это происходит с 4.

16
задан salva 16 December 2011 в 23:36
поделиться