El problema eterno: «0» a la izquierda o «desplazamiento a la derecha»?
\n\n¿Alguna vez se ha preguntado por qué en las consultas SQL los números de pedido se ven como 00042 y en los registros como 23:05:07? Es simple: los números rellenados con ceros se alinean por ancho y no rompen la ordenación. Pero en Go, a diferencia de Python o JavaScript, no hay un método incorporado zfill. Y aquí es donde comienza lo interesante.
En Stack Overflow, la pregunta «How to pad a number with zeros when printing?» ha acumulado miles de visitas y decenas de respuestas. La gente busca no solo una solución, sino una solución rápida. En este artículo, analizaremos todas las formas de rellenar un número con ceros en Go, compararemos su rendimiento y elegiremos la mejor para su proyecto.
\n\n¿Por qué es importante para un desarrollador de Go?
\n\nGo es un lenguaje para programación de sistemas, donde cada nanosegundo cuenta. Si está escribiendo un servicio de alta carga, un formateo incorrecto de cadenas puede convertirse en un cuello de botella. Especialmente si formatea miles de números por segundo.
\n\n- \n
- Registro (logging): marcas de tiempo, ID de solicitudes, códigos de error. \n
- Generación de informes: CSV, JSON, tablas. \n
- Protocolos de red: longitud fija de campos. \n
- Juegos y gráficos: alineación de texto en la consola. \n
Ignorar el rendimiento puede llevar a que su servicio gaste el 10% del tiempo formateando números. ¿No lo cree? Vamos a comprobarlo.
\n\nMétodo n.º 1: El clásico mediante fmt.Sprintf
\n\nLa forma más obvia y a menudo recomendada es usar fmt.Sprintf con el especificador de formato %0Xd, donde X es el ancho total del número incluidos los ceros.
package main\n\nimport "fmt"\n\nfunc main() {\n num := 42\n padded := fmt.Sprintf("%05d", num)\n fmt.Println(padded) // Salida: 00042\n}\n\n\nAquí, %05d significa: «muestra el número como entero decimal, rellena a la izquierda con ceros hasta un ancho de 5 caracteres». Si el número ya es más largo (por ejemplo, 123456), no se añaden ceros; el ancho se ignora.
Ventajas:
\n- \n
- Simple y legible. \n
- Soporta números negativos (el signo estará a la izquierda de los ceros). \n
- Funciona con diferentes tipos:
%05dpara int,%05.2fpara float. \n
Desventajas:
\n- \n
- Usa reflexión internamente en
fmt— es lento. \n - Crea una cadena y un interface{} adicionales. \n
- No es adecuado para rutas críticas (hot paths). \n
Método n.º 2: strconv.FormatInt rápido con relleno manual
\n\nSi el rendimiento es crítico, use el paquete strconv. Trabaja directamente con bytes, sin reflexión.
package main\n\nimport (\n "fmt"\n "strconv"\n "strings"\n)\n\nfunc padNumber(num int64, width int) string {\n s := strconv.FormatInt(num, 10)\n if len(s) >= width {\n return s\n }\n return strings.Repeat("0", width-len(s)) + s\n}\n\nfunc main() {\n fmt.Println(padNumber(42, 5)) // 00042\n}\n\n\nVentajas:
\n- \n
- 2-3 veces más rápido que
fmt.Sprintf. \n - Sin reflexión. \n
- Control total sobre la lógica. \n
Desventajas:
\n- \n
- Más código. \n
- Requiere manejar números negativos por separado (signo a la izquierda de los ceros). \n
strings.Repeatcrea una nueva cadena — se puede optimizar más. \n
Método n.º 3: Rendimiento máximo mediante strings.Builder
\n\nPara aquellos que quieren exprimir al máximo el rendimiento, existe un método sin asignaciones adicionales: usar strings.Builder y preasignar el búfer.
package main\n\nimport (\n "fmt"\n "strconv"\n "strings"\n)\n\nfunc padNumberFast(num int64, width int) string {\n s := strconv.FormatInt(num, 10)\n if len(s)