Como Codificar e Decodificar Hexadecimal em Zig

Introdução

A codificação hexadecimal representa cada byte como dois caracteres (0-9, a-f), sendo essencial para exibir hashes, endereços de memória, cores CSS e dados binários. Zig oferece suporte a hex via std.fmt para formatação e funções para parsing.

Nesta receita, você aprenderá a converter entre bytes e hexadecimal em ambas as direções.

Pré-requisitos

Bytes para Hexadecimal

const std = @import("std");

pub fn main() !void {
    const dados = [_]u8{ 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE };

    // Usando std.fmt (minúsculas)
    std.debug.print("Hex (lower): ", .{});
    for (dados) |b| {
        std.debug.print("{x:0>2}", .{b});
    }
    std.debug.print("\n", .{});

    // Usando std.fmt (maiúsculas)
    std.debug.print("Hex (upper): ", .{});
    for (dados) |b| {
        std.debug.print("{X:0>2}", .{b});
    }
    std.debug.print("\n", .{});

    // Com separador
    std.debug.print("Hex (sep):   ", .{});
    for (dados, 0..) |b, i| {
        if (i > 0) std.debug.print(":", .{});
        std.debug.print("{X:0>2}", .{b});
    }
    std.debug.print("\n", .{});

    // Usando fmtSliceHexLower
    std.debug.print("Slice hex:   {s}\n", .{std.fmt.fmtSliceHexLower(&dados)});
    std.debug.print("Slice HEX:   {s}\n", .{std.fmt.fmtSliceHexUpper(&dados)});
}

Saída esperada

Hex (lower): deadbeefcafebabe
Hex (upper): DEADBEEFCAFEBABE
Hex (sep):   DE:AD:BE:EF:CA:FE:BA:BE
Slice hex:   deadbeefcafebabe
Slice HEX:   DEADBEEFCAFEBABE

Hexadecimal para Bytes

const std = @import("std");

fn hexParaBytes(hex_str: []const u8, output: []u8) ![]u8 {
    if (hex_str.len % 2 != 0) return error.InvalidLength;
    if (output.len < hex_str.len / 2) return error.BufferTooSmall;

    var i: usize = 0;
    while (i < hex_str.len) : (i += 2) {
        const high = try charParaNibble(hex_str[i]);
        const low = try charParaNibble(hex_str[i + 1]);
        output[i / 2] = (high << 4) | low;
    }

    return output[0 .. hex_str.len / 2];
}

fn charParaNibble(c: u8) !u4 {
    return switch (c) {
        '0'...'9' => @intCast(c - '0'),
        'a'...'f' => @intCast(c - 'a' + 10),
        'A'...'F' => @intCast(c - 'A' + 10),
        else => return error.InvalidHexChar,
    };
}

pub fn main() !void {
    const hex_str = "48656c6c6f20576f726c6421";
    var buf: [256]u8 = undefined;

    const bytes = try hexParaBytes(hex_str, &buf);

    std.debug.print("Hex:    {s}\n", .{hex_str});
    std.debug.print("Texto:  {s}\n", .{bytes});
    std.debug.print("Bytes:  ", .{});
    for (bytes) |b| std.debug.print("{X:0>2} ", .{b});
    std.debug.print("\n", .{});
}

Converter Cores CSS

const std = @import("std");

const Cor = struct {
    r: u8,
    g: u8,
    b: u8,

    pub fn fromHex(hex: []const u8) !Cor {
        const offset: usize = if (hex[0] == '#') 1 else 0;
        const h = hex[offset..];

        if (h.len != 6) return error.InvalidColor;

        return .{
            .r = try parsearByte(h[0..2]),
            .g = try parsearByte(h[2..4]),
            .b = try parsearByte(h[4..6]),
        };
    }

    fn parsearByte(hex: []const u8) !u8 {
        return std.fmt.parseInt(u8, hex, 16) catch return error.InvalidHex;
    }

    pub fn toHex(self: Cor, buf: []u8) []u8 {
        return std.fmt.bufPrint(buf, "#{X:0>2}{X:0>2}{X:0>2}", .{ self.r, self.g, self.b }) catch "";
    }
};

pub fn main() !void {
    const cores_hex = [_][]const u8{
        "#FF0000", // Vermelho
        "#00FF00", // Verde
        "#0000FF", // Azul
        "#FFA500", // Laranja
        "#800080", // Roxo
    };

    std.debug.print("{s:<10} {s:>5} {s:>5} {s:>5}\n", .{ "Hex", "R", "G", "B" });
    std.debug.print("{s}\n", .{"-" ** 28});

    for (&cores_hex) |hex| {
        if (Cor.fromHex(hex)) |cor| {
            std.debug.print("{s:<10} {d:>5} {d:>5} {d:>5}\n", .{ hex, cor.r, cor.g, cor.b });
        } else |_| {
            std.debug.print("{s:<10} (inválida)\n", .{hex});
        }
    }
}

Hex Dump de Dados

const std = @import("std");

fn hexDump(dados: []const u8) void {
    var offset: usize = 0;
    while (offset < dados.len) {
        // Offset
        std.debug.print("{X:0>8}  ", .{offset});

        // Hex bytes
        const end = @min(offset + 16, dados.len);
        for (offset..end) |i| {
            std.debug.print("{X:0>2} ", .{dados[i]});
            if (i == offset + 7) std.debug.print(" ", .{});
        }

        // Padding se linha incompleta
        if (end - offset < 16) {
            const faltando = 16 - (end - offset);
            for (0..faltando) |_| std.debug.print("   ", .{});
            if (end - offset <= 8) std.debug.print(" ", .{});
        }

        // ASCII
        std.debug.print(" |", .{});
        for (dados[offset..end]) |b| {
            const c: u8 = if (b >= 32 and b < 127) b else '.';
            std.debug.print("{c}", .{c});
        }
        std.debug.print("|\n", .{});

        offset = end;
    }
}

pub fn main() !void {
    const texto = "Zig é uma linguagem de programação de sistemas!";
    std.debug.print("Hex dump de: \"{s}\"\n\n", .{texto});
    hexDump(texto);
}

Dicas e Boas Práticas

  1. fmtSliceHexLower/Upper: A forma mais simples de converter um slice para hex.

  2. Sempre use padding {X:0>2}: Garante que bytes menores que 0x10 tenham zero à esquerda.

  3. Valide o comprimento: Strings hex devem ter comprimento par.

  4. Case insensitive no parse: Aceite tanto a-f quanto A-F ao decodificar.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.