Atualmente estou estudando criptografia e estava pensando em maneiras de reverter os nibbles de um byte (ex. 0xF5
=> 0x5F
). Eu pensei nessa solução:
byte >> 4 | (byte & 0x0F) << 4
Outras soluções que encontrei on-line eram semelhantes, mas adicionavam um operando extra mascarando o nibble esquerdo de um byte:
(byte & 0xF0) >> 4 | (byte & 0x0F) << 4
Em binário, a segunda solução se parece com isto:
# Extract the right nibble of a byte and shift to the left
[0xF5] 1111 0101 # Original Value
[0x0F] 0000 1111 & # Mask Right Nibble
[0x05] 0000 0101 = # Extracted Right Nibble
[0x50] 1010 0000 << 4 # Shift Four Bits to the Left
# Extract the left nibble of a byte and shift to the right
[0xF5] 1111 0101 # Original Value
[0xF0] 1111 0000 & # Mask Left Nibble
[0xF0] 1111 0000 = # Extracted Left Nibble
[0x0F] 0000 1111 >> 4 # Shift Four Bits to the Right
# Combine the shifted nibbles together
[0x05] 0000 1111 # Left Nibble Shifted to the Right
[0xF0] 0101 0000 | # Right Nibble Shifted to the Left
[0xF5] 0101 1111 = # New Value
Corrija-me se eu estiver errado, mas a segunda solução é útil se você estiver lidando com um tipo de dado que é maior que um byte e está focado somente no byte menos significativo. Então, se você fosse deslocar sem mascarar, os bits dos bytes de ordem mais alta se propagariam para o nibble esquerdo. Assim, mascarar o nibble esquerdo é necessário em relação a esse cenário.
# Bits shift into the least significant byte without masking
[0x0AF5] 0000 1010 1111 0101
[0x00AF] 0000 0000 1010 1111 >> 4
Por outro lado, se você estiver realmente trabalhando com um byte, mascarar o nibble esquerdo não é redundante, já que os bits do nibble esquerdo serão zerados após deslocar quatro bits para a direita?
[0xF5] 1111 0101
[0x0F] 0000 1111 >> 4
Talvez existam outras razões para mascarar os bits de nibble esquerdos que eu desconheço. Por exemplo, outros sistemas podem se comportar de forma diferente onde a segunda solução é necessária.
Estou com a cabeça no lugar certo ou há algo que devo considerar?
Aqui está um código de exemplo para maior esclarecimento:
typedef uint8_t byte;
static inline
byte swap_nibbles(byte bits) {
return bits >> 4 | (bits & 0x0F) << 4;
}
Você não mostra a definição de
byte
. Se ele tem um inteiro de oito bits assinado, então este código:impressões, em muitas implementações C:
Embora
byte
inicialmente contenha os bits 91 16 , embyte >> 4
, ele é promovido paraint
. Como esses bits representam o valor −111, isso produz um com esse valor, que é, com um FFFFFF91 16int
de quatro bytes . Então produz FFFFFF9 16 . (Isso é definido pela implementação.) ORing isso com o 10 16 do lado direito do produz FFFFFF9 16 , e então atribuir isso a converte para . Isso é definido pela implementação, mas o resultado mais comum é encapsular o módulo 256, produzindo os bits F9 16 , representando o valor −7.int
>> 4
|
byte
signed char
Em contraste, usando
byte = (byte &0xF0) >> 4 | (byte & 0x0F) << 4;
estampas:Entretanto, se
byte
tiver um tipo inteiro de oito bits sem sinal, a promoção paraint
não será um problema, poisint
há espaço suficiente para armazenar os valores nessas expressões sem incorrer em problemas de sinal ou estouro.Compiladores modernos podem ser altamente otimizados para operações de bit-twiddling, então é provável que qualquer uma dessas expressões seja bem otimizada para a arquitetura de destino. Sua primeira preocupação deve ser a correção.