Bibliotecas Matemáticas e Científicas em Zig

Bibliotecas Matemáticas e Científicas em Zig

O Zig é uma linguagem excepcionalmente adequada para computação científica e matemática de alto desempenho. Com suporte nativo a operações SIMD, vetores de tamanho fixo avaliados em comptime e controle preciso de representação numérica, o Zig permite cálculos que rivalizam com Fortran e C em performance, mantendo legibilidade e segurança superiores.

std.math — A Base Matemática

A biblioteca padrão oferece um módulo matemático abrangente:

const std = @import("std");
const math = std.math;

pub fn main() void {
    // Constantes
    const pi = math.pi;
    const e = math.e;
    const inf = math.inf(f64);
    _ = inf;

    // Funções trigonométricas
    const angulo: f64 = pi / 4.0;
    const seno = @sin(angulo);
    const cosseno = @cos(angulo);
    const tangente = @tan(angulo);
    _ = seno;
    _ = cosseno;
    _ = tangente;

    // Exponencial e logaritmo
    const exp_val = @exp(@as(f64, 1.0));
    const ln_val = @log(@as(f64, e));
    const log2_val = math.log2(@as(f64, 8.0)); // 3.0
    const log10_val = math.log10(@as(f64, 1000.0)); // 3.0
    _ = exp_val;
    _ = ln_val;
    _ = log2_val;
    _ = log10_val;

    // Potência e raiz
    const potencia = math.pow(f64, 2.0, 10.0); // 1024
    const raiz = @sqrt(@as(f64, 144.0)); // 12
    const cbrt = math.cbrt(@as(f64, 27.0)); // 3
    _ = potencia;
    _ = raiz;
    _ = cbrt;

    // Arredondamento
    const floor = @floor(@as(f64, 3.7)); // 3
    const ceil = @ceil(@as(f64, 3.2)); // 4
    const round = @round(@as(f64, 3.5)); // 4
    _ = floor;
    _ = ceil;
    _ = round;

    // Overflow seguro
    const a: u32 = 4_000_000_000;
    const b: u32 = 1_000_000_000;
    const resultado = math.add(u32, a, b) catch {
        std.debug.print("Overflow detectado!\n", .{});
        return;
    };
    _ = resultado;
}

Álgebra Linear

zlm — Zig Linear Math

Biblioteca focada em operações com vetores e matrizes para gráficos e física:

const zlm = @import("zlm");

pub fn main() void {
    // Vetores
    const v1 = zlm.Vec3.new(1.0, 2.0, 3.0);
    const v2 = zlm.Vec3.new(4.0, 5.0, 6.0);

    const soma = v1.add(v2);
    const produto_escalar = v1.dot(v2);
    const produto_vetorial = v1.cross(v2);
    const magnitude = v1.length();
    const normalizado = v1.normalize();
    _ = soma;
    _ = produto_escalar;
    _ = produto_vetorial;
    _ = magnitude;
    _ = normalizado;

    // Matrizes 4x4
    const identidade = zlm.Mat4.identity();
    const translacao = zlm.Mat4.translation(zlm.Vec3.new(1, 2, 3));
    const rotacao = zlm.Mat4.rotation(std.math.pi / 4.0, zlm.Vec3.new(0, 1, 0));
    const escala = zlm.Mat4.scaling(zlm.Vec3.new(2, 2, 2));

    // Composição de transformações
    const modelo = identidade.mul(translacao).mul(rotacao).mul(escala);

    // Transformar ponto
    const ponto = zlm.Vec4.new(1, 0, 0, 1);
    const transformado = modelo.mulVec(ponto);
    _ = transformado;
}

Matrizes Genéricas

Implementação de matrizes NxM com operações completas:

fn Matrix(comptime T: type, comptime rows: usize, comptime cols: usize) type {
    return struct {
        data: [rows][cols]T,

        const Self = @This();

        pub fn zeros() Self {
            return .{ .data = std.mem.zeroes([rows][cols]T) };
        }

        pub fn identity() Self {
            comptime {
                if (rows != cols) @compileError("Identidade requer matriz quadrada");
            }
            var m = zeros();
            inline for (0..rows) |i| {
                m.data[i][i] = 1;
            }
            return m;
        }

        pub fn mul(self: Self, comptime other_cols: usize, other: Matrix(T, cols, other_cols)) Matrix(T, rows, other_cols) {
            var result = Matrix(T, rows, other_cols).zeros();
            for (0..rows) |i| {
                for (0..other_cols) |j| {
                    var soma: T = 0;
                    for (0..cols) |k| {
                        soma += self.data[i][k] * other.data[k][j];
                    }
                    result.data[i][j] = soma;
                }
            }
            return result;
        }

        pub fn transpor(self: Self) Matrix(T, cols, rows) {
            var result = Matrix(T, cols, rows).zeros();
            for (0..rows) |i| {
                for (0..cols) |j| {
                    result.data[j][i] = self.data[i][j];
                }
            }
            return result;
        }

        pub fn determinante(self: Self) T {
            comptime {
                if (rows != cols) @compileError("Determinante requer matriz quadrada");
            }
            if (rows == 2) {
                return self.data[0][0] * self.data[1][1] - self.data[0][1] * self.data[1][0];
            }
            // Expansão de Laplace para NxN
            var det: T = 0;
            for (0..cols) |j| {
                const sinal: T = if (j % 2 == 0) 1 else -1;
                det += sinal * self.data[0][j] * self.cofator(0, j);
            }
            return det;
        }
    };
}

SIMD — Computação Vetorial

O Zig tem suporte nativo a operações SIMD:

const std = @import("std");

pub fn somaVetoresSimd(a: []const f32, b: []const f32, resultado: []f32) void {
    const vec_size = 8; // AVX: 8 floats por operação

    var i: usize = 0;
    while (i + vec_size <= a.len) : (i += vec_size) {
        const va: @Vector(vec_size, f32) = a[i..][0..vec_size].*;
        const vb: @Vector(vec_size, f32) = b[i..][0..vec_size].*;
        resultado[i..][0..vec_size].* = va + vb;
    }

    // Processar elementos restantes
    while (i < a.len) : (i += 1) {
        resultado[i] = a[i] + b[i];
    }
}

pub fn produtoEscalarSimd(a: []const f32, b: []const f32) f32 {
    const vec_size = 8;
    var soma_vec: @Vector(vec_size, f32) = @splat(0);

    var i: usize = 0;
    while (i + vec_size <= a.len) : (i += vec_size) {
        const va: @Vector(vec_size, f32) = a[i..][0..vec_size].*;
        const vb: @Vector(vec_size, f32) = b[i..][0..vec_size].*;
        soma_vec += va * vb;
    }

    var soma: f32 = @reduce(.Add, soma_vec);
    while (i < a.len) : (i += 1) {
        soma += a[i] * b[i];
    }

    return soma;
}

Estatística

fn Estatistica(comptime T: type) type {
    return struct {
        pub fn media(dados: []const T) T {
            var soma: T = 0;
            for (dados) |v| soma += v;
            return soma / @as(T, @floatFromInt(dados.len));
        }

        pub fn variancia(dados: []const T) T {
            const m = media(dados);
            var soma_quadrados: T = 0;
            for (dados) |v| {
                const diff = v - m;
                soma_quadrados += diff * diff;
            }
            return soma_quadrados / @as(T, @floatFromInt(dados.len));
        }

        pub fn desvioPadrao(dados: []const T) T {
            return @sqrt(variancia(dados));
        }

        pub fn mediana(dados: []T) T {
            std.sort.pdq(T, dados, {}, std.sort.asc(T));
            const n = dados.len;
            if (n % 2 == 0) {
                return (dados[n / 2 - 1] + dados[n / 2]) / 2.0;
            }
            return dados[n / 2];
        }
    };
}

Comptime para Constantes Matemáticas

Uma das vantagens únicas do Zig é o cálculo em tempo de compilação:

// Tabela de senos pré-calculada em comptime
const TABELA_SENOS = blk: {
    var tabela: [360]f32 = undefined;
    for (0..360) |i| {
        const angulo = @as(f32, @floatFromInt(i)) * std.math.pi / 180.0;
        tabela[i] = @sin(angulo);
    }
    break :blk tabela;
};

// Números primos em comptime
fn ehPrimo(n: u64) bool {
    if (n < 2) return false;
    if (n == 2) return true;
    if (n % 2 == 0) return false;
    var i: u64 = 3;
    while (i * i <= n) : (i += 2) {
        if (n % i == 0) return false;
    }
    return true;
}

const PRIMEIROS_PRIMOS = blk: {
    var primos: [100]u64 = undefined;
    var count: usize = 0;
    var n: u64 = 2;
    while (count < 100) : (n += 1) {
        if (ehPrimo(n)) {
            primos[count] = n;
            count += 1;
        }
    }
    break :blk primos;
};

Boas Práticas

  1. Use SIMD quando possível: Ganhos de 4-8x em operações vetoriais
  2. Pré-calcule em comptime: Tabelas de lookup eliminam cálculos em runtime
  3. Escolha precisão adequada: f32 para gráficos, f64 para ciência
  4. Monitore overflow: Use math.add e math.mul para operações seguras
  5. Profile operações numéricas: Use as ferramentas de profiling

Próximos Passos

Explore as bibliotecas gráficas que usam álgebra linear extensivamente, o Mach Engine para simulações, e as bibliotecas de áudio para DSP. Consulte nossos tutoriais para projetos de computação científica.

Continue aprendendo Zig

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