To convert a slice of floats to a byte slice for writing to a binary file in Go, you can use the encoding/binary
package to handle byte order conversion safely and efficiently. Here's how you can do it:
Step-by-Step Explanation:
- Import Required Packages: Use
bytes
for the buffer andencoding/binary
for handling binary data with specified endianness. - Create a Buffer: A
bytes.Buffer
collects the binary data. - Write Floats to Buffer: Use
binary.Write
to encode the entire float slice into the buffer with the desired byte order (LittleEndian or BigEndian). - Handle Errors: Check for errors during the write operation.
- Extract Byte Slice: Convert the buffer's contents to a
[]byte
for writing to a file.
Example Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
"bytes"
"encoding/binary"
"os"
)
func main() {
// Example float64 slice
floats := []float64{3.14, 1.618, 2.718}
// Create a buffer and write the floats to it in LittleEndian format
var buf bytes.Buffer
err := binary.Write(&buf, binary.LittleEndian, floats)
if err != nil {
panic(err)
}
// Get the byte slice
byteSlice := buf.Bytes()
// Write the byte slice to a file
err = os.WriteFile("output.bin", byteSlice, 0644)
if err != nil {
panic(err)
}
}
Notes:
- Float Type: Ensure the float slice type (
float32
orfloat64
) matches the type expected in your application. Adjust the slice type ([]float32
or[]float64
) accordingly. - Endianness: Choose
binary.LittleEndian
orbinary.BigEndian
based on your file format requirements. - Efficiency:
binary.Write
processes the entire slice efficiently in one call, avoiding manual loops.
This approach ensures portability and correct handling of byte order, making it suitable for writing binary files that may be read on different architectures.
Benchmark
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func encode1(fs []float32) []byte {
buf := make([]byte, len(fs)*4)
for i, f := range fs {
u := math.Float32bits(f)
binary.LittleEndian.PutUint32(buf[i*4:], u)
}
return buf
}
func encode2(fs []float32) []byte {
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, fs)
return buf.Bytes()
}
func BenchmarkEncode1(b *testing.B) {
encode1(make([]float32, b.N))
}
func BenchmarkEncode2(b *testing.B) {
encode2(make([]float32, b.N))
}
Results:
1
2
BenchmarkEncode1-8 604999933 1.899 ns/op
BenchmarkEncode2-8 228542809 5.191 ns/op
Lastly, the absolute fastest method is to use unsafe
:
1
2
3
4
5
6
7
func encodeUnsafe(fs []float32) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(&fs[0])), len(fs)*4)
}
func decodeUnsafe(bs []byte) []float32 {
return unsafe.Slice((*float32)(unsafe.Pointer(&bs[0])), len(bs)/4)
}
for float64
1
2
3
4
5
6
7
8
9
func float64ToBytes(f64 float64) []byte {
bts := make([]byte, 8)
(*(*[]float64)(unsafe.Pointer(&bts)))[0] = f64
return bts
}
func bytesToF64(bts []byte) float64 {
return *(*float64)(unsafe.Pointer(&bts[0]))
}
You could even make it with zero allocation like this
1
2
3
4
5
6
7
8
func float64ToBytes(f64 float64) []byte {
var bts []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&bts))
sh.Data = uintptr(unsafe.Pointer(&f64))
sh.Len = 8
sh.Cap = sh.Len
return bts
}
Use math.Float64bits
to get the float64
as a uint64
1
2
3
4
5
6
7
8
9
10
11
func float64ToByte(f float64) []byte {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], math.Float64bits(f))
return buf[:]
}
func float64frombytes(bytes []byte) float64 {
bits := binary.BigEndian.Uint64(bytes)
float := math.Float64frombits(bits)
return float
}