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.
This commit is contained in:
Pieter Noordhuis 2024-04-17 10:58:07 +02:00 committed by GitHub
parent c949655f9f
commit 77d6820075
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 0 deletions

View File

@ -293,6 +293,16 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
switch src.Kind() { switch src.Kind() {
case dyn.KindInt: case dyn.KindInt:
out = src.MustInt() 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: case dyn.KindString:
var err error var err error
out, err = strconv.ParseInt(src.MustString(), 10, 64) 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() { switch src.Kind() {
case dyn.KindFloat: case dyn.KindFloat:
out = src.MustFloat() 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: case dyn.KindString:
var err error var err error
out, err = strconv.ParseFloat(src.MustString(), 64) out, err = strconv.ParseFloat(src.MustString(), 64)

View File

@ -555,6 +555,27 @@ func TestNormalizeIntNil(t *testing.T) {
}, err[0]) }, 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) { func TestNormalizeIntFromString(t *testing.T) {
var typ int var typ int
vin := dyn.V("123") vin := dyn.V("123")
@ -618,6 +639,31 @@ func TestNormalizeFloatNil(t *testing.T) {
}, err[0]) }, 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) { func TestNormalizeFloatFromString(t *testing.T) {
var typ float64 var typ float64
vin := dyn.V("1.2") vin := dyn.V("1.2")