@intFromFloat em Zig
O @intFromFloat converte um valor de ponto flutuante para um tipo inteiro, truncando a parte fracionária. Diferente de conversões implícitas em C que podem gerar comportamento indefinido, o Zig torna essa conversão explícita e definida, garantindo que o programador esteja ciente da perda de precisão.
Sintaxe
@intFromFloat(valor: anytype) IntType
O tipo de retorno é inferido pelo contexto onde o resultado é usado.
O que faz
O @intFromFloat converte um valor de ponto flutuante (f16, f32, f64, f80, f128) para um tipo inteiro. A parte fracionária é truncada (arredondada em direção a zero). Se o valor estiver fora do intervalo representável pelo tipo inteiro de destino, o resultado é um comportamento de segurança ilegal (safety-checked illegal behavior) — o que em modo debug causa um panic.
Parâmetros
- valor (
anytype): Um valor de ponto flutuante a ser convertido. Deve ser um tipo float válido (f16,f32,f64,f80,f128oucomptime_float).
Valor de retorno
Retorna o valor truncado como o tipo inteiro de destino, inferido pelo contexto. A parte fracionária é descartada.
Exemplos práticos
Exemplo 1: Conversões básicas
const std = @import("std");
test "conversão float para inteiro" {
const a: f64 = 42.7;
const b: f64 = -15.9;
const c: f64 = 0.99;
const x: i32 = @intFromFloat(a);
const y: i32 = @intFromFloat(b);
const z: i32 = @intFromFloat(c);
// Truncamento: parte fracionária é descartada
try std.testing.expect(x == 42); // 42.7 -> 42
try std.testing.expect(y == -15); // -15.9 -> -15 (trunca em direção a zero)
try std.testing.expect(z == 0); // 0.99 -> 0
}
Exemplo 2: Conversão em cálculos gráficos
const std = @import("std");
const Ponto = struct {
x: i32,
y: i32,
};
fn pontoNaTela(x_mundo: f64, y_mundo: f64, escala: f64) Ponto {
return .{
.x = @intFromFloat(x_mundo * escala),
.y = @intFromFloat(y_mundo * escala),
};
}
test "conversão para coordenadas de pixel" {
const ponto = pontoNaTela(3.7, 2.1, 100.0);
try std.testing.expect(ponto.x == 370);
try std.testing.expect(ponto.y == 210);
}
Exemplo 3: Arredondamento antes da conversão
const std = @import("std");
fn arredondar(valor: f64) i64 {
// @intFromFloat trunca, então somamos 0.5 para arredondar
if (valor >= 0) {
return @intFromFloat(valor + 0.5);
} else {
return @intFromFloat(valor - 0.5);
}
}
fn teto(valor: f64) i64 {
const truncado: i64 = @intFromFloat(valor);
const como_float: f64 = @floatFromInt(truncado);
if (como_float < valor) return truncado + 1;
return truncado;
}
test "arredondamento personalizado" {
try std.testing.expect(arredondar(3.7) == 4);
try std.testing.expect(arredondar(3.2) == 3);
try std.testing.expect(arredondar(-2.6) == -3);
try std.testing.expect(teto(3.1) == 4);
try std.testing.expect(teto(3.0) == 3);
try std.testing.expect(teto(-2.3) == -2);
}
Casos de uso comuns
- Renderização gráfica: Converter coordenadas de mundo (float) para coordenadas de pixel (inteiro).
- Processamento de áudio: Converter amostras de ponto flutuante (normalizadas entre -1.0 e 1.0) para valores inteiros de PCM.
- Física e simulação: Converter resultados de cálculos físicos para índices de grade ou posições discretas.
- Formatação de números: Extrair a parte inteira de um número para exibição.
- Quantização: Converter valores contínuos para valores discretos em processamento de sinais.
Cuidados importantes
- Se o valor float for
NaN,+Infou-Inf, a conversão causa panic em modo debug. - Se o valor truncado estiver fora do intervalo do tipo inteiro de destino, também causa panic em modo debug.
- Em modo
ReleaseFast, esses casos resultam em comportamento indefinido, assim como em C.
Comportamento de truncamento vs. arredondamento
O @intFromFloat sempre trunca em direção a zero — não arredonda para o inteiro mais próximo. Esse comportamento é consistente e previsível:
// Todos truncam em direção a zero:
@intFromFloat(3.9) // → 3 (trunca para baixo)
@intFromFloat(3.1) // → 3 (trunca para baixo)
@intFromFloat(-3.9) // → -3 (trunca em direção a zero, não para -4)
@intFromFloat(-3.1) // → -3 (trunca em direção a zero)
Para outros comportamentos de arredondamento, use as funções da biblioteca padrão antes de converter:
const std = @import("std");
const valor: f64 = 3.7;
const arredondado: i64 = @intFromFloat(@round(valor)); // 4 — arredonda para o mais próximo
const piso: i64 = @intFromFloat(@floor(valor)); // 3 — arredonda para baixo
const teto: i64 = @intFromFloat(@ceil(valor)); // 4 — arredonda para cima
Comparação com C equivalente
Em C, a conversão de float para int é feita com cast e pode causar comportamento indefinido silencioso:
double valor = 1e18;
int resultado = (int)valor; // Comportamento indefinido! overflow silencioso
Em Zig, o overflow é detectado em modo debug:
const valor: f64 = 1e18;
const resultado: i32 = @intFromFloat(valor); // panic em debug: float out of range
Além disso, em C não há verificação de NaN ou Inf, enquanto Zig detecta esses casos automaticamente em builds com safety checks.
Estratégia de validação antes da conversão
Para código robusto que lida com dados de entrada externos, valide o float antes de converter:
const std = @import("std");
fn floatParaIntSafe(valor: f64, comptime T: type) !T {
if (std.math.isNan(valor)) return error.NaN;
if (std.math.isInf(valor)) return error.Infinito;
const min: f64 = @floatFromInt(std.math.minInt(T));
const max: f64 = @floatFromInt(std.math.maxInt(T));
if (valor < min or valor > max) return error.ForaDoIntervalo;
return @intFromFloat(valor);
}
test "conversão segura" {
try std.testing.expect(try floatParaIntSafe(42.7, i32) == 42);
try std.testing.expectError(error.ForaDoIntervalo, floatParaIntSafe(1e20, i32));
try std.testing.expectError(error.NaN, floatParaIntSafe(std.math.nan(f64), i32));
}
Perguntas Frequentes
P: Como diferenciar @intFromFloat de std.math.lossyCast?
@intFromFloat trunca e causa panic se o valor estiver fora do intervalo. std.math.lossyCast faz o melhor possível — clampea ao min/max do tipo de destino e retorna zero para NaN — sem panic. Use lossyCast quando um resultado aproximado é aceitável.
P: É seguro usar @intFromFloat em modo ReleaseFast?
Não, se os valores de entrada podem ser inválidos. Em ReleaseFast, safety checks são desabilitados e overflow ou NaN resultam em comportamento indefinido. Valide os dados antes ou use ReleaseSafe para manter as verificações mesmo em builds de produção.
P: Por que @intFromFloat trunca em vez de arredondar?
Truncamento é a operação mais próxima do que a maioria das CPUs faz nativamente (instrução cvttss2si em x86, onde o duplo t significa “truncate”). Arredondamento exigiria uma operação extra. Quando arredondamento é necessário, use @round, @floor ou @ceil da biblioteca padrão antes de converter.
Builtins relacionados
- @floatFromInt — Operação inversa: converte inteiro para float
- @as — Conversão de tipo genérica
- @intFromEnum — Converte enum para inteiro
- @intFromBool — Converte booleano para inteiro