Como Trabalhar com Big Integers em Zig

Introdução

Tipos numéricos comuns como u64 e i128 têm limites de tamanho. Quando você precisa trabalhar com números que excedem esses limites – como em criptografia, fatoriais ou matemática combinatória – a biblioteca padrão do Zig oferece std.math.big.int.Managed para inteiros de precisão arbitrária.

Nesta receita, você aprenderá a criar e manipular big integers em Zig.

Pré-requisitos

Operações Básicas com Big Integer

const std = @import("std");
const Managed = std.math.big.int.Managed;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Criar big integers
    var a = try Managed.initSet(allocator, 1000000000);
    defer a.deinit();

    var b = try Managed.initSet(allocator, 999999999);
    defer b.deinit();

    // Adição
    var soma = try Managed.init(allocator);
    defer soma.deinit();
    try soma.add(&a, &b);

    // Multiplicação
    var produto = try Managed.init(allocator);
    defer produto.deinit();
    try produto.mul(&a, &b);

    // Converter para string para exibir
    const soma_str = try soma.toString(allocator, 10, .lower);
    defer allocator.free(soma_str);

    const produto_str = try produto.toString(allocator, 10, .lower);
    defer allocator.free(produto_str);

    std.debug.print("a = 1000000000\n", .{});
    std.debug.print("b = 999999999\n", .{});
    std.debug.print("a + b = {s}\n", .{soma_str});
    std.debug.print("a * b = {s}\n", .{produto_str});
}

Calcular Fatorial

Fatoriais crescem muito rápido e rapidamente excedem limites de tipos fixos:

const std = @import("std");
const Managed = std.math.big.int.Managed;

fn fatorial(allocator: std.mem.Allocator, n: u32) !Managed {
    var resultado = try Managed.initSet(allocator, 1);
    errdefer resultado.deinit();

    var i: u32 = 2;
    while (i <= n) : (i += 1) {
        var temp = try Managed.initSet(allocator, i);
        defer temp.deinit();

        var novo = try Managed.init(allocator);
        try novo.mul(&resultado, &temp);

        resultado.deinit();
        resultado = novo;
    }

    return resultado;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const valores = [_]u32{ 10, 20, 50, 100 };

    for (&valores) |n| {
        var fat = try fatorial(allocator, n);
        defer fat.deinit();

        const str = try fat.toString(allocator, 10, .lower);
        defer allocator.free(str);

        std.debug.print("{d}! = {s}\n", .{ n, str });
        std.debug.print("  ({d} dígitos)\n\n", .{str.len});
    }
}

Potência de Big Integer

const std = @import("std");
const Managed = std.math.big.int.Managed;

fn potencia(allocator: std.mem.Allocator, base_val: u64, exp: u32) !Managed {
    var resultado = try Managed.initSet(allocator, 1);
    errdefer resultado.deinit();

    var base = try Managed.initSet(allocator, base_val);
    defer base.deinit();

    var i: u32 = 0;
    while (i < exp) : (i += 1) {
        var novo = try Managed.init(allocator);
        try novo.mul(&resultado, &base);

        resultado.deinit();
        resultado = novo;
    }

    return resultado;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // 2^64 (excede u64)
    var p1 = try potencia(allocator, 2, 64);
    defer p1.deinit();
    const s1 = try p1.toString(allocator, 10, .lower);
    defer allocator.free(s1);
    std.debug.print("2^64 = {s}\n", .{s1});

    // 2^128
    var p2 = try potencia(allocator, 2, 128);
    defer p2.deinit();
    const s2 = try p2.toString(allocator, 10, .lower);
    defer allocator.free(s2);
    std.debug.print("2^128 = {s}\n", .{s2});

    // 10^30
    var p3 = try potencia(allocator, 10, 30);
    defer p3.deinit();
    const s3 = try p3.toString(allocator, 10, .lower);
    defer allocator.free(s3);
    std.debug.print("10^30 = {s}\n", .{s3});
}

Criar Big Integer a partir de String

const std = @import("std");
const Managed = std.math.big.int.Managed;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Criar a partir de string grande
    var num = try Managed.init(allocator);
    defer num.deinit();
    try num.setString(10, "123456789012345678901234567890");

    const str = try num.toString(allocator, 10, .lower);
    defer allocator.free(str);
    std.debug.print("Número: {s}\n", .{str});

    // Hexadecimal
    var hex_num = try Managed.init(allocator);
    defer hex_num.deinit();
    try hex_num.setString(16, "FFFFFFFFFFFFFFFFFFFFFFFF");

    const hex_dec = try hex_num.toString(allocator, 10, .lower);
    defer allocator.free(hex_dec);

    const hex_str = try hex_num.toString(allocator, 16, .upper);
    defer allocator.free(hex_str);

    std.debug.print("Hex: {s}\n", .{hex_str});
    std.debug.print("Dec: {s}\n", .{hex_dec});
}

Comparação de Big Integers

const std = @import("std");
const Managed = std.math.big.int.Managed;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var a = try Managed.init(allocator);
    defer a.deinit();
    try a.setString(10, "99999999999999999999");

    var b = try Managed.init(allocator);
    defer b.deinit();
    try b.setString(10, "100000000000000000000");

    const ordem = a.order(b);
    switch (ordem) {
        .lt => std.debug.print("a < b\n", .{}),
        .eq => std.debug.print("a == b\n", .{}),
        .gt => std.debug.print("a > b\n", .{}),
    }

    // Verificar igualdade
    var c = try Managed.initSet(allocator, 42);
    defer c.deinit();

    var d = try Managed.initSet(allocator, 42);
    defer d.deinit();

    std.debug.print("42 == 42? {}\n", .{c.order(d) == .eq});
}

Dicas e Boas Práticas

  1. Gerencie a memória: Cada Managed aloca memória. Use defer deinit() sempre.

  2. Operações criam novos valores: Use variáveis temporárias e libere-as adequadamente.

  3. ArenaAllocator facilita: Para cálculos complexos com muitos intermediários, use ArenaAllocator.

  4. toString aloca memória: O resultado precisa ser liberado com allocator.free().

  5. Performance: Big integers são mais lentos que tipos nativos. Use u128 ou i128 se couber.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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