Skip to content

Commit

Permalink
reflect: Determine equivalency of float32 or float64, and *big.Float …
Browse files Browse the repository at this point in the history
…via string representation (#919)

* Alter handling of float32 and float64 values within reflect.Number (#914)

  * The reflect.Number function was handling float32 and float64 values
    by determining whether the value in the *big.Float generated through
    calling val.As() was an exact representation by examining the accuracy
    produced when calling Float32() or Float64() on the *big.Float
  * The reflect.Number function has been altered to verify whether the
    string representation, generated by calling Text(), of the generated
    *big.Float and a *big.Float created from the float32 or float64 value
    are identical

* Removing AllowRoundingNumbers option and modifying reflect.Number accordingly (#914)
  • Loading branch information
bendbennett committed Feb 22, 2024
1 parent 8d0d29d commit 3c7a391
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 584 deletions.
146 changes: 38 additions & 108 deletions internal/reflect/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
"reflect"
"strconv"

"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Number creates a *big.Float and populates it with the data in `val`. It then
Expand All @@ -24,9 +25,7 @@ import (
// *big.Int).
//
// Number will loudly fail when a number cannot be losslessly represented using
// the requested type, unless opts.AllowRoundingNumbers is set to true. This
// setting is mildly dangerous, because Terraform does not like when you round
// things, as a general rule of thumb.
// the requested type.
//
// It is meant to be called through Into, not directly.
func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) {
Expand All @@ -53,73 +52,50 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec
return reflect.ValueOf(result), diags
case reflect.TypeOf(big.NewInt(0)):
intResult, acc := result.Int(nil)
if acc != big.Exact && !opts.AllowRoundingNumbers {
if acc != big.Exact {
return reflect.ValueOf(result), append(diags, roundingErrorDiag)
}
return reflect.ValueOf(intResult), diags
}

switch target.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
intResult, acc := result.Int64()
if acc != big.Exact && !opts.AllowRoundingNumbers {
if acc != big.Exact {
return target, append(diags, roundingErrorDiag)
}
switch target.Kind() {
case reflect.Int:
if strconv.IntSize == 32 && intResult > math.MaxInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt32
return target, append(diags, roundingErrorDiag)
}
if strconv.IntSize == 32 && intResult < math.MinInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int(intResult)), diags
case reflect.Int8:
if intResult > math.MaxInt8 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt8
return target, append(diags, roundingErrorDiag)
}
if intResult < math.MinInt8 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt8
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int8(intResult)), diags
case reflect.Int16:
if intResult > math.MaxInt16 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt16
return target, append(diags, roundingErrorDiag)
}
if intResult < math.MinInt16 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt16
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int16(intResult)), diags
case reflect.Int32:
if intResult > math.MaxInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MaxInt32
return target, append(diags, roundingErrorDiag)
}
if intResult < math.MinInt32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
intResult = math.MinInt32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(int32(intResult)), diags
case reflect.Int64:
Expand All @@ -128,113 +104,67 @@ func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflec
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:
uintResult, acc := result.Uint64()
if acc != big.Exact && !opts.AllowRoundingNumbers {
if acc != big.Exact {
return target, append(diags, roundingErrorDiag)
}
switch target.Kind() {
case reflect.Uint:
if strconv.IntSize == 32 && uintResult > math.MaxUint32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint(uintResult)), diags
case reflect.Uint8:
if uintResult > math.MaxUint8 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint8
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint8(uintResult)), diags
case reflect.Uint16:
if uintResult > math.MaxUint16 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint16
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint16(uintResult)), diags
case reflect.Uint32:
if uintResult > math.MaxUint32 {
if !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
uintResult = math.MaxUint32
return target, append(diags, roundingErrorDiag)
}
return reflect.ValueOf(uint32(uintResult)), diags
case reflect.Uint64:
return reflect.ValueOf(uintResult), diags
}
case reflect.Float32:
floatResult, acc := result.Float32()
if acc != big.Exact && !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
} else if acc == big.Above {
floatResult = math.MaxFloat32
} else if acc == big.Below {
floatResult = math.SmallestNonzeroFloat32
} else if acc != big.Exact {
err := fmt.Errorf("unsure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
floatResult, _ := result.Float32()

bf := big.NewFloat(float64(floatResult))

if result.Text('f', -1) != bf.Text('f', -1) {
diags.Append(roundingErrorDiag)

return target, diags
}

return reflect.ValueOf(floatResult), diags
case reflect.Float64:
floatResult, acc := result.Float64()
if acc != big.Exact && !opts.AllowRoundingNumbers {
return target, append(diags, roundingErrorDiag)
}
if acc == big.Above {
if floatResult == math.Inf(1) || floatResult == math.MaxFloat64 {
floatResult = math.MaxFloat64
} else if floatResult == 0.0 || floatResult == math.SmallestNonzeroFloat64 {
floatResult = -math.SmallestNonzeroFloat64
} else {
err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
return target, diags
}
} else if acc == big.Below {
if floatResult == math.Inf(-1) || floatResult == -math.MaxFloat64 {
floatResult = -math.MaxFloat64
} else if floatResult == -0.0 || floatResult == -math.SmallestNonzeroFloat64 { //nolint:staticcheck
floatResult = math.SmallestNonzeroFloat64
} else {
err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
return target, diags
}
} else if acc != big.Exact {
err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult)
diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)
floatResult, _ := result.Float64()

bf := big.NewFloat(floatResult)

if result.Text('f', -1) != bf.Text('f', -1) {
diags.Append(roundingErrorDiag)

return target, diags
}

return reflect.ValueOf(floatResult), diags
}

err = fmt.Errorf("cannot convert number to %s", target.Type())

diags.AddAttributeError(
path,
"Value Conversion Error",
"An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(),
)

return target, diags
}

Expand Down

0 comments on commit 3c7a391

Please sign in to comment.