Como Lidar com Overflow Aritmético em Zig

Introdução

Overflow aritmético ocorre quando o resultado de uma operação excede o valor máximo (ou mínimo) que um tipo numérico pode representar. Em muitas linguagens, isso causa bugs silenciosos. Zig trata overflow como um erro detectável, oferecendo operadores específicos para diferentes comportamentos: erro em debug, wrapping, saturação e checked.

Nesta receita, você aprenderá a usar cada um desses mecanismos.

Pré-requisitos

Comportamento Padrão: Erro em Debug

Em modo debug, operações que causam overflow são detectadas e geram um panic:

const std = @import("std");

pub fn main() !void {
    const a: u8 = 250;
    const b: u8 = 10;

    // Em debug, isso causa panic por overflow
    // const resultado = a + b; // PANIC: integer overflow

    // Forma segura: verificar antes
    if (@as(u16, a) + @as(u16, b) > std.math.maxInt(u8)) {
        std.debug.print("Overflow detectado: {d} + {d} > 255\n", .{ a, b });
    } else {
        std.debug.print("OK: {d} + {d} = {d}\n", .{ a, b, a + b });
    }

    // Sem overflow
    const c: u8 = 100;
    const d: u8 = 50;
    std.debug.print("OK: {d} + {d} = {d}\n", .{ c, d, c + d });
}

Operadores de Wrapping (+%, -%, *%)

Wrapping “dá a volta” quando atinge o limite, como em C:

const std = @import("std");

pub fn main() !void {
    // Wrapping addition: dá a volta no overflow
    const a: u8 = 250;
    const b: u8 = 10;
    const wrap_result = a +% b; // 250 + 10 = 260, wraps to 4
    std.debug.print("250 +%% 10 = {d} (wrapping)\n", .{wrap_result});

    // Wrapping subtraction
    const c: u8 = 5;
    const d: u8 = 10;
    const wrap_sub = c -% d; // 5 - 10 = -5, wraps to 251
    std.debug.print("5 -%% 10 = {d} (wrapping)\n", .{wrap_sub});

    // Wrapping multiplication
    const e: u8 = 200;
    const f: u8 = 2;
    const wrap_mul = e *% f; // 400, wraps to 144
    std.debug.print("200 *%% 2 = {d} (wrapping)\n", .{wrap_mul});

    // Wrapping negation
    const g: i8 = -128;
    const wrap_neg = -%g; // -(-128) = 128 overflows i8, wraps to -128
    std.debug.print("-%%(-128) = {d} (wrapping)\n", .{wrap_neg});
}

Operadores de Saturação (+|, -|, *|)

Saturação “gruda” no valor máximo ou mínimo:

const std = @import("std");

pub fn main() !void {
    // Saturating addition
    const a: u8 = 250;
    const b: u8 = 10;
    const sat_result = a +| b; // min(260, 255) = 255
    std.debug.print("250 +| 10 = {d} (saturação)\n", .{sat_result});

    // Saturating subtraction
    const c: u8 = 5;
    const d: u8 = 10;
    const sat_sub = c -| d; // max(5-10, 0) = 0
    std.debug.print("5 -| 10 = {d} (saturação)\n", .{sat_sub});

    // Saturating multiplication
    const e: u8 = 200;
    const f: u8 = 2;
    const sat_mul = e *| f; // min(400, 255) = 255
    std.debug.print("200 *| 2 = {d} (saturação)\n", .{sat_mul});

    // Útil para processamento de imagem (pixel values)
    std.debug.print("\nAjuste de brilho de pixels:\n", .{});
    const pixels = [_]u8{ 100, 150, 200, 230, 255 };
    const ajuste: u8 = 80;

    for (pixels) |p| {
        const novo = p +| ajuste;
        std.debug.print("  {d:>3} + {d} = {d:>3}\n", .{ p, ajuste, novo });
    }
}

Saída esperada

250 +| 10 = 255 (saturação)
5 -| 10 = 0 (saturação)
200 *| 2 = 255 (saturação)

Ajuste de brilho de pixels:
  100 + 80 = 180
  150 + 80 = 230
  200 + 80 = 255
  230 + 80 = 255
  255 + 80 = 255

Checked Arithmetic com @addWithOverflow

Detectar overflow e obter o resultado e um flag de overflow:

const std = @import("std");

pub fn main() !void {
    // @addWithOverflow retorna .{resultado, overflow_bit}
    const a: u8 = 200;
    const b: u8 = 100;

    const result1 = @addWithOverflow(a, b);
    if (result1[1] != 0) {
        std.debug.print("{d} + {d}: OVERFLOW (wrap={d})\n", .{ a, b, result1[0] });
    } else {
        std.debug.print("{d} + {d} = {d}\n", .{ a, b, result1[0] });
    }

    // Sem overflow
    const c: u8 = 100;
    const d: u8 = 50;
    const result2 = @addWithOverflow(c, d);
    if (result2[1] != 0) {
        std.debug.print("{d} + {d}: OVERFLOW\n", .{ c, d });
    } else {
        std.debug.print("{d} + {d} = {d}\n", .{ c, d, result2[0] });
    }

    // @mulWithOverflow
    const e: u8 = 20;
    const f: u8 = 15;
    const result3 = @mulWithOverflow(e, f);
    if (result3[1] != 0) {
        std.debug.print("{d} * {d}: OVERFLOW\n", .{ e, f });
    } else {
        std.debug.print("{d} * {d} = {d}\n", .{ e, f, result3[0] });
    }
}

Operações Seguras com std.math

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

pub fn main() !void {
    // add com erro de overflow
    const a: u8 = 200;
    const b: u8 = 100;

    if (math.add(u8, a, b)) |resultado| {
        std.debug.print("{d} + {d} = {d}\n", .{ a, b, resultado });
    } else |_| {
        std.debug.print("{d} + {d}: overflow!\n", .{ a, b });
    }

    // mul com erro de overflow
    if (math.mul(u8, 20, 15)) |resultado| {
        std.debug.print("20 * 15 = {d}\n", .{resultado});
    } else |_| {
        std.debug.print("20 * 15: overflow!\n", .{});
    }

    // sub com erro de overflow
    if (math.sub(u8, 5, 10)) |resultado| {
        std.debug.print("5 - 10 = {d}\n", .{resultado});
    } else |_| {
        std.debug.print("5 - 10: overflow (underflow para unsigned)!\n", .{});
    }

    // cast seguro entre tipos
    const grande: u32 = 300;
    if (math.cast(u8, grande)) |valor| {
        std.debug.print("Cast {d} para u8: {d}\n", .{ grande, valor });
    } else {
        std.debug.print("{d} não cabe em u8\n", .{grande});
    }
}

Dicas e Boas Práticas

  1. *Operadores padrão (+, -, ) detectam overflow em debug: É seguro por padrão. Use release mode apenas quando necessário e testado.

  2. Use +| para processamento de sinais: Saturação é ideal para áudio, imagens e sensores.

  3. Use +% para contadores circulares: Perfeito para timestamps que dão a volta e ring buffers.

  4. Use std.math.add/mul para erros recuperáveis: Quando o overflow é esperado e deve ser tratado.

  5. Widen primeiro: Promova para tipo maior antes da operação (@as(u16, a) + @as(u16, b)).

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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