Skip to content

Commit dcc3088

Browse files
haoqixumvdan
authored andcommitted
cue: fix decoding into big.Float
cue decodes a value into big.Float with big.Float.MarshalText incorrectly and returns an error if the value can not be represented by a float64. This adds the Float method to cue.Value for converting a number into big.Float and decodes a value into big.Float if it cannot be represented by float64. Fixes #3927 Change-Id: Ia81518bd06d8e8566a66f46f0d56006919ff75d7 Signed-off-by: haoqixu <[email protected]> Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1215548 Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Roger Peppe <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent d1210a5 commit dcc3088

File tree

4 files changed

+116
-13
lines changed

4 files changed

+116
-13
lines changed

cue/decode.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"cmp"
2020
"encoding"
2121
"encoding/json"
22+
"math/big"
2223
"reflect"
2324
"slices"
2425
"strconv"
@@ -125,6 +126,17 @@ func (d *decoder) decode(x reflect.Value, v Value, isPtr bool) {
125126
}
126127

127128
if it != nil {
129+
if _, ok := it.(*big.Float); ok {
130+
f, err := v.Float(nil)
131+
if err != nil {
132+
err = errors.Wrapf(err, v.Pos(), "Decode")
133+
d.addErr(err)
134+
return
135+
}
136+
x.Elem().Set(reflect.ValueOf(*f))
137+
return
138+
}
139+
128140
b, err := v.Bytes()
129141
if err != nil {
130142
err = errors.Wrapf(err, v.Pos(), "Decode")
@@ -273,7 +285,10 @@ func (d *decoder) interfaceValue(v Value) (x interface{}) {
273285
x, err = v.Int(nil)
274286

275287
case FloatKind:
276-
x, err = v.Float64() // or big int or
288+
if f, err := v.Float64(); err == nil {
289+
return f
290+
} // or big int or
291+
x, err = v.Float(nil)
277292

278293
case StringKind:
279294
x, err = v.String()
@@ -895,11 +910,11 @@ func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.Te
895910
}
896911
if v.Type().NumMethod() > 0 && v.CanInterface() {
897912
if u, ok := v.Interface().(json.Unmarshaler); ok {
898-
return u, nil, reflect.Value{}
913+
return u, nil, v
899914
}
900915
if !decodingNull {
901916
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
902-
return nil, u, reflect.Value{}
917+
return nil, u, v
903918
}
904919
}
905920
}

cue/decode_test.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -285,27 +285,21 @@ func TestDecode(t *testing.T) {
285285
dst: new(interface{}),
286286
want: float64(1.797693134e+308),
287287
}, {
288-
// even larger float which doesn't fit into a float64
289-
// TODO(mvdan): this should work via *big.Float, just like we do with *big.Int above.
290288
value: `1.99769313499e+508`,
291289
dst: new(interface{}),
292-
err: "value was rounded up",
290+
want: bigFloat(`1.99769313499e+508`),
293291
}, {
294-
// same as the above, but negative
295-
// TODO(mvdan): this should work via *big.Float, just like we do with *big.Int above.
296292
value: `-1.99769313499e+508`,
297293
dst: new(interface{}),
298-
err: "value was rounded down",
294+
want: bigFloat(`-1.99769313499e+508`),
299295
}, {
300-
// TODO: this should work.
301296
value: `1.99769313499e+508`,
302297
dst: new(*big.Float),
303-
err: "Decode: cannot use value 1.99769313499E+508 (type float) as (string|bytes)",
298+
want: bigFloat(`1.99769313499e+508`),
304299
}, {
305-
// TODO: this should work.
306300
value: `-1.99769313499e+508`,
307301
dst: new(*big.Float),
308-
err: "Decode: cannot use value -1.99769313499E+508 (type float) as (string|bytes)",
302+
want: bigFloat(`-1.99769313499e+508`),
309303
}}
310304
for _, tc := range testCases {
311305
cuetdtest.FullMatrix.Run(t, tc.value, func(t *testing.T, m *cuetdtest.M) {
@@ -315,6 +309,8 @@ func TestDecode(t *testing.T) {
315309
got := reflect.ValueOf(tc.dst).Elem().Interface()
316310
if diff := cmp.Diff(got, tc.want, cmp.Comparer(func(a, b *big.Int) bool {
317311
return a.Cmp(b) == 0
312+
}), cmp.Comparer(func(a, b *big.Float) bool {
313+
return a.Cmp(b) == 0
318314
})); diff != "" {
319315
t.Error(diff)
320316
t.Errorf("\n%#v\n%#v", got, tc.want)
@@ -328,6 +324,12 @@ func bigInt(s string) *big.Int {
328324
return n
329325
}
330326

327+
func bigFloat(s string) *big.Float {
328+
f := &big.Float{}
329+
f, _, _ = f.Parse(s, 0)
330+
return f
331+
}
332+
331333
func TestDecodeIntoCUEValue(t *testing.T) {
332334
cuetdtest.FullMatrix.Do(t, func(t *testing.T, m *cuetdtest.M) {
333335
// We should be able to decode into a CUE value so we can

cue/types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,24 @@ func init() {
491491
}
492492
}
493493

494+
// Float returns a big.Float nearest to x. It reports an error if v is
495+
// not a number. If a non-nil *Float argument f is provided, Float stores the result in f
496+
// instead of allocating a new Float.
497+
func (v Value) Float(f *big.Float) (*big.Float, error) {
498+
var err error
499+
500+
n, err := v.getNum(adt.NumberKind)
501+
if err != nil {
502+
return nil, err
503+
}
504+
if f == nil {
505+
f = &big.Float{}
506+
}
507+
508+
f, _, err = f.Parse(n.X.String(), 0)
509+
return f, err
510+
}
511+
494512
// Float64 returns the float64 value nearest to x. It reports an error if v is
495513
// not a number. If x is too small to be represented by a float64 (|x| <
496514
// math.SmallestNonzeroFloat64), the result is (0, ErrBelow) or (-0, ErrAbove),

cue/types_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,74 @@ func TestFloat(t *testing.T) {
581581
}
582582
}
583583

584+
func TestBigFloat(t *testing.T) {
585+
testCases := []struct {
586+
value string
587+
bigFloat *big.Float
588+
kind cue.Kind
589+
err string
590+
}{{
591+
value: "1",
592+
bigFloat: bigFloat("1"),
593+
kind: cue.IntKind,
594+
}, {
595+
value: "-1",
596+
bigFloat: bigFloat("-1"),
597+
kind: cue.IntKind,
598+
}, {
599+
value: "0.0",
600+
bigFloat: bigFloat("0.0"),
601+
kind: cue.FloatKind,
602+
}, {
603+
value: "1.0",
604+
bigFloat: bigFloat("1.0"),
605+
kind: cue.FloatKind,
606+
}, {
607+
value: "2.6",
608+
bigFloat: bigFloat("2.6"),
609+
kind: cue.FloatKind,
610+
}, {
611+
value: "20.600",
612+
bigFloat: bigFloat("20.600"),
613+
kind: cue.FloatKind,
614+
}, {
615+
value: "1/0",
616+
bigFloat: nil,
617+
err: "division by zero",
618+
kind: cue.BottomKind,
619+
}, {
620+
value: "1.797693134862315708145274237317043567982e+308",
621+
bigFloat: bigFloat("1.797693134862315708145274237317043567982e+308"),
622+
kind: cue.FloatKind,
623+
}, {
624+
value: "-1.797693134862315708145274237317043567982e+308",
625+
bigFloat: bigFloat("-1.797693134862315708145274237317043567982e+308"),
626+
kind: cue.FloatKind,
627+
}, {
628+
value: "4.940656458412465441765687928682213723650e-324",
629+
bigFloat: bigFloat("4.940656458412465441765687928682213723650e-324"),
630+
kind: cue.FloatKind,
631+
}, {
632+
value: "-4.940656458412465441765687928682213723650e-324",
633+
bigFloat: bigFloat("-4.940656458412465441765687928682213723650e-324"),
634+
kind: cue.FloatKind,
635+
}}
636+
for _, tc := range testCases {
637+
cuetdtest.FullMatrix.Run(t, tc.value, func(t *testing.T, m *cuetdtest.M) {
638+
n := getValue(m, tc.value)
639+
if n.Kind() != tc.kind {
640+
t.Fatal("Not a number")
641+
}
642+
643+
bf, err := n.Float(nil)
644+
checkErr(t, err, tc.err, "Float")
645+
if bf != nil && bf.Cmp(tc.bigFloat) != 0 {
646+
t.Errorf("Float: got %v; want %v", bf, tc.bigFloat)
647+
}
648+
})
649+
}
650+
}
651+
584652
func TestString(t *testing.T) {
585653
testCases := []struct {
586654
value string

0 commit comments

Comments
 (0)