(-2147483648> 0) devuelve verdadero en C ++?

241

-2147483648 es el número entero más pequeño para el tipo entero con 32 bits, pero parece que se desbordará en la if(...)oración:

if (-2147483648 > 0)
    std::cout << "true";
else
    std::cout << "false";

Esto se imprimirá trueen mis pruebas. Sin embargo, si lanzamos -2147483648 a entero, el resultado será diferente:

if (int(-2147483648) > 0)
    std::cout << "true";
else
    std::cout << "false";

Esto se imprimirá false.

Estoy confundido. ¿Alguien puede dar una explicación sobre esto?


Actualización 02-05-2012:

Gracias por sus comentarios, en mi compilador, el tamaño de int es de 4 bytes. Estoy usando VC para algunas pruebas simples. He cambiado la descripción en mi pregunta.

Esas son muchas respuestas muy buenas en esta publicación, AndreyT dio una explicación muy detallada sobre cómo el compilador se comportará en dicha entrada y cómo se implementó este entero mínimo. qPCR4vir por otro lado dio algunas "curiosidades" relacionadas y cómo se representan los enteros. ¡Tan impresionante!

benilo
fuente
48
"Todos sabemos que -2147483648 es el número más pequeño de enteros" Eso depende del tamaño del entero.
orlp
14
"Todos sabemos que -2147483648 es el número más pequeño de enteros". Pensé que no había un número entero más pequeño, ya que hay infinitos de ellos ... Lo que sea.
@Inisheer Con enteros de 4 bytes, puede tener un INT_MINde -9223372036854775808, si CHAR_BITes 16. E incluso con CHAR_BIT == 8y sizeof(int== 4) `puede obtener -9223372036854775807porque C no requiere números de 2 Complementos.
12431234123412341234123

Respuestas:

391

-2147483648No es un "número". El lenguaje C ++ no admite valores literales negativos.

-2147483648es en realidad una expresión: un valor literal positivo 2147483648con -operador unario en frente. El valor 2147483648es aparentemente demasiado grande para el lado positivo del intrango en su plataforma. Si el tipo long inttuviera un rango mayor en su plataforma, el compilador tendría que asumir automáticamente que 2147483648tiene long inttipo. (En C ++ 11, el compilador también tendría que considerar el long long inttipo). Esto haría que el compilador evaluara -2147483648en el dominio de tipo más grande y el resultado sería negativo, como cabría esperar.

Sin embargo, aparentemente en su caso el rango de long intes el mismo que el rango de int, y en general no hay un tipo entero con un rango mayor que inten su plataforma. Esto significa formalmente que la constante positiva 2147483648desborda todos los tipos de enteros con signo disponibles, lo que a su vez significa que el comportamiento de su programa no está definido. (Es un poco extraño que la especificación del lenguaje opte por un comportamiento indefinido en tales casos, en lugar de requerir un mensaje de diagnóstico, pero así es como es).

En la práctica, teniendo en cuenta que el comportamiento es indefinido, 2147483648puede interpretarse como un valor negativo dependiente de la implementación que se vuelve positivo después de haberse -aplicado unario . Alternativamente, algunas implementaciones podrían decidir intentar usar tipos sin signo para representar el valor (por ejemplo, en C89 / 90 se requería usar compiladores unsigned long int, pero no en C99 o C ++). Las implementaciones pueden hacer cualquier cosa, ya que el comportamiento no está definido de todos modos.

Como nota al margen, esta es la razón por la cual las constantes como INT_MINse suelen definir como

#define INT_MIN (-2147483647 - 1)

en lugar del aparentemente más directo

#define INT_MIN -2147483648

Este último no funcionaría según lo previsto.

Hormiga
fuente
78
Esta es también la razón por esto se hace: #define INT_MIN (-2147483647 - 1).
orlp
55
@ RichardJ.RossIII: con el sonido metálico probablemente obtengas un literal de 64 bits, ya que era demasiado grande para caber en un int. La implementación de OP puede no tener un tipo de 64 bits.
Carl Norum
1
@ RichardJ.RossIII: Creo que este comportamiento está definido / indefinido en la implementación.
Oliver Charlesworth
3
Nunca pensé que un "número negativo" no se analiza como tal. No veo una razon. Espero que -1.0se analice como un valor doble negativo, ¿no?
Leemes
66
@ qPCR4vir: No. Como escribí en mi comentario a su respuesta, ni C ni C ++ modernos permiten el uso de tipos sin signo en este caso (con una constante decimal sin sufijo ). Solo el primer estándar C (C89 / 90) permitido unsigned long inten este contexto, pero en C99 este permiso fue eliminado. Se requieren literales sin sufijo en C y C ++ para tener tipos firmados . Si ve un tipo sin firmar aquí cuando uno con signo funcionaría, significa que su compilador está dañado. Si ve un tipo sin signo aquí cuando ningún tipo con signo funcionaría, entonces esto es solo una manifestación específica de comportamiento indefinido.
AnT
43

El compilador (VC2012) promueve los enteros "mínimos" que pueden contener los valores. En el primer caso, signed int(y long int) no puede (antes de que se aplique el signo), pero unsigned intpuede: ¿ 2147483648tieneunsigned int ???? tipo. En el segundo forzas intdesde el unsigned.

const bool i= (-2147483648 > 0) ;  //   --> true

advertencia C4146: operador unario menos aplicado al tipo sin signo , el resultado sigue sin firmar

Aquí hay "curiosidades" relacionadas:

const bool b= (-2147483647      > 0) ; //  false
const bool i= (-2147483648      > 0) ; //  true : result still unsigned
const bool c= ( INT_MIN-1       > 0) ; //  true :'-' int constant overflow
const bool f= ( 2147483647      > 0) ; //  true
const bool g= ( 2147483648      > 0) ; //  true
const bool d= ( INT_MAX+1       > 0) ; //  false:'+' int constant overflow
const bool j= ( int(-2147483648)> 0) ; //  false : 
const bool h= ( int(2147483648) > 0) ; //  false
const bool m= (-2147483648L     > 0) ; //  true 
const bool o= (-2147483648LL    > 0) ; //  false

C ++ 11 estándar :

2.14.2 Literales enteros [lex.icon]

...

Un literal entero es una secuencia de dígitos que no tiene período ni parte exponente. Un literal entero puede tener un prefijo que especifica su base y un sufijo que especifica su tipo.

...

El tipo de un literal entero es el primero de la lista correspondiente en la que se puede representar su valor.

ingrese la descripción de la imagen aquí

Si un literal entero no puede ser representado por ningún tipo en su lista y un tipo entero extendido (3.9.1) puede representar su valor, puede tener ese tipo entero extendido. Si todos los tipos en la lista para el literal están firmados, se firmará el tipo entero extendido. Si todos los tipos en la lista para el literal no tienen signo, el tipo entero extendido no tendrá signo. Si la lista contiene tipos con signo y sin signo, el tipo entero extendido puede ser con signo o sin signo. Un programa está mal formado si una de sus unidades de traducción contiene un literal entero que no puede ser representado por ninguno de los tipos permitidos.

Y estas son las reglas de promociones para enteros en el estándar.

4.5 Promociones integrales [conv.prom]

A prvalue de un tipo entero distinto de bool, char16_t, char32_t, o wchar_tcuyo número entero de conversión de rango (4,13) es menor que el rango de int se puede convertir en un prvalue de tipo intsi intpuede representar todos los valores del tipo de fuente; de lo contrario, el valor de origen puede convertirse en un valor de tipo unsigned int.

qPCR4vir
fuente
3
@ qPCR4vir: En C89 / 90 los compiladores se suponía que los tipos de uso int, long int, unsigned long intpara representar las constantes decimales unsuffixed. Ese fue el único lenguaje que permitió el uso de tipos sin signo para constantes decimales sin mezclar. En C ++ 98 era into long int. No se permiten tipos sin firmar. Ni C (a partir de C99) ni C ++ permiten que el compilador use tipos sin signo en este contexto. Su compilador es, por supuesto, libre de usar tipos sin signo si ninguno de los firmados funciona, pero esto sigue siendo solo una manifestación específica de comportamiento indefinido.
AnT
@AndreyT. ¡Excelente! Por supuesto, tu derecho. ¿VC2012 está roto?
qPCR4vir
@ qPCR4vir: AFAIK, VC2012 todavía no es un compilador de C ++ 11 (¿lo es?), lo que significa que tiene que usar into long intrepresentar 2147483648. Además, que yo sepa, en tanto VC2012 inty long intson tipos de 32 bits. Esto significa que en VC2012 literal 2147483648debería conducir a un comportamiento indefinido . Cuando el comportamiento no está definido, el compilador puede hacer cualquier cosa. Eso significaría que VC2012 no está roto. Simplemente emitió un mensaje de diagnóstico engañoso. En lugar de decirle que el comportamiento es completamente indefinido, decidió usar un tipo sin signo.
AnT
@AndreyT: ¿Estás diciendo que los compiladores son libres de emitir demonios nasales si el código fuente contiene un literal decimal sin sufijo que excede el valor máximo de un signo longy no está obligado a emitir un diagnóstico? Eso parecería roto.
supercat
La misma "advertencia C4146" en VS2008 y "esta constante decimal no está firmada solo en ISO C90" en G ++
espía
6

En resumen, se 2147483648desborda -2147483648y (-(-2147483648) > 0)es true.

Así es como se 2147483648ve en binario.

Además, en el caso de los cálculos binarios con signo, el bit más significativo ("MSB") es el bit de signo. Esta pregunta puede ayudar a explicar por qué.

drzymala
fuente
4

Debido a -2147483648que en realidad se aplica 2147483648con negación ( -), el número no es lo que esperarías. En realidad es el equivalente de este pseudocódigo:operator -(2147483648)

Ahora, suponiendo que su compilador sea sizeof(int)igual 4y CHAR_BITse defina como 8, eso haría que el 2147483648desbordamiento sea el valor máximo con signo de un entero ( 2147483647). Entonces, ¿cuál es el máximo más uno? Vamos a resolver eso con un entero de 4 bits y 2 segundos.

¡Espere! 8 desborda el entero! qué hacemos? Utilice su representación sin signo 1000e interprete los bits como un entero con signo. Esta representación nos deja con la -8aplicación de la negación del complemento 2 que resulta en 8, que, como todos sabemos, es mayor que 0.

Esta es la razón por la cual <limits.h>(y <climits>) comúnmente se define INT_MINcomo ((-2147483647) - 1)- para que el entero con signo máximo ( 0x7FFFFFFF) se niegue ( 0x80000001), luego se disminuya ( 0x80000000).

Cole Johnson
fuente
Para un número de 4 bits, la negación del complemento de dos -8sigue siendo -8.
Ben Voigt
Excepto que -8 se interpreta como 0-8, no negativo 8. Y 8 desborda un int firmado de 4 bits
Cole Johnson
Considere -(8)qué en C ++ es lo mismo que -8: es la negación aplicada a un literal, no a un literal negativo. El literal es 8, que no cabe en un entero de 4 bits con signo, por lo que debe estar sin signo. El patrón es 1000. Hasta ahora tu respuesta es correcta. La negación del complemento de dos 1000en 4 bits es 1000que no importa si está firmado o no. Su respuesta dice "interprete los bits como un entero con signo", lo que hace que el valor -8después de la negación del complemento a los dos, tal como era antes de la negación.
Ben Voigt
Por supuesto, en "C ++ de 4 bits" no hay "interpretar los bits como un paso entero con signo". El literal se convierte en el tipo más pequeño que puede expresarlo, que es un entero de 4 bits sin signo . El valor del literal es 8. Se aplica la negación (módulo 16), dando como resultado una respuesta final de 8. La codificación sigue siendo 1000 pero el valor es diferente porque se eligió un tipo sin signo.
Ben Voigt