El Problema
Dado un entero con signo de 32 bits, devolver el número con sus dígitos invertidos. Si el resultado se desborda fuera del rango de un entero con signo de 32 bits ([-2^31, 2^31 - 1]), devolver 0.
A primera vista parece un ejercicio trivial de manipulación numérica. Extraer dígitos con módulo, reconstruir el número multiplicando por 10… algo que cualquiera puede hacer en un par de minutos. Pero la trampa está en lo que no se ve: el desbordamiento (overflow).
La Trampa del Overflow
El rango de un i32 va de -2,147,483,648 a 2,147,483,647. Invertir un número como 1,999,999,999 produce 9,999,999,991, que excede el máximo. Si no controlamos esto, el programa colapsa o devuelve basura.
La pregunta clave es: ¿cómo detectar el overflow antes de que ocurra?
El Enfoque Clásico (C/C++)
En C, la estrategia típica es comparar manualmente contra INT_MAX / 10 antes de multiplicar. Es funcional pero verboso y propenso a errores de signo.
El Enfoque Idiomático (Rust)
Rust, por diseño, nos obliga a pensar en el overflow. En modo debug, las operaciones aritméticas que desbordan provocan un panic. En modo release, hacen wrapping silencioso. Ninguna de las dos opciones es lo que queremos.
La solución elegante: usar los métodos checked_mul y checked_add que devuelven Option<i32>. Si la operación es segura, obtenemos Some(valor). Si desborda, obtenemos None. Encadenarlos con and_then nos da una pipeline limpia y segura.
Lo que en C requiere comparaciones manuales contra límites, en Rust se convierte en una sola expresión declarativa dentro de un match.
La Solución
El algoritmo es directo:
- Extraer el último dígito con
num % 10. - Reducir el número con
num /= 10. - Acumular en
revmultiplicando por 10 y sumando el dígito, pero protegiendo cada operación contra overflow. - Si en cualquier paso la multiplicación o la suma desborda, devolvemos
0inmediatamente.
Un detalle sutil: en Rust, el operador % preserva el signo del dividendo. Si num es negativo, digit también lo será. Esto significa que no necesitamos manejar el signo por separado: checked_add con un dígito negativo efectivamente resta, y la detección de overflow funciona correctamente para ambos extremos del rango.
impl Solution {
pub fn reverse(x: i32) -> i32 {
let mut num = x;
let mut rev = 0i32;
while num != 0 {
let digit = num % 10;
num /= 10;
match rev.checked_mul(10).and_then(|v| v.checked_add(digit)) {
Some(val) => rev = val,
None => return 0,
}
}
rev
}
}
Conclusión
Este problema es un recordatorio de que la aritmética de enteros no es tan inocente como parece. La diferencia entre un programa correcto y uno con comportamiento indefinido puede ser una sola multiplicación sin guardia.
Rust convierte lo que en otros lenguajes es disciplina del programador en una garantía del sistema de tipos. Los métodos checked_* no son un lujo: son la forma correcta de trabajar con aritmética que puede desbordar.