I am able to convert []byte
into unsigned integers:
a := binary.LittleEndian.Uint16(sampleA)
b := binary.BigEndian.Uint32(sampleB)
This leverages the BigEndian and LittleEndian types within the Go package https://golang.org/src/encoding/binary/binary.go.
This provides Uint16()
however there are no equivalent Int16()
or Float32()
.
Any thoughts on why not? Also, how ought this be done?
Big-endian is an order in which the "big end" (most significant value in the sequence) is stored first, at the lowest storage address. Little-endian is an order in which the "little end" (least significant value in the sequence) is stored first.
On little endian platforms, the value 1 is stored in one byte as 01 (the same as big endian), in two bytes as 01 00, and in four bytes as 01 00 00 00. If an integer is negative, the "two's complement" representation is used. The high-order bit of the most significant byte of the integer will be set on.
Specifically, little-endian is when the least significant bytes are stored before the more significant bytes, and big-endian is when the most significant bytes are stored before the less significant bytes. When we write a number (in hex), i.e. 0x12345678 , we write it with the most significant byte first (the 12 part).
Little and big endian are two ways of storing multibyte data-types ( int, float, etc). In little endian machines, last byte of binary representation of the multibyte data-type is stored first. On the other hand, in big endian machines, first byte of binary representation of the multibyte data-type is stored first.
Converting numeric types into a series of bytes ([]byte
) and vice versa is about the endianness. How you interpret the result is entirely up to you.
All you need is to assemble a 16-bit, 32-bit or 64-bit value, once it's done, you can interpret the result as you want.
For example if you already have a uint16
value, to use it as a signed value, all you need is a type conversion because the memory layout of an uint16
and int16
is the same (converting from uint16
to int16
doesn't change the memory representation just the type):
a := binary.LittleEndian.Uint16(sampleA)
// If you need int16:
a2 := int16(a)
Similarly:
a := binary.LittleEndian.Uint64(sampleA)
// If you need int64:
a2 := int64(a)
The situation is a little more complicated with uint -> float conversion as using a simple type conversion would try to convert the numeric value and not just change the type (and thus would change the memory representation).
For converting unsigned integers to float types, you can use functions of the math
package, namely math.Float32frombits()
and math.Float64frombits()
, and for the reverse direction (converting a float value to an unsigned integer) having the same memory layout: math.Float32bits()
and math.Float64bits()
.
For example:
a := binary.LittleEndian.Uint64(sampleA)
// If you need a float64:
a2 := math.Float64frombits(a)
If you would look into the implementation of these functions from the math
package, you can see that the memory value/layout is not manipulated, it is just "viewed" as a different type, by using the unsafe
package. For example:
func Float32frombits(b uint32) float32 { return *(*float32)(unsafe.Pointer(&b)) }
As mentioned by Paul, the binary
package provides Read()
and Write()
functions to do these conversions under the hood so you don't need to.
Showcasing using the same "pi" example (from the doc of binary.Read()
):
b := []byte{0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}
// USING binary.Read()
var pi float64
buf := bytes.NewReader(b)
err := binary.Read(buf, binary.LittleEndian, &pi)
if err != nil {
fmt.Println("binary.Read failed:", err)
}
fmt.Println(pi)
// Using LittleEndian.Uint64() and math.Float64frombits()
a := binary.LittleEndian.Uint64(b)
a2 := math.Float64frombits(a)
fmt.Println(a2)
Output (try it on the Go Playground):
3.141592653589793
3.141592653589793
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With