(-2147483648> 0) retorna verdadeiro em C ++?

241

-2147483648 é o menor número inteiro para o tipo inteiro com 32 bits, mas parece que ele transbordará na if(...)frase:

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

Isso será impresso truenos meus testes. No entanto, se convertermos -2147483648 em número inteiro, o resultado será diferente:

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

Isso será impresso false.

Estou confuso. Alguém pode dar uma explicação sobre isso?


Atualização 02-05-2012:

Obrigado pelos seus comentários, no meu compilador, o tamanho de int é 4 bytes. Estou usando o VC para alguns testes simples. Eu mudei a descrição na minha pergunta.

São muitas respostas muito boas neste post, o AndreyT deu uma explicação muito detalhada sobre como o compilador se comportará em tais entradas e como esse número inteiro mínimo foi implementado. O qPCR4vir, por outro lado, forneceu algumas "curiosidades" relacionadas e como o número inteiro é representado. Tão impressionante!

benilo
fonte
48
"todos sabemos que -2147483648 é o menor número de números inteiros" Isso depende do tamanho do número inteiro.
orlp
14
"todos sabemos que -2147483648 é o menor número de números inteiros" - pensei que não havia um número inteiro menor, pois existem muitos deles ... De qualquer maneira.
@Inisheer Com números inteiros de 4 bytes, você pode ter um INT_MINde -9223372036854775808, se CHAR_BITfor 16. E mesmo com CHAR_BIT == 8e sizeof(int== 4) `você pode obter -9223372036854775807porque C não requer números de 2 complementos.
12431234123412341234123

Respostas:

391

-2147483648não é um "número". A linguagem C ++ não suporta valores literais negativos.

-2147483648é na verdade uma expressão: um valor literal positivo 2147483648com um -operador unário à sua frente. O valor 2147483648é aparentemente muito alto para o lado positivo do intintervalo na sua plataforma. Se o tipo long inttivesse maior alcance em sua plataforma, o compilador teria que assumir automaticamente que 2147483648possui o long inttipo. (No C ++ 11, o compilador também precisaria considerar o long long inttipo.) Isso faria o compilador avaliar -2147483648no domínio de tipo maior e o resultado seria negativo, como seria de esperar.

No entanto, aparentemente no seu caso, o intervalo de long inté igual ao intervalo de inte, em geral, não existe um tipo inteiro com maior alcance do que intna sua plataforma. Isso significa formalmente que a constante positiva 2147483648excede todos os tipos de números inteiros assinados disponíveis, o que, por sua vez, significa que o comportamento do seu programa é indefinido. (É um pouco estranho que a especificação da linguagem opte por comportamentos indefinidos nesses casos, em vez de exigir uma mensagem de diagnóstico, mas é assim que acontece.)

Na prática, levando em conta que o comportamento é indefinido, 2147483648pode ser interpretado como um valor negativo dependente da implementação que, por acaso, se torna positivo após a -aplicação unária a ele. Como alternativa, algumas implementações podem decidir tentar usar tipos não assinados para representar o valor (por exemplo, em compiladores C89 / 90 eram necessários unsigned long int, mas não em C99 ou C ++). As implementações têm permissão para fazer qualquer coisa, pois o comportamento é indefinido de qualquer maneira.

Como uma observação lateral, esta é a razão pela qual constantes como INT_MINsão tipicamente definidas como

#define INT_MIN (-2147483647 - 1)

em vez do aparentemente mais direto

#define INT_MIN -2147483648

Este último não funcionaria como pretendido.

Formiga
fonte
78
Isto é também porque isso é feito: #define INT_MIN (-2147483647 - 1).
orlp
5
@ RichardJ.RossIII - com o clang, você provavelmente está recebendo um literal de 64 bits, já que era muito grande para caber em um int. A implementação do OP pode não ter um tipo de 64 bits.
Carl Norum
1
@ RichardJ.RossIII: Eu acredito que esse comportamento é definido / indefinido pela implementação.
Oliver Charlesworth 04/04
3
Eu nunca pensei que um "número negativo" não fosse analisado como tal. Não vejo razão. Espero que -1.0seja analisado como um valor duplo negativo, não é?
Leem
6
@ qPCR4vir: Não. Como escrevi no meu comentário à sua resposta, nem o C nem o C ++ modernos permitem o uso de tipos não assinados neste caso (com uma constante decimal não substituída ). Somente o primeiro padrão C (C89 / 90) foi permitido unsigned long intnesse contexto, mas no C99 essa permissão foi removida. Literais não substituídos em C e C ++ devem ter tipos assinados . Se você vir um tipo não assinado aqui quando um assinado funcionaria, significa que seu compilador está quebrado. Se você vir um tipo não assinado aqui, quando nenhum tipo assinado funcionaria, isso é apenas uma manifestação específica de comportamento indefinido.
AnT
43

O compilador (VC2012) promove os números "mínimos" que podem conter os valores. No primeiro caso, signed int(e long int) não pode (antes da aplicação do sinal), mas unsigned intpode: 2147483648hasunsigned int ???? tipo. No segundo você força a intpartir do unsigned.

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

aviso C4146: operador negativo unário aplicado ao tipo não assinado , resultado ainda não assinado

Aqui estão relacionadas "curiosidades":

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

Padrão C ++ 11 :

2.14.2 Literais inteiros [lex.icon]

...

Um literal inteiro é uma sequência de dígitos que não possui período ou parte do expoente. Um literal inteiro pode ter um prefixo que especifica sua base e um sufixo que especifica seu tipo.

...

O tipo de um literal inteiro é o primeiro da lista correspondente na qual seu valor pode ser representado.

insira a descrição da imagem aqui

Se um literal inteiro não puder ser representado por qualquer tipo em sua lista e um tipo inteiro estendido (3.9.1) puder representar seu valor, ele poderá ter esse tipo de número inteiro estendido. Se todos os tipos na lista do literal forem assinados, o tipo inteiro estendido será assinado. Se todos os tipos na lista para o literal não tiverem sinal, o tipo inteiro estendido não terá sinal. Se a lista contiver os tipos assinado e não assinado, o tipo inteiro estendido poderá ser assinado ou não. Um programa não está formado se uma de suas unidades de conversão contiver um literal inteiro que não possa ser representado por nenhum dos tipos permitidos.

E estas são as regras de promoções para números inteiros no padrão.

4.5 Promoções integrais [conv.prom]

Um prvalue de um tipo inteiro diferente de bool, char16_t, char32_t, ou wchar_tcujo número inteiro conversão posto (4,13) é menor do que a patente de int pode ser convertido para um prvalue de tipo intse intpodem representar todos os valores do tipo de fonte; caso contrário, o pré-valor de origem pode ser convertido em um pré-valor do tipo unsigned int.

qPCR4vir
fonte
3
@ qPCR4vir: Em C89 / 90 os compiladores deveriam tipos de uso int, long int, unsigned long intpara representar constantes decimais unsuffixed. Esse era o único idioma que permitia o uso de tipos não assinados para constantes decimais não substituídas. No C ++ 98, era intou long int. Não são permitidos tipos não assinados. Nem C (iniciando em C99) nem C ++ permitem que o compilador use tipos não assinados nesse contexto. Obviamente, seu compilador é livre para usar tipos não assinados, se nenhum dos assinados funcionar, mas isso ainda é apenas uma manifestação específica de comportamento indefinido.
AnT
@AndreyT. Ótimo! Claro, seu direito. O VC2012 está quebrado?
QPCR4vir
@ qPCR4vir: AFAIK, VC2012 ainda não é um compilador C ++ 11 (é?), o que significa que ele precisa usar um intou outro long intpara representar 2147483648. Além disso, AFAIK, em VC2012 tanto inte long intsão tipos de 32 bits. Isso significa que, no VC2012, o literal 2147483648deve levar a um comportamento indefinido . Quando o comportamento é indefinido, o compilador pode fazer qualquer coisa. Isso significaria que o VC2012 não está quebrado. Simplesmente emitiu uma mensagem de diagnóstico enganosa. Em vez de dizer que o comportamento é indefinido, ele decidiu usar um tipo não assinado.
AnT
@AndreyT: Você está dizendo que os compiladores são livres para emitir demônios nasais se o código-fonte contiver um literal decimal não substituído que exceda o valor máximo de um sinal longe não seja necessário emitir um diagnóstico? Isso parece quebrado.
supercat
O mesmo "aviso C4146" no VS2008 e "essa constante decimal não está assinada apenas na ISO C90" no G ++
spyder
6

Em suma, 2147483648transborda para -2147483648e (-(-2147483648) > 0)é true.

Isto é como 2147483648parece em binário.

Além disso, no caso de cálculos binários assinados, o bit mais significativo ("MSB") é o bit de sinal. Esta pergunta pode ajudar a explicar o porquê.

drzymala
fonte
4

Como -2147483648na verdade se aplica 2147483648negation ( -), o número não é o que você esperaria. Na verdade, é o equivalente a esse pseudocódigo:operator -(2147483648)

Agora, assumindo que o seu compilador tem sizeof(int)igual 4e CHAR_BITé definido como 8, que faria 2147483648transbordar o valor máximo assinado de um inteiro ( 2147483647). Então, qual é o máximo mais um? Vamos resolver isso com um número inteiro de 4 bits e 2s.

Esperar! 8 excede o número inteiro! O que nós fazemos? Use sua representação não assinada 1000e interprete os bits como um número inteiro assinado. Essa representação nos deixa com -8a aplicação da negação do complemento 2s resultando em 8, que, como todos sabemos, é maior que 0.

É por isso que <limits.h>(e <climits>) geralmente definem INT_MINcomo ((-2147483647) - 1)- para que o número máximo máximo assinado ( 0x7FFFFFFF) seja negado ( 0x80000001) e depois decrementado ( 0x80000000).

Cole Johnson
fonte
Para um número de 4 bits, a negação do complemento de dois -8ainda é -8.
Ben Voigt
Só que -8 é interpretado como 0-8, não negativo 8. E 8 transborda um int assinado de 4 bits
Cole Johnson
Considere o -(8)que em C ++ é o mesmo que -8- é negação aplicada a um literal, não um literal negativo. O literal é 8, que não se encaixa em um número inteiro de 4 bits assinado, portanto deve ser não assinado. O padrão é 1000. Até agora, sua resposta está correta. A negação do complemento dos dois 1000em 4 bits é 1000que não importa se está assinado ou não. Sua resposta diz: "interprete os bits como um número inteiro assinado", que cria o valor -8após a negação do complemento dos dois, exatamente como era antes da negação.
Ben Voigt
Obviamente, no "C ++ de 4 bits" não há "interpretar os bits como um passo inteiro assinado". O literal se torna o menor tipo que pode expressá-lo, que é um número inteiro de 4 bits sem sinal . O valor do literal é 8. A negação é aplicada (módulo 16), resultando em uma resposta final de 8. A codificação ainda é 1000, mas o valor é diferente porque um tipo não assinado foi escolhido.
Ben Voigt