Kugutsumen
Kugutsumen

Reputation: 936

Converting int32 to byte array in go

I am using big.NewInt(int64(e)).Bytes() to convert an int32 to a byte array. Is there a more elegant way to do this?

I expect AQAB to be the base64 encoded value of e

http://play.golang.org/p/M46X7OpZpu

const e = 65537

func base64Encode(b []byte) string {
  return strings.TrimRight(base64.StdEncoding.EncodeToString(b), "=")
}

func main() {
  fmt.Printf("exp %d\n", e)

  b := make([]byte, 4)
  binary.BigEndian.PutUint32(b, e)
  fmt.Printf("b: BigEndian.PutUint32 %x (Bad) %s\n", b, base64Encode(b))

  b2 := make([]byte, 4)
  binary.BigEndian.PutUint32(b2, e)
  for i := range b2 {
    if b2[i] != 0 {
    b2 = b2[i:]
    break
     }
  }
  fmt.Printf("b2: BigEndian.PutUint32 %x (Good) %s\n", b2, base64Encode(b2))

  b4 := big.NewInt(int64(e)).Bytes()
  fmt.Printf("b4: big.NewInt(int64(e)).Bytes() %x (Good) %s\n", b4, base64Encode(b4))
}

Output:

exp 65537
b: BigEndian.PutUint32 00010001 (Bad) AAEAAQ
b2: BigEndian.PutUint32 010001 (Good) AQAB
b4: big.NewInt(int64(e)).Bytes() 010001 (Good) AQAB

exp 1
b: BigEndian.PutUint32 00000001 (Bad) AAAAAQ
b2: BigEndian.PutUint32 01 (Good) AQ
b4: big.NewInt(int64(e)).Bytes() 01 (Good) AQ

exp 1000000
b: BigEndian.PutUint32 000f4240 (Bad) AA9CQA
b2: BigEndian.PutUint32 0f4240 (Good) D0JA
b4: big.NewInt(int64(e)).Bytes() 0f4240 (Good) D0JA

Edit:

I've benchmarked b2 and b4:

b2  1000000000          68.1 ns/op         8 B/op          1 allocs/op
b4  200000000          248 ns/op          90 B/op          3 allocs/op

I'll use b2 for now...

Upvotes: 13

Views: 10184

Answers (2)

Tchakabam
Tchakabam

Reputation: 505

This is the most straight forward (and shortest (and safest) (and maybe most performant)) way:

buf.Bytes() is of type bytes slice.

    var val uint32 = 42
    buf := new(bytes.Buffer)
    err := binary.Write(buf, binary.LittleEndian, val)
    if err != nil {
        fmt.Println("binary.Write failed:", err)
    }
    fmt.Printf("% x\n", buf.Bytes())

Upvotes: -1

voidlogic
voidlogic

Reputation: 6826

For this kind of task I think your first options should always be using encoding/binary and, if that is insufficient, bitwise math. However, in some cases the overhead of copying data is too large or these safe solutions are too slow:

While I would not call it elegant you can use Go's unsafe and reflect* packages to do this very quickly. Just remember, this does not copy the data; rather, it just gives you another "view" of it. And being well- unsafe means that you need to be very careful- (hello units tests and code review) and keep in mind you are breaking Go's memory safety. However, when execution speed is the dominating concern and your team agrees unsafe is warranted, unsafe can seldom be beat.

const BYTES_IN_INT32 = 4

func UnsafeCaseInt32ToBytes(val int32) []byte {
    hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&val)), Len: BYTES_IN_INT32, Cap: BYTES_IN_INT32}
    return *(*[]byte)(unsafe.Pointer(&hdr))
}

func UnsafeCastInt32sToBytes(ints []int32) []byte {        
    length := len(ints) * BYTES_IN_INT32
    hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&ints[0])), Len: length, Cap: length}
    return *(*[]byte)(unsafe.Pointer(&hdr))
}

*Note: You may want to use SizeOf rather than a constant. I like the constant better.

Update: here are some benchmark results:

BenchmarkB2     20000000     88.7  ns/op
BenchmarkB4     5000000     309    ns/op
BenchmarkUnsafe 1000000000    2.25 ns/op

Upvotes: 6

Related Questions