2012-09-01 6 views
15

Chcę wykonać pewne działania arytmetyczne w niepodpisany, i trzeba podjąć bezwzględną wartość ujemną int, coś jakPrawidłowy sposób podjąć bezwzględną wartość 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... 
} 

Ale INT_MIN może być problematyczne, 0 - INT_MIN jest UB jeśli jest wykonywany z arytmetyczną sygnaturą. Co to jest standardowy/solidny/bezpieczny/skuteczny sposób na wykonanie tego w języku C?

EDIT:

Jeśli wiemy, że jesteśmy w 2-uzupełnienie, być może ukryte i jawne oddanych bitowe ops byłoby standard? jeśli to możliwe, chciałbym uniknąć tego założenia.

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... 
} 

Odpowiedz

20

Konwersja podpisał unsigned jest dobrze zdefiniowana: można otrzymać odpowiadającą przedstawiciel Modulo 2 N. Dlatego dodaje daje poprawną wartość bezwzględną n:

int n = /* ... */; 

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

Aktualizacja: Jako @ aka.nice sugeruje, że może faktycznie zastąpić UINT_MAX + 1U przez 0U:

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

Aha, w czysto teoretycznym przypadku, gdy 'UINT_MAX == INT_MAX == - (INT_MIN + 1)', nie jest możliwe do reprezentowania '| INT_MIN |' 'jako unsigned int' anyway =) –

+0

@ Danielanischer: czy ten przypadek jest rzeczywiście możliwy, zważywszy, że 'int' i' unsigned int' muszą mieć takie same wymagania dotyczące rozmiaru i wyrównania? –

+0

Jest możliwe, że 'unsigned int' może mieć jeszcze jeden bit padding niż' int'. Nigdy nie słyszałam o takiej implementacji, ale standard nie gwarantuje, że nigdy się nie stanie. (O ile nie przeoczyłem czegoś.) –

2

Można zawsze test dla >= -INT_MAX, jest to zawsze dobrze zdefiniowane. Jedyny przypadek jest interesujący dla ciebie, jeśli INT_MIN < -INT_MAX i że some_signed_value == INT_MIN. Musiałbyś przetestować tę sprawę osobno.

6

W przypadku negatywnym, weź some_signed_value+1. Neguj go (jest to bezpieczne, ponieważ nie może być INT_MIN). Konwertuj na niepodpisane. Następnie dodaj jedną;

+0

Sprawdziłem i gcc wygenerował ten sam kod dla 1U + (unsigned) (- (x + 1)) i dla - (unsigned) (x), coś jak (~ magnitudo) +1, ale bez gałęzi, więc oba będą tak wydajne. Później wydaje się, że nieco mniej intryguje przesłanie. –

+0

@ aka.nice: Tak, też to sprawdziłem. '1+ (unsigned) - (x + 1)' jest prawdopodobnie nieco niejasne, ale nie powoduje konwersji wartości ujemnej liczby podpisanej na niepodpisaną; obsada jest czysto zmianą typu, a nie zmianą wartości. W twojej wersji, niektóre wysiłki logiczne muszą zapewnić, że arytmetyka spełnia to, czego się oczekuje; argument nie jest tak prosty, jak "wartości są w bezpiecznym zakresie na każdym kroku". –

+1

Tak, rzeczywiście, twoje rozwiązanie lepiej pasuje do mojej pierwotnej intencji. Ponowna interpretacja negatywu x jako pozytywnego jest zamiarem zaciemnienia dla kogoś, kto dokładnie czyta kod i wymaga znajomości standardowych konwencji. Ale mniej uważny czytelnik natychmiast rozpozna formę abs w - (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

To brzmi OK, ale to głównie odpowiedź @R z jeszcze jednym odgałęzieniem, a może po prostu odpowiedź @Jens ... –

Powiązane problemy