2012-09-01 11 views
15

quiero realizar algunas aritmética de firmar, y la necesidad de tomar el valor absoluto de int negativo, algo así comomanera correcta para tomar el valor absoluto de 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... 
} 

Pero INT_MIN podría ser problemático, 0 - INT_MIN es UB si se realiza en aritmética firmada. ¿Cuál es una forma estándar/robusta/segura/eficiente de hacer esto en C?

EDIT:

Si sabemos que estamos en 2-complemento, tal conversión implícita y explícita de operaciones de bits sería estándar? si es posible, me gustaría evitar esta suposición.

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

Respuesta

20

La conversión de firmado a firmar está bien definido: Se obtiene el correspondiente módulo representativa 2 N. Por lo tanto, la siguiente información le dará el valor absoluto correcto de n:

int n = /* ... */; 

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

Actualización: Como @ aka.nice sugiere, en realidad podemos sustituir por UINT_MAX + 1U0U:

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

Oh, y en el caso puramente teórico donde 'UINT_MAX == INT_MAX == - (INT_MIN + 1)', es imposible representar '| INT_MIN |' 'como un int' sin signo de todos modos =) –

+0

@DanielFischer: ¿ese caso es realmente posible, dado que 'int' y' unsigned int' deben tener los mismos requisitos de tamaño y alineación? –

+0

Es posible, 'unsigned int' podría tener un bit de relleno más que' int'. Nunca he oído hablar de una implementación donde ese es el caso, pero el estándar no garantiza que nunca suceda. (A menos que haya pasado por alto algo) –

2

siempre se puede prueba para >= -INT_MAX, esto siempre está bien definido. El único caso interesante para usted es INT_MIN < -INT_MAX y some_signed_value == INT_MIN. Tendría que probar ese caso por separado.

6

En el caso negativo, tome some_signed_value+1. Negarlo (esto es seguro porque no puede ser INT_MIN). Convertir a unsigned. Luego agrega uno;

+0

He comprobado y gcc generan el mismo código para 1U + (sin signo) (- (x + 1)) y para - (sin signo) (x), algo así como (~ magnitud) +1 pero sin ramas, por lo que ambos serán tan eficientes. Más tarde uno parece un poco menos obsceno de intención. –

+0

@ aka.nice: Sí, acabo de comprobarlo también. '1+ (unsigned) - (x + 1)' es quizás un poco oscuro, pero no trae la conversión de valor de una cantidad firmada negativa a unsigned; el yeso es puramente un cambio en el tipo, no un cambio en el valor. En su versión, debe hacerse algún esfuerzo de razonamiento para asegurar que la aritmética haga lo que se espera; el argumento no es tan simple como "los valores están en un rango seguro en cada paso". –

+1

Sí, de hecho su solución se ajusta mejor a mi intención inicial. La reinterpretación de la x negativa como positiva es una obstrucción de la intención para alguien que lee de cerca el código, y requiere el conocimiento de las convenciones estándar. Pero el lector menos atento reconocerá de inmediato una forma de abs en - (sin signo) 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

Parece correcto, pero es mayormente @R answer con una rama más, o tal vez solo la respuesta de @Jens ... –

Cuestiones relacionadas