# Formatting big integers as hexadecimal digits separated by colons

I'm pretty sure this can be written as a couple of lines, but I'm unable to come up with a better solution.

package main

import (
"fmt"
"math/big"
)

func colonedSerial(i *big.Int) string {
hex := fmt.Sprintf("%x", i)
if len(hex)%2 == 1 {
hex = "0" + hex
}
numberOfColons := len(hex)/2 - 1
colonedHex := make([]byte, len(hex)+numberOfColons)
for i, j := 0, 0; i < len(hex)-1; i, j = i+1, j+1 {
colonedHex[j] = hex[i]
if i%2 == 1 {
j++
colonedHex[j] = []byte(":")[0]
}
}
// we skipped the last one to avoid the colon at the end
colonedHex[len(colonedHex)-1] = hex[len(hex)-1]
return string(colonedHex)
}

func main() {
fmt.Println(colonedSerial(big.NewInt(1234)))
// "04:d2"
fmt.Println(colonedSerial(big.NewInt(123456)))
// "01:e2:40"
fmt.Println(colonedSerial(big.NewInt(1234567891011121314)))
// "11:22:10:f4:b2:d2:30:a2"
}


Also the []byte(":")[0] is very ugly. Is there a better way to do that? (I did not wanted to write the ASCII value of colon (58) as it would be a magic number).

Go Playground version: https://play.golang.org/p/GTxajcr3NY

In Go, we usually expect reasonable performance and brevity. For example,

package main

import (
"encoding/hex"
"fmt"
"math/big"
)

func formatSerial(serial *big.Int) string {
b := serial.Bytes()
buf := make([]byte, 0, 3*len(b))
x := buf[1*len(b) : 3*len(b)]
hex.Encode(x, b)
for i := 0; i < len(x); i += 2 {
buf = append(buf, x[i], x[i+1], ':')
}
return string(buf[:len(buf)-1])
}

func main() {
fmt.Println(formatSerial(big.NewInt(1234)))
// "04:d2"
fmt.Println(formatSerial(big.NewInt(123456)))
// "01:e2:40"
fmt.Println(formatSerial(big.NewInt(1234567891011121314)))
// "11:22:10:f4:b2:d2:30:a2"
}


Output:

04:d2
01:e2:40
11:22:10:f4:b2:d2:30:a2


Benchmark: serial := big.NewInt(1234567891011121314):

BenchmarkPeterSO         3000000       552 ns/op        72 B/op      3 allocs/op
BenchmarkKissgyorgy       500000      2545 ns/op       120 B/op      7 allocs/op
Benchmark200Success        30000     40675 ns/op     40675 B/op     35 allocs/op

• oh wow. I didn't wanted to use append to avoid multiple allocations, but the cap trick is nice! but how is yours so much faster than mine? what are those allocations in my version? I explicitly wanted to avoid those, that's whhy I made a byte slice and not string concatenation – kissgyorgy Jun 14 '17 at 6:22
• @kissgyorgy: When you write fmt.Sprintf("%x", i), it interprets the format string and, by reflection, inspects the format variables at run time. Read the Go code for src/fmt/format.go and src/fmt/print.go. For example, try replacing fmt.Sprintf("%x", i) with i.Text(16). My code is compile-time code that does close to the minimum. – peterSO Jun 14 '17 at 18:14
• Ok I just understood your version now. You use only one array, store the hex elements from the middle and store the final from the start reusing the full array at the end. I like it very much, thanks! – kissgyorgy Jun 14 '17 at 21:54

Since the task mainly involves inserting a colon after every two hex digits, I would treat it as a search-and-replace operation, using a regular expression.

import (
"fmt"
"math/big"
"regexp"
"strings"
)

func colonedSerial(i *big.Int) string {
re := regexp.MustCompile("..")

hex := fmt.Sprintf("%x", i)
if len(hex)%2 == 1 {
hex = "0" + hex
}
return strings.TrimRight(re.ReplaceAllString(hex, "\$0:"), ":")
}

• This is actually really nice! – kissgyorgy Jun 14 '17 at 6:27