São os operadores shift (<<, >>) aritméticos ou lógicos em C?
-
08-06-2019 - |
Pergunta
Em C, são a mudança operadores (<<
, >>
) aritmética ou lógica?
Solução
De acordo com a K&R 2ª edição os resultados são dependente de implementação para a direita mudanças de valores assinados.
Taxas diz-se que C/C++ 'normalmente' implementa uma aritmética a mudança de valores assinados.
Basicamente o que você precisa para testar o seu compilador ou não contar com ele.Meu VS2008 ajuda para o atual MS C++ compilador diz que o compilador faz uma aritmética shift.
Outras dicas
Quando deslocar para a esquerda, não há nenhuma diferença entre a aritmética e lógica de mudança.Quando deslocar para a direita, o tipo de mudança depende do tipo do valor a ser deslocado.
(Como plano de fundo para os leitores não familiarizados com a diferença, uma "lógica" shift direita por 1 bit muda todos os bits para a direita e preenche o bit mais à esquerda com um 0.Uma "aritmética" shift deixa o valor original o bit mais à esquerda.A diferença torna-se importante quando se lida com números negativos.)
Ao deslocar um valor sem sinal, > > operador em C é uma lógica de mudança.Ao deslocar um valor assinado, > > operador é uma aritmética shift.
Por exemplo, supondo que uma máquina de 32 bits:
signed int x1 = 5;
assert((x1 >> 1) == 2);
signed int x2 = -5;
assert((x2 >> 1) == -3);
unsigned int x3 = (unsigned int)-5;
assert((x3 >> 1) == 0x7FFFFFFD);
TL;DR
Considere i
e n
para ser a esquerda e para a direita operandos, respectivamente, de uma mudança de operador;o tipo de i
, após inteiro promoção, ser T
.Supondo n
para estar em [0, sizeof(i) * CHAR_BIT)
— não definido de outra forma — temos, nestes casos:
| Direction | Type | Value (i) | Result |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | < 0 | Implementation-defined† |
| Left (<<) | unsigned | ≥ 0 | (i * 2ⁿ) % (T_MAX + 1) |
| Left | signed | ≥ 0 | (i * 2ⁿ) ‡ |
| Left | signed | < 0 | Undefined |
† a maioria dos compiladores implementar isso como aritmética shift
‡ indefinido, se o valor excede o de resultado tipo T;promovido tipo de eu
Mudança
O primeiro é a diferença entre lógica e aritmética desloca de um ponto de vista matemático, sem se preocupar com o tamanho de tipo de dados.Lógico turnos sempre preenche descartados bits com zeros, enquanto aritmética shift preenche com zeros apenas para deslocar para a esquerda, mas para a direita shift copia o MSB preservando assim o sinal do operando (supondo um complemento de dois codificação para valores negativos).
Em outras palavras, o deslocamento lógico olha deslocada operando apenas como uma sequência de bits e movê-los, sem se preocupar com o sinal do valor resultante.Aritmética shift vê isso como uma (assinado) número e preserva o sinal de que mudanças são feitas.
Uma esquerda shift aritmético de um número X por n é equivalente a multiplicar X por 2n e é, portanto, equivalente à lógica de deslocamento à esquerda;uma lógica de mudança também daria o mesmo resultado, desde MSB enfim chega ao fim e não há nada para preservar.
O direito aritmética mudança de um número X por n é equivalente à divisão de X por 2n SOMENTE se X é não-negativo!Divisão de número inteiro não é nada, mas matemática divisão e rodada para 0 (trunc).
Para números negativos, representados por duas complemento de codificação, deslocando para a direita por n bits tem o efeito de, matematicamente, ao dividi-lo por 2n e arredondamento para −∞ (chão);assim, o direito de deslocamento é diferente para não-negativos e negativos.
para X ≥ 0, X >> n = X / 2n = trunc(X ÷ 2n)
para X < 0, X >> n = floor(X ÷ 2n)
onde ÷
é matemática divisão, /
é a divisão de número inteiro.Vejamos um exemplo:
37)10 = 100101)2
37 ÷ 2 = 18.5
37 / 2 = 18 (arredondamento de 18,5 para 0) = 10010)2 [resultado da aritmética para a direita shift]
-37)10 = 11011011)2 (considerando-se um complemento de dois, de 8 bits de representação)
-37 ÷ 2 = -18.5
-37 / 2 = -18 (arredondamento de 18,5 para 0) = 11101110)2 [NÃO o resultado da aritmética para a direita shift]
-37 >> 1 = -19 (arredondamento de 18,5 para −∞) = 11101101)2 [resultado da aritmética para a direita shift]
Como Guy Steele apontou, esta discrepância levou a erros em mais de um compilador.Aqui não negativo (matemática) pode ser mapeado para não assinados e assinado valores não-negativos (C);ambos são tratados da mesma e clique com o botão direito deslocamento deles é feito pela divisão de número inteiro.
Tão lógica e aritmética são equivalentes na esquerda-deslocamento e para valores não-negativos no direito de deslocamento;é no direito de mudança de valores negativos que eles diferem.
Operando e Tipos de Resultado
Padrão C99 §6.5.7:
Cada um dos operandos devem ter tipos inteiros.
O inteiro promoções são realizadas em cada um dos operandos.O tipo do resultado é que o operando esquerdo promovido.Se o valor do operando direito é negativo ou maior que ou igual à largura do operando esquerdo promovido, o comportamento é indefinido.
short E1 = 1, E2 = 3;
int R = E1 << E2;
No trecho acima, ambos os operandos tornar int
(devido ao número inteiro de promoção);se E2
foi negativo ou E2 ≥ sizeof(int) * CHAR_BIT
em seguida, a operação é indefinido.Isto é porque a deslocar mais do que o bits é certamente vai transbordar.Tinha R
sido declarada como short
, o int
resultado da operação de deslocamento iria ser implicitamente convertido para short
;uma conversão de restrição, que pode levar à implementação definido pelo comportamento se o valor não é representável no tipo de destino.
Shift Esquerdo
O resultado de E1 << E2 E1 é de esquerda deslocada E2 posições de bit;desocupado bits são preenchidos com zeros.Se E1 tem um tipo não assinado, o valor do resultado é E1×2E2, redução do módulo um a mais do que o valor máximo representáveis no tipo de resultado.Se E1 tem uma assinado o tipo e o não-valor negativo, e E1×2E2 é representáveis no tipo de resultado e, em seguida, que é o valor resultante;caso contrário, o comportamento é indefinido.
Como a esquerda desloca são as mesmas para ambos, o desocupado bits são simplesmente preenchidos com zeros.Em seguida, ele afirma que para tanto não assinados e assinado tipos é uma aritmética shift.Eu estou interpretando-a como aritmética shift desde lógico turnos de não se preocupar com o valor representado por bits, ele apenas olha para ele como um fluxo de bits;mas a norma não fala em termos de bits, mas definindo-a em termos do valor obtido pelo produto de E1, com 2E2.
O truque aqui é que, para assinada tipos de valor deve ser não-negativo e o valor resultante deve ser representáveis no tipo de resultado.Caso contrário, a operação é indefinido. O tipo de resultado seria o tipo de E1 após a aplicação de promoção integral, e não o de destino (a variável que vai armazenar o resultado) tipo.O valor resultante é implicitamente convertido para o tipo de destino;se não representáveis em que tipo e, em seguida, a conversão é definido para implementação (C99 §6.3.1.3/3).
Se E1 é assinado um tipo com um valor negativo, em seguida, o comportamento da esquerda mudança é indefinido. Esta é uma rota fácil para indefinido comportamentos que podem facilmente passar despercebidos.
Shift Direita
O resultado de E1 >> E2 E1 é clique com o botão direito deslocado E2 posições de bit.Se E1 tem um tipo não assinado ou se E1 tem assinado um tipo e um valor negativo, o valor do resultado é a parte integral do quociente de E1/2E2.Se E1 tem assinado um tipo e um valor negativo, o valor resultante é definido para implementação.
Shift direita por não assinados e assinado valores não-negativos são bastante direta;a vaga de bits são preenchidos com zeros. Assinado valores negativos o resultado do direito de deslocamento é definido para implementação. O que disse, a maioria das implementações, como o GCC e O Visual C++ implementar deslocamentos à direita como aritmética mudança preservando o bit de sinal.
Conclusão
Ao contrário de Java, que tem um operador especial >>>
para o desvio lógico, para além do habitual >>
e <<
, C e C++ têm apenas aritmética mudando com algumas áreas deixado indefinido e definido para implementação.A razão de eu considerar eles como a aritmética é devido para a formulação padrão de operação matematicamente, ao invés de tratar o deslocado operando como um fluxo de bits;esta é talvez a razão pela qual ele deixa as áreas da onu/definido para implementação em vez de apenas a definição de todos os casos, a lógica de turnos.
Em termos do tipo de mudança que você conseguir, o importante é o tipo do valor que você está se deslocando.Uma clássica fonte de erros é quando você mudar de uma literal para, digamos, a máscara de bits.Por exemplo, se você queria largar mais à esquerda os bits de um número inteiro não assinado, em seguida, você pode tentar este como a sua máscara:
~0 >> 1
Infelizmente, isso vai causar problemas porque a máscara vai ter todos os seus bits definido como o valor a ser deslocado (~0) é assinado, assim, uma aritmética mudança é realizada.Em vez disso, você deseja forçar uma mudança lógica por declarar explicitamente o valor como unsigned, i.é.fazendo algo parecido com isto:
~0U >> 1;
Aqui estão as funções para garantir a lógica do direito e de deslocamento aritmético à direita deslocamento de um int em C:
int logicalRightShift(int x, int n) {
return (unsigned)x >> n;
}
int arithmeticRightShift(int x, int n) {
if (x < 0 && n > 0)
return x >> n | ~(~0U >> n);
else
return x >> n;
}
Quando você fizer - shift da esquerda por 1, multiplique por 2 - shift direita por 1 a dividir por 2
x = 5
x >> 1
x = 2 ( x=5/2)
x = 5
x << 1
x = 10 (x=5*2)
Bem, eu olhei - a na wikipédia, e eles têm a dizer:
C, no entanto, tem apenas um shift direita operador >>.Muitos compiladores C escolha que a direita shift para executar a função em que tipo de número inteiro está sendo deslocado;muitas vezes, inteiros assinados são mudou usando a aritmética de mudança, e inteiros são deslocadas usando a lógica shift.
Então, parece que depende do seu compilador.Também nesse artigo, note que o shift da esquerda é o mesmo para operações aritméticas e lógicas.Eu recomendaria fazer um simples teste com alguns números assinados e não assinados na fronteira de caso (conjunto de bits alta, claro) e ver que o resultado é no seu compilador.Eu também recomendaria evitar dependendo sendo um ou outro, pois parece C não tem padrão, pelo menos se for razoável e possível, para evitar tal dependência.
Shift esquerdo <<
Este é, de alguma forma fácil e sempre que você usar o operador shift, é sempre um bit de operação, de modo que não pode usá-lo com um double e float operação.Sempre que saímos de mudança de um a zero, é sempre adicionado ao bit menos significativo (LSB
).
Mas em shift direita >>
temos que seguir uma regra e essa regra é chamado de "bit de sinal copiar".Significado de "bit de sinal copiar" se o bit mais significativo (MSB
) é definida, em seguida, depois de um shift direita novamente o MSB
será definido se ela foi redefinida, em seguida, é repor novamente, significa que, se o valor anterior era zero, em seguida, depois de deslocar novamente, o bit é zero se o anterior foi um pouco depois que a mudança é novamente um.Esta regra não é aplicável para a esquerda shift.
O exemplo mais importante no direito de mudar se você mudar qualquer número negativo para a direita shift e, em seguida, depois de algum mudando o valor para finalmente chegar a zero e, em seguida, depois de este se mudar essa -1 qualquer número de vezes que o valor permanecerá o mesmo.Verifique por favor.
gcc normalmente usa lógica turnos não assinado variáveis e para a esquerda desloca sobre assinado variáveis.O aritmético para a direita shift é o verdadeiramente importante porque ele vai assinar estender a variável.
gcc vai usar isso quando aplicável, como outros compiladores são propensos a fazer.
O GCC faz
de -ve - > Shift Aritmético
Para +ve -> Deslocamento Lógico
De acordo com muitos c compiladores:
<<
é uma aritmética shift esquerda ou para a esquerda bit a bit shift.>>
é uma aritmética direito shiftor bit a bit deslocamento à direita.