Encontrei o código de alguém que parece acreditar que há um problema ao subtrair um inteiro sem sinal de outro inteiro do mesmo tipo quando o resultado seria negativo. Portanto, esse código como este estaria incorreto mesmo se funcionar na maioria das arquiteturas.
unsigned int To, Tf;
To = getcounter();
while (1) {
Tf = getcounter();
if ((Tf-To) >= TIME_LIMIT) {
break;
}
}
Esta é a única citação vagamente relevante do padrão C que pude encontrar.
Um cálculo envolvendo operandos sem sinal nunca pode transbordar, porque um resultado que não pode ser representado pelo tipo inteiro sem sinal resultante é o módulo reduzido do número que é um maior que o maior valor que pode ser representado pelo tipo resultante.
Suponho que essa citação possa significar que, quando o operando certo é maior, a operação é ajustada para ser significativa no contexto de números de módulo truncados.
ie
0x0000 - 0x0001 == 0x 1 0000 - 0x0001 == 0xFFFF
ao contrário de usar a semântica assinada dependente da implementação:
0x0000 - 0x0001 == (sem sinal) (0 + -1) == (0xFFFF mas também 0xFFFE ou 0x8001)
Qual ou qual interpretação está certa? Está definido de alguma forma?
Respostas:
O resultado de uma subtração que gera um número negativo em um tipo sem sinal é bem definido:
Como você pode ver,
(unsigned)0 - (unsigned)1
é igual a -1 módulo UINT_MAX + 1, ou em outras palavras, UINT_MAX.Observe que, embora diga "Um cálculo envolvendo operandos sem sinal nunca pode estourar", o que pode levar você a acreditar que se aplica apenas para exceder o limite superior, isso é apresentado como uma motivação para a parte de ligação real da frase: "a resultado que não pode ser representado pelo tipo inteiro sem sinal resultante é módulo reduzido o número que é um maior do que o maior valor que pode ser representado pelo tipo resultante. " Essa frase não se restringe ao estouro do limite superior do tipo e se aplica igualmente a valores muito baixos para serem representados.
fonte
uint
sempre teve a intenção de representar o anel matemático dos inteiros0
por meioUINT_MAX
, com as operações de módulo de adição e multiplicaçãoUINT_MAX+1
, e não porque de um estouro. No entanto, isso levanta a questão de por que, se os anéis são um tipo de dados tão fundamental, a linguagem não oferece suporte mais geral para anéis de outros tamanhos.Quando você trabalha com tipos não assinados , a aritmética modular (também conhecida como comportamento "wrap around" ) está ocorrendo. Para entender essa aritmética modular , basta dar uma olhada nestes relógios:
9 + 4 = 1 ( 13 mod 12 ), então para a outra direção é: 1 - 4 = 9 ( -3 mod 12 ). O mesmo princípio é aplicado ao trabalhar com tipos sem sinal. Se o tipo de resultado for
unsigned
, então a aritmética modular ocorre.Agora observe as seguintes operações armazenando o resultado como um
unsigned int
:Quando você quiser ter certeza de que o resultado é
signed
, armazene-o nasigned
variável ou faça o castsigned
. Quando você deseja obter a diferença entre os números e certificar-se de que a aritmética modular não será aplicada, você deve considerar o uso deabs()
funções definidas emstdlib.h
:Tenha muito cuidado, especialmente ao escrever as condições, porque:
mas
fonte
int d = abs(five - seven);
não é boa. Primeirofive - seven
é calculado: a promoção deixa os tipos de operando comounsigned int
, o resultado é calculado como módulo(UINT_MAX+1)
e avalia paraUINT_MAX-1
. Então, esse valor é o parâmetro real paraabs
, o que é uma má notícia.abs(int)
causa um comportamento indefinido passando o argumento, uma vez que não está no intervalo eabs(long long)
provavelmente pode conter o valor, mas o comportamento indefinido ocorre quando o valor de retorno é coagidoint
a inicializard
.operator T()
. A adição nas duas expressões que estamos discutindo é realizada em tipounsigned int
, com base nos tipos de operando. O resultado da adição éunsigned int
. Em seguida, esse resultado é convertido implicitamente para o tipo necessário no contexto, uma conversão que falha porque o valor não é representável no novo tipo.double x = 2/3;
vsdouble y = 2.0/3;
Bem, a primeira interpretação está correta. No entanto, seu raciocínio sobre a "semântica assinada" neste contexto está errado.
Novamente, sua primeira interpretação está correta. A aritmética sem sinal segue as regras da aritmética de módulo, o que significa que
0x0000 - 0x0001
avalia para0xFFFF
tipos sem sinal de 32 bits.No entanto, a segunda interpretação (aquela baseada na "semântica com sinais") também é necessária para produzir o mesmo resultado. Ou seja, mesmo que você avalie
0 - 1
no domínio do tipo assinado e obtenha-1
como resultado intermediário, isso-1
ainda é necessário para produzir0xFFFF
quando, posteriormente, for convertido para o tipo não assinado. Mesmo que alguma plataforma use uma representação exótica para inteiros com sinal (complemento de 1, magnitude com sinal), essa plataforma ainda é obrigada a aplicar regras de aritmética do módulo ao converter valores inteiros com sinal em valores sem sinal.Por exemplo, esta avaliação
ainda tem a garantia de produzir
UINT_MAX
emc
, mesmo se a plataforma estiver usando uma representação exótica para inteiros assinados.fonte
Com números sem sinal de tipo
unsigned int
ou maiores, na ausência de conversões de tipo,a-b
é definido como o número sem sinal que, quando adicionadob
, renderáa
. A conversão de um número negativo em sem sinal é definida como o resultado do número que, quando adicionado ao número original com sinal invertido, renderá zero (portanto, converter -5 em sem sinal renderá um valor que, quando adicionado a 5, resultará em zero) .Observe que os números sem sinal menores do que
unsigned int
podem ser promovidos ao tipoint
antes da subtração, o comportamento dea-b
dependerá do tamanho deint
.fonte