@shrExact em Zig
O @shrExact realiza um shift right (deslocamento à direita) e garante que nenhum bit significativo é perdido (descartado pela parte inferior). Se algum bit 1 for deslocado para fora (ou seja, se os bits inferiores não forem zero), causa panic em modo safe. É a versão “exata” do shift right.
Sintaxe
@shrExact(value: T, shift_amt: Log2T) T
Parâmetros
- value (
T): Valor inteiro a ser deslocado. - shift_amt (
Log2T): Número de posições para deslocar à direita.
Valor de retorno
Retorna T — o resultado do shift right, garantindo que bits descartados eram zero.
Exemplos práticos
Exemplo 1: Divisão exata por potência de 2
const std = @import("std");
pub fn main() void {
// 16 >> 2 = 4 (exato, sem perda)
const a: u8 = @shrExact(@as(u8, 16), 2);
std.debug.print("16 >> 2 = {}\n", .{a}); // 4
// 64 >> 3 = 8 (exato)
const b: u32 = @shrExact(@as(u32, 64), 3);
std.debug.print("64 >> 3 = {}\n", .{b}); // 8
// Isso causaria panic em safe mode:
// @shrExact(@as(u8, 7), 1) — 7 é ímpar, bit inferior seria perdido!
}
Exemplo 2: Extrair campo de bits alinhado
const std = @import("std");
fn extrairByte(valor: u32, posicao: u5) u8 {
// Posição deve ser múltiplo de 8
const deslocado = @shrExact(valor & (@as(u32, 0xFF) << posicao), posicao);
return @intCast(deslocado);
}
pub fn main() void {
const valor: u32 = 0xAABBCCDD;
std.debug.print("Byte 0: 0x{X:0>2}\n", .{extrairByte(valor, 0)}); // DD
std.debug.print("Byte 8: 0x{X:0>2}\n", .{extrairByte(valor, 8)}); // CC
std.debug.print("Byte 16: 0x{X:0>2}\n", .{extrairByte(valor, 16)}); // BB
}
Exemplo 3: Verificar alinhamento
const std = @import("std");
fn verificarAlinhamento(endereco: usize, comptime alinhamento: u6) bool {
// Se o endereço é alinhado, os bits inferiores são zero
// @shrExact só funciona se os bits inferiores forem zero
const mascara = (@as(usize, 1) << alinhamento) - 1;
return (endereco & mascara) == 0;
}
pub fn main() void {
std.debug.print("16 alinhado a 4? {}\n", .{verificarAlinhamento(16, 2)}); // true
std.debug.print("17 alinhado a 4? {}\n", .{verificarAlinhamento(17, 2)}); // false
std.debug.print("256 alinhado a 16? {}\n", .{verificarAlinhamento(256, 4)}); // true
}
Casos de uso comuns
- Divisão exata por potência de 2: Quando se sabe que o resultado é inteiro.
- Extração de campos de bits: Deslocar campos alinhados de registradores.
- Conversão de unidades: Bytes para KB (shift 10) quando o valor é múltiplo.
- Decodificação de protocolos: Extrair campos de cabeçalhos binários.
Diferença entre @shrExact e o operador >>
O operador >> padrão em Zig descarta silenciosamente quaisquer bits deslocados para fora. Isso é aceitável em muitos contextos, mas @shrExact adiciona a garantia de que o descarte não ocorre — se algum bit 1 for perdido, o programa falha de forma detectável.
Essa distinção é análoga a @shlExact: o “Exact” comunica que o shift é matematicamente exato (equivalente a uma divisão inteira sem resto).
Uso para divisão inteira verificada
@shrExact(x, n) é equivalente a x / 2^n, mas apenas quando x é divisível por 2^n. Use quando a divisibilidade for uma invariante do algoritmo:
const std = @import("std");
// Converter bytes para palavras de 4 bytes (deve ser múltiplo de 4)
fn bytesParaPalavras(bytes: u32) u32 {
// @shrExact garante que bytes é múltiplo de 4
// Se não for, panic em safe mode — indicando bug no código
return @shrExact(bytes, 2);
}
// Converter bytes para kilobytes (deve ser múltiplo de 1024)
fn bytesParaKb(bytes: usize) usize {
return @shrExact(bytes, 10);
}
Comparação com equivalente em C
Em C, shift right não verifica se bits são perdidos:
// C: shift right silencioso
unsigned int resultado = 7 >> 1; // 3, mas o bit 0 foi perdido silenciosamente
// C: verificar divisibilidade antes (extra overhead)
unsigned int valor = 7;
if (valor % 2 != 0) {
// não é exatamente divisível
}
unsigned int resultado = valor >> 1;
Em Zig, @shrExact documenta e verifica a intenção:
// Zig: explícito — queremos divisão exata
const resultado = @shrExact(@as(u32, 8), 1); // ok: 4
// @shrExact(@as(u32, 7), 1) causaria panic — 7 é ímpar
Casos de uso em protocolos de rede
Campos de comprimento em cabeçalhos binários frequentemente codificam tamanhos em múltiplos de unidades maiores (palavras de 4 bytes, blocos de 512 bytes). @shrExact é ideal para decodificá-los:
const std = @import("std");
// Cabeçalho IP: campo IHL está em palavras de 32 bits
const CabecalhoIP = packed struct {
ihl: u4, // Internet Header Length em palavras de 32 bits
versao: u4,
// ... outros campos
};
fn tamanhoHeaderBytes(ihl: u4) u8 {
// ihl * 4 bytes por palavra; o inverso deve ser exato
return @as(u8, ihl) * 4;
}
fn ihlDoTamanho(tamanho_bytes: u8) u4 {
// Tamanho deve ser múltiplo de 4
const ihl = @shrExact(@as(u32, tamanho_bytes), 2);
return @intCast(ihl);
}
Erros comuns
1. Usar @shrExact quando o valor pode ter bits inferiores não-zero:
var valor: u32 = lerDaRede(); // valor desconhecido em runtime
// PERIGOSO se valor pode não ser múltiplo de 4:
const resultado = @shrExact(valor, 2); // panic potencial
// SEGURO: verificar primeiro ou usar >> se descarte é aceitável
const resultado = if (valor % 4 == 0) @shrExact(valor, 2) else valor >> 2;
2. Confundir com divisão aritmética para tipos signed: Para inteiros negativos, >> é aritmético (preserva o sinal) em Zig. @shrExact ainda requer que os bits descartados sejam zero, mas bits inferiores de números negativos em complemento de dois podem ser não-zero mesmo para valores “múltiplos”.
Perguntas Frequentes
P: @shrExact é mais lento que >>?
R: Em modo Debug/ReleaseSafe, @shrExact adiciona uma verificação extra (testar se os bits inferiores são zero). Em ReleaseFast, o comportamento é o mesmo que >>, sem overhead — o compilador emite apenas a instrução de shift.
P: Posso usar @shrExact em comptime?
R: Sim. Em comptime, se o valor não for exatamente divisível, o compilador emite um erro de compilação — o que é ainda melhor que um panic em runtime.
P: Qual a relação entre @shrExact e @ctz?
R: @ctz (count trailing zeros) conta quantos zeros existem nos bits inferiores, o que indica a maior potência de 2 pela qual o valor é divisível. Você pode usar @ctz(x) >= n para verificar se @shrExact(x, n) seria seguro sem causar panic.
Builtins relacionados
- @shlExact — Shift left exato
- @clz — Contar zeros à esquerda
- @ctz — Contar zeros à direita
- @popCount — Contar bits em 1