Skip to content

Commit c6c6d84

Browse files
Merge pull request #8 from parquet-go/generics
use generics to enforce type safety
2 parents 7709569 + ee8dffb commit c6c6d84

File tree

10 files changed

+136
-143
lines changed

10 files changed

+136
-143
lines changed

bitpack.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
// integers of various bit widths.
33
package bitpack
44

5+
// Int is a type constraint representing the integer types that this package
6+
// supports.
7+
type Int interface {
8+
~int32 | ~uint32 | ~int64 | ~uint64 | ~int | ~uintptr
9+
}
10+
511
// ByteCount returns the number of bytes needed to hold the given bit count.
612
func ByteCount(bitCount uint) int {
713
return int((bitCount + 7) / 8)

examples/example_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package examples
2+
3+
import (
4+
"fmt"
5+
"github.com/parquet-go/bitpack"
6+
)
7+
8+
func ExamplePack_int32() {
9+
// Pack int32 values
10+
values := []int32{1, 2, 3, 4, 5}
11+
bitWidth := uint(3)
12+
13+
packedSize := bitpack.ByteCount(uint(len(values)) * bitWidth)
14+
// Allocate buffer with extra space for pack algorithm
15+
// (needs 3 bytes per value + padding for safe bit operations)
16+
dst := make([]byte, packedSize+bitpack.PaddingInt32)
17+
18+
// Pack the values
19+
bitpack.Pack(dst, values, bitWidth)
20+
21+
// Calculate actual packed size
22+
fmt.Printf("Packed %d values into %d bytes\n", len(values), packedSize)
23+
// Output: Packed 5 values into 2 bytes
24+
}
25+
26+
func ExampleUnpack_int32() {
27+
// First, pack some values
28+
values := []int32{10, 20, 30, 40, 50}
29+
bitWidth := uint(6)
30+
31+
packedSize := bitpack.ByteCount(uint(len(values)) * bitWidth)
32+
// Allocate buffer for packing
33+
packed := make([]byte, packedSize+bitpack.PaddingInt32)
34+
bitpack.Pack(packed, values, bitWidth)
35+
36+
// Now unpack them
37+
dst := make([]int32, len(values))
38+
bitpack.Unpack(dst, packed, bitWidth)
39+
40+
fmt.Printf("Unpacked values: %v\n", dst)
41+
// Output: Unpacked values: [10 20 30 40 50]
42+
}
43+
44+
func ExamplePack_int64() {
45+
// Pack int64 values
46+
values := []int64{100, 200, 300, 400, 500}
47+
bitWidth := uint(9)
48+
49+
packedSize := bitpack.ByteCount(uint(len(values)) * bitWidth)
50+
// Allocate buffer with extra space for pack algorithm
51+
// (needs packed size + padding for safe bit operations)
52+
dst := make([]byte, packedSize+bitpack.PaddingInt64)
53+
54+
// Pack the values
55+
bitpack.Pack(dst, values, bitWidth)
56+
57+
// Calculate actual packed size
58+
fmt.Printf("Packed %d values into %d bytes\n", len(values), packedSize)
59+
// Output: Packed 5 values into 6 bytes
60+
}
61+
62+
func ExampleUnpack_int64() {
63+
// First, pack some values
64+
values := []int64{1000, 2000, 3000, 4000, 5000}
65+
bitWidth := uint(13)
66+
67+
packedSize := bitpack.ByteCount(uint(len(values)) * bitWidth)
68+
// Allocate buffer for packing
69+
packed := make([]byte, packedSize+bitpack.PaddingInt64)
70+
bitpack.Pack(packed, values, bitWidth)
71+
72+
// Now unpack them
73+
dst := make([]int64, len(values))
74+
bitpack.Unpack(dst, packed, bitWidth)
75+
76+
fmt.Printf("Unpacked values: %v\n", dst)
77+
// Output: Unpacked values: [1000 2000 3000 4000 5000]
78+
}

examples/int32_test.go

Lines changed: 0 additions & 42 deletions
This file was deleted.

examples/int64_test.go

Lines changed: 0 additions & 42 deletions
This file was deleted.

go.mod

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
module github.com/parquet-go/bitpack
22

3-
go 1.25.1
3+
go 1.24.0
44

5-
require (
6-
github.com/parquet-go/parquet-go v0.25.1
7-
golang.org/x/sys v0.37.0
8-
)
5+
require golang.org/x/sys v0.37.0

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
github.com/parquet-go/parquet-go v0.25.1 h1:l7jJwNM0xrk0cnIIptWMtnSnuxRkwq53S+Po3KG8Xgo=
2-
github.com/parquet-go/parquet-go v0.25.1/go.mod h1:AXBuotO1XiBtcqJb/FKFyjBG4aqa3aQAAWF3ZPzCanY=
31
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
42
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=

pack.go

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
package bitpack
22

3-
// PackInt32 packs values from src to dst, each value is packed into the given
4-
// bit width regardless of how many bits are needed to represent it.
5-
//
6-
// The function panics if dst is too short to hold the bit packed values.
7-
func PackInt32(dst []byte, src []int32, bitWidth uint) {
8-
assertPack(dst, len(src), bitWidth)
9-
packInt32(dst, src, bitWidth)
10-
}
3+
import (
4+
"unsafe"
115

12-
// PackInt64 packs values from src to dst, each value is packed into the given
13-
// bit width regardless of how many bits are needed to represent it.
14-
//
15-
// The function panics if dst is too short to hold the bit packed values.
16-
func PackInt64(dst []byte, src []int64, bitWidth uint) {
17-
assertPack(dst, len(src), bitWidth)
18-
packInt64(dst, src, bitWidth)
19-
}
6+
"github.com/parquet-go/bitpack/unsafecast"
7+
)
208

21-
func assertPack(dst []byte, count int, bitWidth uint) {
22-
_ = dst[:ByteCount(bitWidth*uint(count))]
9+
// Pack packs values from src to dst, each value is packed into the given
10+
// bit width regardless of how many bits are needed to represent it.
11+
func Pack[T Int](dst []byte, src []T, bitWidth uint) {
12+
_ = dst[:ByteCount(bitWidth*uint(len(src)))]
13+
switch unsafe.Sizeof(T(0)) {
14+
case 4:
15+
packInt32(dst, unsafecast.Slice[int32](src), bitWidth)
16+
default:
17+
packInt64(dst, unsafecast.Slice[int64](src), bitWidth)
18+
}
2319
}

pack_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ func TestPackInt32(t *testing.T) {
2525
// Pack the values
2626
size := bitpack.ByteCount(uint(n) * bitWidth)
2727
packed := make([]byte, size+bitpack.PaddingInt32)
28-
bitpack.PackInt32(packed, block[:n], bitWidth)
28+
bitpack.Pack(packed, block[:n], bitWidth)
2929

3030
// Unpack and verify
3131
unpacked := make([]int32, n)
32-
bitpack.UnpackInt32(unpacked, packed, bitWidth)
32+
bitpack.Unpack(unpacked, packed, bitWidth)
3333

3434
if !slices.Equal(block[:n], unpacked) {
3535
t.Fatalf("values mismatch for length=%d\nwant: %v\ngot: %v", n, block[:n], unpacked)
@@ -55,11 +55,11 @@ func TestPackInt64(t *testing.T) {
5555
// Pack the values
5656
size := bitpack.ByteCount(uint(n) * bitWidth)
5757
packed := make([]byte, size+bitpack.PaddingInt64)
58-
bitpack.PackInt64(packed, block[:n], bitWidth)
58+
bitpack.Pack(packed, block[:n], bitWidth)
5959

6060
// Unpack and verify
6161
unpacked := make([]int64, n)
62-
bitpack.UnpackInt64(unpacked, packed, bitWidth)
62+
bitpack.Unpack(unpacked, packed, bitWidth)
6363

6464
if !slices.Equal(block[:n], unpacked) {
6565
t.Fatalf("values mismatch for length=%d\nwant: %v\ngot: %v", n, block[:n], unpacked)
@@ -86,7 +86,7 @@ func BenchmarkPackInt32(b *testing.B) {
8686
src := block[:]
8787

8888
for i := 0; i < b.N; i++ {
89-
bitpack.PackInt32(dst, src, bitWidth)
89+
bitpack.Pack(dst, src, bitWidth)
9090
}
9191

9292
b.SetBytes(4 * blockSize)
@@ -114,7 +114,7 @@ func BenchmarkPackInt64(b *testing.B) {
114114
src := block[:]
115115

116116
for i := 0; i < b.N; i++ {
117-
bitpack.PackInt64(dst, src, bitWidth)
117+
bitpack.Pack(dst, src, bitWidth)
118118
}
119119

120120
b.SetBytes(8 * blockSize)

unpack.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package bitpack
22

3+
import (
4+
"unsafe"
5+
6+
"github.com/parquet-go/bitpack/unsafecast"
7+
)
8+
39
// PaddingInt32 is the padding expected to exist after the end of input buffers
410
// for the UnpackInt32 algorithm to avoid reading beyond the end of the input.
511
const PaddingInt32 = 16
@@ -8,20 +14,16 @@ const PaddingInt32 = 16
814
// for the UnpackInt32 algorithm to avoid reading beyond the end of the input.
915
const PaddingInt64 = 32
1016

11-
// UnpackInt32 unpacks 32 bit integers from src to dst.
12-
//
13-
// The function unpacked len(dst) integers, it panics if src is too short to
14-
// contain len(dst) values of the given bit width.
15-
func UnpackInt32(dst []int32, src []byte, bitWidth uint) {
16-
_ = src[:ByteCount(bitWidth*uint(len(dst))+8*PaddingInt32)]
17-
unpackInt32(dst, src, bitWidth)
18-
}
19-
20-
// UnpackInt64 unpacks 64 bit integers from src to dst.
21-
//
22-
// The function unpacked len(dst) integers, it panics if src is too short to
23-
// contain len(dst) values of the given bit width.
24-
func UnpackInt64(dst []int64, src []byte, bitWidth uint) {
25-
_ = src[:ByteCount(bitWidth*uint(len(dst))+8*PaddingInt64)]
26-
unpackInt64(dst, src, bitWidth)
17+
// Unpack unpacks values from src to dst, each value is unpacked from the given
18+
// bit width regardless of how many bits are needed to represent it.
19+
func Unpack[T Int](dst []T, src []byte, bitWidth uint) {
20+
sizeofT := uint(unsafe.Sizeof(T(0)))
21+
padding := (8 * sizeofT) / 2 // 32 bits => 16, 64 bits => 32
22+
_ = src[:ByteCount(bitWidth*uint(len(dst))+8*padding)]
23+
switch sizeofT {
24+
case 4:
25+
unpackInt32(unsafecast.Slice[int32](dst), src, bitWidth)
26+
default:
27+
unpackInt64(unsafecast.Slice[int64](dst), src, bitWidth)
28+
}
2729
}

0 commit comments

Comments
 (0)