Skip to content

Commit ead6023

Browse files
committed
add Float16 type
1 parent 151b6df commit ead6023

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed

custom_float16.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package struc
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
"math"
7+
"strconv"
8+
)
9+
10+
type Float16 float64
11+
12+
func (f *Float16) Pack(p []byte, opt *Options) (int, error) {
13+
order := opt.Order
14+
if order == nil {
15+
order = binary.BigEndian
16+
}
17+
sign := uint16(0)
18+
if *f < 0 {
19+
sign = 1
20+
}
21+
var frac, exp uint16
22+
if math.IsInf(float64(*f), 0) {
23+
exp = 0x1f
24+
frac = 0
25+
} else if math.IsNaN(float64(*f)) {
26+
exp = 0x1f
27+
frac = 1
28+
} else {
29+
bits := math.Float64bits(float64(*f))
30+
exp64 := (bits >> 52) & 0x7ff
31+
if exp64 != 0 {
32+
exp = uint16((exp64 - 1023 + 15) & 0x1f)
33+
}
34+
frac = uint16((bits >> 42) & 0x3ff)
35+
}
36+
var out uint16
37+
out |= sign << 15
38+
out |= exp << 10
39+
out |= frac & 0x3ff
40+
order.PutUint16(p, out)
41+
return 2, nil
42+
}
43+
func (f *Float16) Unpack(r io.Reader, length int, opt *Options) error {
44+
order := opt.Order
45+
if order == nil {
46+
order = binary.BigEndian
47+
}
48+
var tmp [2]byte
49+
if _, err := r.Read(tmp[:]); err != nil {
50+
return err
51+
}
52+
val := order.Uint16(tmp[:2])
53+
sign := (val >> 15) & 1
54+
exp := int16((val >> 10) & 0x1f)
55+
frac := val & 0x3ff
56+
if exp == 0x1f {
57+
if frac != 0 {
58+
*f = Float16(math.NaN())
59+
} else {
60+
*f = Float16(math.Inf(int(sign)*-2 + 1))
61+
}
62+
} else {
63+
var bits uint64
64+
bits |= uint64(sign) << 63
65+
bits |= uint64(frac) << 42
66+
if exp > 0 {
67+
bits |= uint64(exp-15+1023) << 52
68+
}
69+
*f = Float16(math.Float64frombits(bits))
70+
}
71+
return nil
72+
}
73+
func (f *Float16) Size(opt *Options) int {
74+
return 2
75+
}
76+
func (f *Float16) String() string {
77+
return strconv.FormatFloat(float64(*f), 'g', -1, 32)
78+
}

custom_float16_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package struc
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"fmt"
7+
"math"
8+
"strconv"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func TestFloat16(t *testing.T) {
14+
// test cases from https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Half_precision_examples
15+
tests := []struct {
16+
B string
17+
F float64
18+
}{
19+
//s expnt significand
20+
{"0 01111 0000000000", 1},
21+
{"0 01111 0000000001", 1.0009765625},
22+
{"1 10000 0000000000", -2},
23+
{"0 11110 1111111111", 65504},
24+
// {"0 00001 0000000000", 0.0000610352},
25+
// {"0 00000 1111111111", 0.0000609756},
26+
// {"0 00000 0000000001", 0.0000000596046},
27+
{"0 00000 0000000000", 0},
28+
// {"1 00000 0000000000", -0},
29+
{"0 11111 0000000000", math.Inf(1)},
30+
{"1 11111 0000000000", math.Inf(-1)},
31+
{"0 01101 0101010101", 0.333251953125},
32+
}
33+
for _, test := range tests {
34+
var buf bytes.Buffer
35+
f := Float16(test.F)
36+
if err := Pack(&buf, &f); err != nil {
37+
t.Error("pack failed:", err)
38+
continue
39+
}
40+
bitval, _ := strconv.ParseUint(strings.Replace(test.B, " ", "", -1), 2, 16)
41+
tmp := binary.BigEndian.Uint16(buf.Bytes())
42+
if tmp != uint16(bitval) {
43+
t.Errorf("incorrect pack: %s != %016b (%f)", test.B, tmp, test.F)
44+
continue
45+
}
46+
var f2 Float16
47+
if err := Unpack(&buf, &f2); err != nil {
48+
t.Error("unpack failed:", err)
49+
continue
50+
}
51+
// let sprintf deal with (im)precision for me here
52+
if fmt.Sprintf("%f", f) != fmt.Sprintf("%f", f2) {
53+
t.Errorf("incorrect unpack: %016b %f != %f", bitval, f, f2)
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)