Skip to content

Commit 725a4d2

Browse files
authored
Merge pull request #40 from Basekick-Labs/fix/int64-float64-decode
fix(decode): allow float-encoded values to decode into int64/uint64 (#2)
2 parents 9e0e881 + fdc8ac6 commit 725a4d2

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

decode_number.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,32 @@ func (d *Decoder) DecodeUint64() (uint64, error) {
8787
return d.uint(c)
8888
}
8989

90+
func floatToInt64(f float64) (int64, error) {
91+
if math.IsNaN(f) || math.IsInf(f, 0) {
92+
return 0, fmt.Errorf("msgpack: cannot decode %v into int64", f)
93+
}
94+
if f != math.Trunc(f) {
95+
return 0, fmt.Errorf("msgpack: cannot decode fractional float %v into int64", f)
96+
}
97+
if f > float64(math.MaxInt64) || f < float64(math.MinInt64) {
98+
return 0, fmt.Errorf("msgpack: float %v overflows int64", f)
99+
}
100+
return int64(f), nil
101+
}
102+
103+
func floatToUint64(f float64) (uint64, error) {
104+
if math.IsNaN(f) || math.IsInf(f, 0) {
105+
return 0, fmt.Errorf("msgpack: cannot decode %v into uint64", f)
106+
}
107+
if f != math.Trunc(f) {
108+
return 0, fmt.Errorf("msgpack: cannot decode fractional float %v into uint64", f)
109+
}
110+
if f < 0 || f > float64(math.MaxUint64) {
111+
return 0, fmt.Errorf("msgpack: float %v overflows uint64", f)
112+
}
113+
return uint64(f), nil
114+
}
115+
90116
func (d *Decoder) uint(c byte) (uint64, error) {
91117
if c == msgpcode.Nil {
92118
return 0, nil
@@ -115,6 +141,18 @@ func (d *Decoder) uint(c byte) (uint64, error) {
115141
return uint64(n), err
116142
case msgpcode.Uint64, msgpcode.Int64:
117143
return d.uint64()
144+
case msgpcode.Float:
145+
n, err := d.float32(c)
146+
if err != nil {
147+
return 0, err
148+
}
149+
return floatToUint64(float64(n))
150+
case msgpcode.Double:
151+
n, err := d.float64(c)
152+
if err != nil {
153+
return 0, err
154+
}
155+
return floatToUint64(n)
118156
}
119157
return 0, fmt.Errorf("msgpack: invalid code=%x decoding uint64", c)
120158
}
@@ -158,6 +196,18 @@ func (d *Decoder) int(c byte) (int64, error) {
158196
case msgpcode.Uint64, msgpcode.Int64:
159197
n, err := d.uint64()
160198
return int64(n), err
199+
case msgpcode.Float:
200+
n, err := d.float32(c)
201+
if err != nil {
202+
return 0, err
203+
}
204+
return floatToInt64(float64(n))
205+
case msgpcode.Double:
206+
n, err := d.float64(c)
207+
if err != nil {
208+
return 0, err
209+
}
210+
return floatToInt64(n)
161211
}
162212
return 0, fmt.Errorf("msgpack: invalid code=%x decoding int64", c)
163213
}

types_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,73 @@ func TestDecodeFloat32FromFloat64(t *testing.T) {
10281028
require.Error(t, msgpack.Unmarshal(b, &f))
10291029
}
10301030

1031+
func TestDecodeInt64FromFloat64(t *testing.T) {
1032+
// Issue #2: float64-encoded values (e.g. from JSON) should decode into int64/uint64.
1033+
1034+
t.Run("happy path", func(t *testing.T) {
1035+
// float64 -> int64
1036+
b, err := msgpack.Marshal(float64(10))
1037+
require.NoError(t, err)
1038+
var i int64
1039+
require.NoError(t, msgpack.Unmarshal(b, &i))
1040+
require.Equal(t, int64(10), i)
1041+
1042+
// float64 -> uint64
1043+
var u uint64
1044+
require.NoError(t, msgpack.Unmarshal(b, &u))
1045+
require.Equal(t, uint64(10), u)
1046+
1047+
// float32 -> int64
1048+
b, err = msgpack.Marshal(float32(42))
1049+
require.NoError(t, err)
1050+
require.NoError(t, msgpack.Unmarshal(b, &i))
1051+
require.Equal(t, int64(42), i)
1052+
1053+
// negative float64 -> int64
1054+
b, err = msgpack.Marshal(float64(-7))
1055+
require.NoError(t, err)
1056+
require.NoError(t, msgpack.Unmarshal(b, &i))
1057+
require.Equal(t, int64(-7), i)
1058+
1059+
// zero
1060+
b, err = msgpack.Marshal(float64(0))
1061+
require.NoError(t, err)
1062+
require.NoError(t, msgpack.Unmarshal(b, &i))
1063+
require.Equal(t, int64(0), i)
1064+
})
1065+
1066+
t.Run("errors", func(t *testing.T) {
1067+
// NaN -> int64
1068+
b, err := msgpack.Marshal(math.NaN())
1069+
require.NoError(t, err)
1070+
var i int64
1071+
require.Error(t, msgpack.Unmarshal(b, &i))
1072+
1073+
// NaN -> uint64
1074+
var u uint64
1075+
require.Error(t, msgpack.Unmarshal(b, &u))
1076+
1077+
// +Inf -> int64
1078+
b, _ = msgpack.Marshal(math.Inf(1))
1079+
require.Error(t, msgpack.Unmarshal(b, &i))
1080+
1081+
// -Inf -> uint64
1082+
b, _ = msgpack.Marshal(math.Inf(-1))
1083+
require.Error(t, msgpack.Unmarshal(b, &u))
1084+
1085+
// fractional -> int64
1086+
b, _ = msgpack.Marshal(3.14)
1087+
require.Error(t, msgpack.Unmarshal(b, &i))
1088+
1089+
// fractional -> uint64
1090+
require.Error(t, msgpack.Unmarshal(b, &u))
1091+
1092+
// negative float -> uint64
1093+
b, _ = msgpack.Marshal(float64(-1))
1094+
require.Error(t, msgpack.Unmarshal(b, &u))
1095+
})
1096+
}
1097+
10311098
func TestFloat32(t *testing.T) {
10321099
tests := []struct {
10331100
in float32

0 commit comments

Comments
 (0)