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
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
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
*Operadores padrão (+, -, ) detectam overflow em debug: É seguro por padrão. Use release mode apenas quando necessário e testado.
Use
+|para processamento de sinais: Saturação é ideal para áudio, imagens e sensores.Use
+%para contadores circulares: Perfeito para timestamps que dão a volta e ring buffers.Use
std.math.add/mulpara erros recuperáveis: Quando o overflow é esperado e deve ser tratado.Widen primeiro: Promova para tipo maior antes da operação (
@as(u16, a) + @as(u16, b)).
Receitas Relacionadas
- Operações Matemáticas - Funções matemáticas
- Trabalhando com Big Integers - Sem limites de tamanho
- Converter entre Bases Numéricas - Bases numéricas
- Try/Catch e Tratamento de Erros - Tratar erros