Matemática entera
Disfruto mucho haciendo que datos tabulados sean fáciles de leer en la terminal. A menudo esto me lleva a lugares extraños y maravillosos.
datos tabulados y escanear y bonito
Cuando uno escribe datos tabulados a una terminal, ‘bonito’ es muy a menudo usado para significar ‘fácil de leer’, ‘fácil de escanear’ eso que uno hace cuando está buscando un elemento de texto en una columna, distinto de leer. En inglés es to scan que es la misma palabra que escanear y escandir. No sé si se le dice escandir a este proceso. , y ‘alineado’. Si hacés bien este último, los otros dos salen casi solos.
Así que si tenés un montón de números (acá estoy hablando de int
s y cosas
así), querés alinearlos de la manera que el usuario espera, y eso es casi
siempre
No tengo idea de cómo funciona esto en lenguas donde el
texto no fluye de izquierda a derecha, más allá de que existen, y de que es
complicado y contextual, así que voy a suponer texto de izquierda a derecha y me
encantaría aprender de otras opciones algún día
con los números
alineados a la derecha.
Year Units Net
2021 7 7
2022 14 -23
2023 654386 5
Si estás usando Go, y usando text/tabwriter
de su librería estándard, y todo
lo que estás imprimiendo son números, y no te importa que los encabezados
también se alineen a la derecha, es muy cómodo la opción AlignRight
que manda
todo para la derecha y ya está:
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.AlignRight)
w.Write(data)
w.Flush()
te da
Year Units Net
2021 7 7
2022 14 -23
2023 654386 5
que está bastante bien.
¿qué ancho tiene ese número?
Sin embargo, a veces tenés otras cosas mezcladas con los números, como una
columna de texto ASCII
Texto que es otra cosa que ASCII queda para
otro artículo.
. O quizás tengas el requermiento de alinear los
encabezados de las columnas a la izquierda. En estos casos, necesitás alinear
los números a la derecha usando algo como fmt.Sprintf("%5d", num)
, donde a
menudo hay una cota superior natural para el ancho de los números a presentar en
la terminal (e.g. para años sería 4, o 6 si estás haciendo historia o
futurulogía). Pero a veces esa ‘cota superior natural’ es algo muy grande e
inusual como 1<<64
. ¿Cuánto espacio realmente necesitás para tus números?
Tenés que caminar todos los números en la columna, encontrar los extemos, y ver
qué tamaño tienen.
¿Cómo ves qué tamaño tienen? Bueno, podrías simplemente mirar el largo de su
fmt.Sprint
. Fácil!
func lenFmt[V constraints.Unsigned](n V) V {
return V(len(fmt.Sprint(n)))
}
Pero si sospechás del comportamiento de fmt
con respecto a su performance y/o
uso de memoria (y probablemente deberías), quizás quieras toquetear esto un
poco, aún sin cambiar el ‘algoritmo’:
func lenStrconv[V constraints.Unsigned](n V) V {
return V(len(strconv.FormatUint(uint64(n), 10)))
}
Ahora, el largo de la representación decimal de un entero u
es simplemente
⌊log₁₀u⌋+1
(con un caso especial para el 0). Así que podrías hacer directamente eso,
func lenMath[V constraints.Unsigned](n V) V {
return V(math.Floor(math.Log10(float64(n)))) + 1
}
o… podrías usar matemática entera para calcular ⌊log₁₀u⌋
. Que es lo que he hecho en
intmath.Len
.
$ go test -short -bench Len -benchmem
goos: linux
goarch: amd64
pkg: chipaca.com/intmath
cpu: 12th Gen Intel(R) Core(TM) i5-12600H
BenchmarkLenFmt-12 21367644 49.89 ns/op 16 B/op 1 allocs/op
BenchmarkLenStrconv-12 76015239 15.01 ns/op 7 B/op 0 allocs/op
BenchmarkLenMath-12 179116516 6.703 ns/op 0 B/op 0 allocs/op
BenchmarkLenInt-12 1000000000 0.3558 ns/op 0 B/op 0 allocs/op
PASS
ok chipaca.com/intmath 5.826s
chipaca.com/intmath
está todavía
bastante verde, pero la idea es que sea un complemento para math/bits
de la
librería estándar, para que entre los dos puedas hacer la mayoría de las cosas
más obvias de math
usando algoritmos que sean más rápidos para enteros.