Esiste un modo efficiente e portatile per verificare quando le operazioni di moltiplicazione con gli operandi int64_t o uint64_t hanno un overflow in C?rilevamento della moltiplicazione di overflow di uint64_t con C
Ad esempio, per l'aggiunta di uint64_t posso fare:
if (UINT64_MAX - a < b) overflow_detected();
else sum = a + b;
Ma non può arrivare a una semplice espressione simile per la moltiplicazione.
Tutto ciò che accade a me è rompere gli operandi in parti alte e basse uint32_t ed eseguire la moltiplicazione di quelle parti mentre si verifica un overflow, qualcosa di veramente brutto e probabilmente inefficiente.
Update 1: Alcuni codice di riferimento attuare diversi approcci aggiunto
Update 2: Jens metodo Gustedt aggiunto
programma di benchmarking:
#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);
}
Finora, caso 4 che utilizza il punto mobile per filtrare la maggior parte dei casi è il più veloce quando si presume che gli overflow siano molto insoliti, almeno sul mio computer dove sono solo due t è più lento del caso del nulla.
Caso 5, è del 30% più lento di 4, ma esegue sempre la stessa, non v'è alcun numero di casi speciali che richiedono un'elaborazione più lenta come accade con 4.
Cosa ne pensi dell'utilizzo di virgola mobile e, se il risultato è molto vicino a 2^64, l'intero si moltiplica? Se il risultato in virgola mobile è superiore a 9.223370E + 18, il prodotto esatto sarà certamente maggiore di 2^63 e se il risultato in virgola mobile è inferiore a 9.223374E + 18, il prodotto esatto sarà sicuramente inferiore a 3^63. Quindi se il risultato a virgola mobile è chiuso e il numero intero senza segno si moltiplica più di 1ULL << 63, il risultato intero non rappresenta un overflow. – supercat
@supercat: questo è in genere il caso 4. – salva
Il caso quattro sembra utilizzare la divisione per verificare se il risultato si adatta. Su molti processori, fare in modo che il numero intero senza segno si moltiplica da solo sarebbe molto più veloce (la divisione dei numeri interi è spesso una delle istruzioni più lente). – supercat