From 77d6820075989b5de42bf278d02f58ba647a012d Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Wed, 17 Apr 2024 10:58:07 +0200 Subject: [PATCH] Convert between integer and float in normalization (#1371) ## Changes We currently issue a warning if an integer is used where a floating point number is expected. But if they are convertible, we should convert and not issue a warning. This change fixes normalization if they are convertible between each other. We still produce a warning if the type conversion leads to a loss in precision. ## Tests Unit tests pass. --- libs/dyn/convert/normalize.go | 20 +++++++++++++ libs/dyn/convert/normalize_test.go | 46 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/libs/dyn/convert/normalize.go b/libs/dyn/convert/normalize.go index b4bee977..35d4d821 100644 --- a/libs/dyn/convert/normalize.go +++ b/libs/dyn/convert/normalize.go @@ -293,6 +293,16 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn switch src.Kind() { case dyn.KindInt: out = src.MustInt() + case dyn.KindFloat: + out = int64(src.MustFloat()) + if src.MustFloat() != float64(out) { + return dyn.InvalidValue, diags.Append(diag.Diagnostic{ + Severity: diag.Warning, + Summary: fmt.Sprintf(`cannot accurately represent "%g" as integer due to precision loss`, src.MustFloat()), + Location: src.Location(), + Path: path, + }) + } case dyn.KindString: var err error out, err = strconv.ParseInt(src.MustString(), 10, 64) @@ -326,6 +336,16 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d switch src.Kind() { case dyn.KindFloat: out = src.MustFloat() + case dyn.KindInt: + out = float64(src.MustInt()) + if src.MustInt() != int64(out) { + return dyn.InvalidValue, diags.Append(diag.Diagnostic{ + Severity: diag.Warning, + Summary: fmt.Sprintf(`cannot accurately represent "%d" as floating point number due to precision loss`, src.MustInt()), + Location: src.Location(), + Path: path, + }) + } case dyn.KindString: var err error out, err = strconv.ParseFloat(src.MustString(), 64) diff --git a/libs/dyn/convert/normalize_test.go b/libs/dyn/convert/normalize_test.go index 1a0869a9..843b4ea5 100644 --- a/libs/dyn/convert/normalize_test.go +++ b/libs/dyn/convert/normalize_test.go @@ -555,6 +555,27 @@ func TestNormalizeIntNil(t *testing.T) { }, err[0]) } +func TestNormalizeIntFromFloat(t *testing.T) { + var typ int + vin := dyn.V(float64(1.0)) + vout, err := Normalize(&typ, vin) + assert.Empty(t, err) + assert.Equal(t, dyn.V(int64(1)), vout) +} + +func TestNormalizeIntFromFloatError(t *testing.T) { + var typ int + vin := dyn.V(1.5) + _, err := Normalize(&typ, vin) + assert.Len(t, err, 1) + assert.Equal(t, diag.Diagnostic{ + Severity: diag.Warning, + Summary: `cannot accurately represent "1.5" as integer due to precision loss`, + Location: vin.Location(), + Path: dyn.EmptyPath, + }, err[0]) +} + func TestNormalizeIntFromString(t *testing.T) { var typ int vin := dyn.V("123") @@ -618,6 +639,31 @@ func TestNormalizeFloatNil(t *testing.T) { }, err[0]) } +func TestNormalizeFloatFromInt(t *testing.T) { + var typ float64 + + // Maximum safe integer that can be accurately represented as a float. + vin := dyn.V(int64(9007199254740992)) + vout, err := Normalize(&typ, vin) + assert.Empty(t, err) + assert.Equal(t, dyn.V(float64(9007199254740992)), vout) +} + +func TestNormalizeFloatFromIntError(t *testing.T) { + var typ float64 + + // Minimum integer that cannot be accurately represented as a float. + vin := dyn.V(9007199254740992 + 1) + _, err := Normalize(&typ, vin) + assert.Len(t, err, 1) + assert.Equal(t, diag.Diagnostic{ + Severity: diag.Warning, + Summary: `cannot accurately represent "9007199254740993" as floating point number due to precision loss`, + Location: vin.Location(), + Path: dyn.EmptyPath, + }, err[0]) +} + func TestNormalizeFloatFromString(t *testing.T) { var typ float64 vin := dyn.V("1.2")