# Integer math

I very much enjoy making tabular data easy to read on the terminal. Often, this takes me to strange and wonderful places.

## tabular data and scanning and pretty

When writing tabular data to the terminal, ‘pretty’ is often a shorthand for ‘easy to read’, ‘easy to scan’, and ‘aligned’. Nail the last one and you get most of the other two already.

So if you have a bunch of numbers (I’m talking `int`

s and the like, here), you
want them to line up in a way the user expects, and that is
*usually*
I have no idea how this works in languages where
text doesn’t flow left-to-right, beyond that they exist, and that it’s
complicated and contextual, so I’m assuming left-to-right text and look forward
to learning about other things some day
with numbers aligned to
the right.

```
Year Units Net
2021 7 7
2022 14 -23
2023 654386 5
```

If you’re using Go, and using its standard library’s `text/tabwriter`

, and *all*
you’re printing is numbers, and you don’t mind if your column headers are also
aligned to the right, it has a handy `AlignRight`

formatting option that aligns
everything to the right and then you’re done:

```
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.AlignRight)
w.Write(data)
w.Flush()
```

gives you

```
Year Units Net
2021 7 7
2022 14 -23
2023 654386 5
```

which is quite good.

## how long is that number in the window?

However, sometimes you have other things than numbers mixed in, like a column of
ASCII text
Non-ASCII text will be covered in a future
article.
. Or you are required to align the column headers to the
left. In this case, you need to align the numbers to the right using something
like `fmt.Sprintf("%5d", num)`

, where often you’ll have a natural upper bound
for the width of the numbers as presented on your terminal (e.g. for years
that’ll be 4, or 6 if you’re into history or futurology). But sometimes that
’natural upper bound’ is something very large and unusual like `1<<64`

. So how
much space do you *really* need for *your* numbers? You’ve got to walk all the numbers in
the column, find the extremes, and see how big they are.

How do you see how big they are? Well, you *could* just check the length of
its `fmt.Sprint`

. Easy!

```
func lenFmt[V constraints.Unsigned](n V) V {
return V(len(fmt.Sprint(n)))
}
```

However, if you’re suspicious of `fmt`

vis-à-vis its performance and/or memory
usage (and you probably should be) you might want to tweak it a little, without
changing the ‘algorithm’,

```
func lenStrconv[V constraints.Unsigned](n V) V {
return V(len(strconv.FormatUint(uint64(n), 10)))
}
```

Now, the length of the decimal representation of an integer `u`

is just
`⌊log₁₀u⌋+1`

(with a special case for 0). So you *could* do just that,

```
func lenMath[V constraints.Unsigned](n V) V {
return V(math.Floor(math.Log10(float64(n)))) + 1
}
```

or … you could use integer math to do `⌊log₁₀u⌋`

. Which is what I’ve done in
`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`

is very much still a work in progress, but the idea is for
it to complement `math/bits`

so that together they can do most of the boring
stuff from `math`

, but using algorithms that work faster for integers.