Em C, os operadores de turno ( <<
, >>
) são aritméticos ou lógicos?
c
binary
bit-manipulation
bit-shift
littlebyte
fonte
fonte
Respostas:
De acordo com a K&R 2nd edition, os resultados dependem da implementação para mudanças corretas dos valores assinados.
A Wikipedia diz que C / C ++ 'geralmente' implementa uma mudança aritmética nos valores assinados.
Basicamente, você precisa testar seu compilador ou não confiar nele. Minha ajuda do VS2008 para o atual compilador MS C ++ diz que o compilador faz uma alteração aritmética.
fonte
Ao deslocar para a esquerda, não há diferença entre deslocamento aritmético e lógico. Ao mudar para a direita, o tipo de mudança depende do tipo de valor que está sendo alterado.
(Como pano de fundo para os leitores não familiarizados com a diferença, um deslocamento "lógico" para a direita em 1 bit desloca todos os bits para a direita e preenche o bit mais à esquerda com um 0. Um deslocamento "aritmético" deixa o valor original no bit mais à esquerda A diferença se torna importante ao lidar com números negativos.)
Ao mudar um valor não assinado, o operador >> em C é uma mudança lógica. Ao mudar um valor assinado, o operador >> é uma mudança aritmética.
Por exemplo, assumindo uma máquina de 32 bits:
fonte
TL; DR
Considere
i
en
sejam os operandos esquerdo e direito, respectivamente, de um operador de turno; o tipo dei
, após a promoção inteira, sejaT
. Assumindon
estar dentro[0, sizeof(i) * CHAR_BIT)
- indefinido de outra forma - temos os seguintes casos:† a maioria dos compiladores implementa isso como deslocamento aritmético
‡ indefinido se o valor ultrapassar o tipo de resultado T; tipo promovido de i
Mudança
Primeiro, a diferença entre mudanças lógicas e aritméticas do ponto de vista matemático, sem se preocupar com o tamanho do tipo de dados. Os turnos lógicos sempre preenchem os bits descartados com zeros, enquanto o turno aritmético os preenche apenas com o turno esquerdo, mas, no turno direito, ele copia o MSB preservando o sinal do operando (assumindo o complemento de dois para codificar valores negativos).
Em outras palavras, a mudança lógica considera o operando alterado como apenas um fluxo de bits e os move, sem se preocupar com o sinal do valor resultante. O deslocamento aritmético o vê como um número (assinado) e preserva o sinal à medida que os turnos são feitos.
Um deslocamento aritmético esquerdo de um número X por n equivale a multiplicar X por 2 n e, portanto, equivalente a deslocamento esquerdo lógico; uma mudança lógica também daria o mesmo resultado, já que o MSB de qualquer forma cai no final e não há nada a preservar.
Um deslocamento aritmético à direita de um número X por n é equivalente à divisão inteira de X por 2 n SOMENTE se X for não negativo! Divisão inteira não é senão divisão matemática e arredondada para 0 ( trunc ).
Para números negativos, representados pela codificação do complemento de dois, deslocar para a direita por n bits tem o efeito de dividi-lo matematicamente por 2 n e arredondar para −∞ ( piso ); portanto, o deslocamento à direita é diferente para valores não negativos e negativos.
onde
÷
é a divisão matemática,/
é a divisão inteira. Vejamos um exemplo:Como Guy Steele apontou , essa discrepância levou a erros em mais de um compilador . Aqui, não negativo (matemática) pode ser mapeado para valores não negativos não assinados e assinados (C); ambos são tratados da mesma forma e o deslocamento à direita é feito por divisão inteira.
Portanto, lógico e aritmético são equivalentes no deslocamento para a esquerda e para valores não negativos no deslocamento para a direita; é na mudança correta de valores negativos que eles diferem.
Tipos de operando e resultado
Norma C99 §6.5.7 :
No trecho acima, ambos os operandos se tornam
int
(devido à promoção de número inteiro); seE2
foi negativo ouE2 ≥ sizeof(int) * CHAR_BIT
então a operação é indefinida. Isso ocorre porque o deslocamento de mais do que os bits disponíveis certamente transbordará. Se tivesseR
sido declarado comoshort
, oint
resultado da operação de turno seria implicitamente convertido emshort
; uma conversão restritiva, que pode levar ao comportamento definido pela implementação se o valor não for representável no tipo de destino.Desvio à esquerda
Como os desvios à esquerda são os mesmos para ambos, os bits desocupados são simplesmente preenchidos com zeros. Ele então afirma que, para os tipos não assinados e assinados, é uma mudança aritmética. Estou interpretando isso como uma mudança aritmética, pois as mudanças lógicas não se preocupam com o valor representado pelos bits, mas apenas como um fluxo de bits; mas o padrão fala não em termos de bits, mas definindo-o em termos do valor obtido pelo produto de E1 com 2 E2 .
A ressalva aqui é que, para tipos assinados, o valor deve ser não negativo e o valor resultante deve ser representável no tipo de resultado. Caso contrário, a operação é indefinida. O tipo de resultado seria o tipo do E1 após a aplicação da promoção integral e não o tipo de destino (a variável que manterá o resultado). O valor resultante é convertido implicitamente no tipo de destino; se não for representável nesse tipo, a conversão será definida pela implementação (C99 §6.3.1.3 / 3).
Se E1 for um tipo assinado com um valor negativo, o comportamento do deslocamento para a esquerda é indefinido. Esse é um caminho fácil para um comportamento indefinido que pode ser facilmente esquecido.
Deslocamento para a direita
O deslocamento certo para valores não negativos não assinados e assinados é bastante direto; os bits vagos são preenchidos com zeros. Para valores negativos assinados, o resultado da mudança à direita é definido pela implementação. Dito isso, a maioria das implementações como GCC e Visual C ++ implementam a mudança à direita como mudança aritmética, preservando o bit de sinal.
Conclusão
Diferente do Java, que possui um operador especial
>>>
para deslocamento lógico além do usual>>
e<<
, C e C ++ têm apenas deslocamento aritmético com algumas áreas deixadas indefinidas e definidas pela implementação. A razão pela qual eu os considero aritméticos é devido à formulação padrão da operação matematicamente, em vez de tratar o operando alterado como um fluxo de bits; talvez essa seja a razão pela qual deixa essas áreas sem definição de implementação, em vez de apenas definir todos os casos como mudanças lógicas.fonte
-Inf
números negativos e positivos. Arredondar para 0 de um número positivo é um caso particular de arredondamento para-Inf
. Ao truncar, você sempre descarta valores ponderados positivamente e, portanto, subtrai do resultado preciso.Em termos do tipo de turno que você recebe, o importante é o tipo de valor que você está mudando. Uma fonte clássica de erros é quando você muda um literal para, por exemplo, mascarar os bits. Por exemplo, se você deseja soltar o bit mais à esquerda de um número inteiro não assinado, tente isso como sua máscara:
Infelizmente, isso causará problemas porque a máscara terá todos os seus bits configurados porque o valor que está sendo alterado (~ 0) é assinado e, portanto, é realizada uma mudança aritmética. Em vez disso, você deseja forçar uma mudança lógica declarando explicitamente o valor como não assinado, ou seja, fazendo algo assim:
fonte
Aqui estão as funções para garantir o deslocamento lógico à direita e o deslocamento aritmético à direita de um int em C:
fonte
Quando você faz - desvio à esquerda por 1, você multiplica por 2 - desvio à direita por 1, divide por 2
fonte
Bem, procurei na wikipedia e eles têm o seguinte a dizer:
Portanto, parece que depende do seu compilador. Também nesse artigo, observe que o deslocamento para a esquerda é o mesmo para aritmética e lógica. Eu recomendaria fazer um teste simples com alguns números assinados e não assinados na caixa de borda (conjunto de bits altos, é claro) e ver qual é o resultado no seu compilador. Eu também recomendaria evitar, dependendo de ser um ou outro, pois parece que C não tem padrão, pelo menos se for razoável e possível evitar essa dependência.
fonte
Desvio à esquerda
<<
De alguma forma, isso é fácil e sempre que você usa o operador de turno, é sempre uma operação pouco a pouco, portanto não podemos usá-lo com uma operação double e float. Sempre que deixamos o deslocamento um zero, ele sempre é adicionado ao bit menos significativo (
LSB
).Mas, no turno certo
>>
, temos que seguir uma regra adicional e essa regra é chamada "cópia de bit de sinal". O significado de "cópia do bit de sinal" é que, se o bit mais significativo (MSB
) for definido, depois de uma mudança à direita novamenteMSB
, será definido se foi redefinido e redefinido novamente, significa que se o valor anterior for zero e depois de mudar novamente, o bit é zero se o bit anterior era um, depois do turno é novamente um. Esta regra não é aplicável a um deslocamento para a esquerda.O exemplo mais importante no deslocamento para a direita, se você mudar qualquer número negativo para o deslocamento para a direita, depois de algumas mudanças, o valor finalmente chegará a zero e, depois disso, se a mudança for -1, qualquer número de vezes que o valor permanecerá o mesmo. Por favor, verifique.
fonte
gccnormalmente usará turnos lógicos em variáveis não assinadas e para turnos à esquerda em variáveis assinadas. O deslocamento aritmético para a direita é o verdadeiramente importante, porque irá estender a variável.
gcc usará isso quando aplicável, como outros compiladores provavelmente o farão.
fonte
O GCC faz
for -ve -> Mudança aritmética
Para + ve -> Mudança Lógica
fonte
De acordo com muitos c compiladores:
<<
é um desvio à esquerda aritmético ou um desvio à esquerda bit a bit.>>
é um deslocamento aritmético para a direita ou deslocamento para a direita em bits.fonte
>>
Aritmética ou bit a bit (lógica)?" Você respondeu ">>
é aritmético ou bit a bit". Isso não responde à pergunta.<<
e>>
os operadores são lógicos, não aritmética