2012-09-01 11 views
15

voglio eseguire alcune aritmetica senza segno, e la necessità di prendere valore assoluto di int negativo, qualcosa comemodo corretto di prendere valore assoluto di INT_MIN

do_some_arithmetic_in_unsigned_mode(int some_signed_value) 
{ 
    unsigned int magnitude; 
    int negative; 
    if(some_signed_value<0) { 
     magnitude = 0 - some_signed_value; 
     negative = 1; 
    } else { 
     magnitude = some_signed_value; 
     negative = 0; 
    } 
    ...snip... 
} 

Ma INT_MIN potrebbe essere problematico, 0 - INT_MIN è UB se eseguito in aritmetica firmata. Che cosa è un modo standard/robusto/sicuro/efficace per farlo in C?

EDIT:

Se sappiamo di essere in 2-complemento, forse cast implicito e ops bit esplicite sarebbe normale? se possibile, vorrei evitare questa ipotesi.

do_some_arithmetic_in_unsigned_mode(int some_signed_value) 
{ 
    unsigned int magnitude=some_signed_value; 
    int negative=some_signed_value<0; 
    if (negative) { 
     magnitude = (~magnitude) + 1; 
    } 
    ...snip... 
} 

risposta

20

La conversione da firmato per unsigned è ben definito: È possibile ottenere il corrispondente modulo rappresentante 2 N. Pertanto, la seguente vi darà il giusto valore assoluto di n:

int n = /* ... */; 

unsigned int abs_n = n < 0 ? UINT_MAX - ((unsigned int)(n)) + 1U 
          : (unsigned int)(n); 

Aggiornamento: Come @ aka.nice suggerisce, possiamo effettivamente sostituire UINT_MAX + 1U da 0U:

unsigned int abs_n = n < 0 : -((unsigned int)(n)) : (unsigned int)(n); 
+4

Oh, e nel caso puramente teorico in cui 'UINT_MAX == INT_MAX == - (INT_MIN + 1)', è impossibile rappresentare '| INT_MIN |' come int' 'senza segno comunque =) –

+0

@DanielFischer: questo caso è effettivamente possibile, dato che 'int' e' unsigned int' devono avere le stesse dimensioni e requisiti di allineamento? –

+0

È possibile che 'unsigned int' possa avere un bit di riempimento più di 'int'. Non ho mai sentito di un'implementazione in cui sia così, ma lo standard non garantisce che non accada mai. (A meno che non abbia trascurato qualcosa.) –

2

Si può sempre test per >= -INT_MAX, questo è sempre ben definito. L'unico caso interessante per te è se INT_MIN < -INT_MAX e quello some_signed_value == INT_MIN. Dovresti testare quel caso separatamente.

6

In caso negativo, prendere some_signed_value+1. Negatelo (questo è sicuro perché non può essere INT_MIN). Converti in non firmato. Quindi aggiungine uno;

+0

Ho controllato e gcc genera lo stesso codice per 1U + (senza segno) (- (x + 1)) e per - (senza segno) (x), qualcosa come (~ magnitudine) +1 ma senza ramo, quindi entrambi saranno altrettanto efficienti. Più tardi, però, sembra che un po 'meno l'intenzione oscuri. –

+0

@ aka.nice: Sì, l'ho appena controllato anch'io. '1+ (unsigned) - (x + 1)' è forse un po 'oscuro, ma non porta la conversione del valore di una quantità con segno negativo a unsigned; il cast è puramente un cambio di tipo, non un cambiamento di valore. Nella tua versione, alcuni sforzi di ragionamento devono andare nel garantire che l'aritmetica faccia ciò che ci si aspetta; l'argomento non è semplice come "i valori sono in un intervallo di sicurezza ad ogni passo". –

+1

Sì, davvero la tua soluzione si adatta meglio alla mia intenzione iniziale. Re-interpretare la x negativa come positivo è l'intenzione che oscura per una lettura attenta del codice e richiede la conoscenza delle convenzioni standard. Ma il lettore meno attento riconoscerà immediatamente una forma di abs in - (unsigned) x ... –

0
static unsigned absolute(int x) 
{ 
      if (INT_MIN == x) { 
        /* Avoid tricky arithmetic overflow possibilities */ 
        return ((unsigned) -(INT_MIN + 1)) + 1U; 
      } else if (x < 0) { 
        return -x; 
      } else { 
        return x; 
      } 
} 
+0

Sembra OK, ma è principalmente la risposta @R con un altro ramo, o forse solo la risposta @Jens ... –

Problemi correlati