Skip to content

Commit

Permalink
Clarify error on out of safe range floats
Browse files Browse the repository at this point in the history
Fixes #396
  • Loading branch information
arp242 committed Oct 1, 2023
1 parent 59c762d commit 663cca6
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 26 deletions.
2 changes: 1 addition & 1 deletion decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ func (md *MetaData) unifyFloat64(data any, rv reflect.Value) error {
if num, ok := data.(int64); ok {
if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) ||
(rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) {
return md.parseErr(errParseRange{i: num, size: rvk.String()})
return md.parseErr(errUnsafeFloat{i: num, size: rvk.String()})
}
rv.SetFloat(float64(num))
return nil
Expand Down
24 changes: 13 additions & 11 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func TestDecodeErrors(t *testing.T) {
{
&struct{ V float32 }{},
`V = 999999999999999`,
`toml: line 1 (last key "V"): 999999999999999 is out of range for float32`,
`toml: line 1 (last key "V"): 999999999999999 is out of the safe float32 range`,
},
{
&struct{ V string }{},
Expand Down Expand Up @@ -198,7 +198,7 @@ func TestDecodeErrors(t *testing.T) {
{
&struct{ V struct{ N float32 } }{},
`V.N = 999999999999999`,
`toml: line 1 (last key "V.N"): 999999999999999 is out of range for float32`,
`toml: line 1 (last key "V.N"): 999999999999999 is out of the safe float32 range`,
},
{
&struct{ V struct{ N string } }{},
Expand All @@ -225,13 +225,15 @@ func TestDecodeErrors(t *testing.T) {
}

for _, tt := range tests {
_, err := Decode(tt.toml, tt.s)
if err == nil {
t.Fatal("err is nil")
}
if err.Error() != tt.wantErr {
t.Errorf("\nhave: %q\nwant: %q", err, tt.wantErr)
}
t.Run("", func(t *testing.T) {
_, err := Decode(tt.toml, tt.s)
if err == nil {
t.Fatal("err is nil")
}
if err.Error() != tt.wantErr {
t.Errorf("\nhave: %q\nwant: %q", err, tt.wantErr)
}
})
}
}

Expand Down Expand Up @@ -416,8 +418,8 @@ func TestDecodeFloatOverflow(t *testing.T) {
if tt.overflow && err == nil {
t.Fatal("expected error, but err is nil")
}
if (tt.overflow && !errorContains(err, "out of range")) || (!tt.overflow && err != nil) {
t.Fatalf("unexpected error:\n%v", err)
if (tt.overflow && !errorContains(err, "out of the safe float") && !errorContains(err, "out of range")) || (!tt.overflow && err != nil) {
t.Fatalf("unexpected error:\n%T: %[1]v", err)
}
})
}
Expand Down
34 changes: 29 additions & 5 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ type (
i any // int or float
size string // "int64", "uint16", etc.
}
errUnsafeFloat struct {
i interface{} // float32 or float64
size string // "float32" or "float64"
}
errParseDuration struct{ d string }
)

Expand All @@ -190,8 +194,12 @@ func (e errLexStringNL) Error() string { return "strings cannot contain new
func (e errLexStringNL) Usage() string { return usageStringNewline }
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
func (e errParseRange) Usage() string { return usageIntOverflow }
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
func (e errParseDuration) Usage() string { return usageDuration }
func (e errUnsafeFloat) Error() string {
return fmt.Sprintf("%v is out of the safe %s range", e.i, e.size)
}
func (e errUnsafeFloat) Usage() string { return usageUnsafeFloat }
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
func (e errParseDuration) Usage() string { return usageDuration }

const usageEscape = `
A '\' inside a "-delimited string is interpreted as an escape character.
Expand Down Expand Up @@ -248,19 +256,35 @@ bug in the program that uses too small of an integer.
The maximum and minimum values are:
size │ lowest │ highest
───────┼────────────────┼──────────
───────┼────────────────┼──────────────
int8 │ -128 │ 127
int16 │ -32,768 │ 32,767
int32 │ -2,147,483,648 │ 2,147,483,647
int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
uint8 │ 0 │ 255
uint16 │ 0 │ 65535
uint32 │ 0 │ 4294967295
uint16 │ 0 │ 65,535
uint32 │ 0 │ 4,294,967,295
uint64 │ 0 │ 1.8 × 10¹⁸
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
`

const usageUnsafeFloat = `
This number is outside of the "safe" range for floating point numbers; whole
(non-fractional) numbers outside the below range can not always be represented
accurately in a float, leading to some loss of accuracy.
Explicitly mark a number as a fractional unit by adding ".0", which will incur
some loss of accuracy; for example:
f = 2_000_000_000.0
Accuracy ranges:
float32 = 16,777,215
float64 = 9,007,199,254,740,991
`

const usageDuration = `
A duration must be as "number<unit>", without any spaces. Valid units are:
Expand Down
18 changes: 9 additions & 9 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ func TestParseError(t *testing.T) {
| The maximum and minimum values are:
|
| size │ lowest │ highest
| ───────┼────────────────┼──────────
| ───────┼────────────────┼──────────────
| int8 │ -128 │ 127
| int16 │ -32,768 │ 32,767
| int32 │ -2,147,483,648 │ 2,147,483,647
| int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
| uint8 │ 0 │ 255
| uint16 │ 0 │ 65535
| uint32 │ 0 │ 4294967295
| uint16 │ 0 │ 65,535
| uint32 │ 0 │ 4,294,967,295
| uint64 │ 0 │ 1.8 × 10¹⁸
|
| int refers to int32 on 32-bit systems and int64 on 64-bit systems.
Expand All @@ -134,14 +134,14 @@ func TestParseError(t *testing.T) {
| The maximum and minimum values are:
|
| size │ lowest │ highest
| ───────┼────────────────┼──────────
| ───────┼────────────────┼──────────────
| int8 │ -128 │ 127
| int16 │ -32,768 │ 32,767
| int32 │ -2,147,483,648 │ 2,147,483,647
| int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
| uint8 │ 0 │ 255
| uint16 │ 0 │ 65535
| uint32 │ 0 │ 4294967295
| uint16 │ 0 │ 65,535
| uint32 │ 0 │ 4,294,967,295
| uint64 │ 0 │ 1.8 × 10¹⁸
|
| int refers to int32 on 32-bit systems and int64 on 64-bit systems.
Expand All @@ -165,14 +165,14 @@ func TestParseError(t *testing.T) {
| The maximum and minimum values are:
|
| size │ lowest │ highest
| ───────┼────────────────┼──────────
| ───────┼────────────────┼──────────────
| int8 │ -128 │ 127
| int16 │ -32,768 │ 32,767
| int32 │ -2,147,483,648 │ 2,147,483,647
| int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
| uint8 │ 0 │ 255
| uint16 │ 0 │ 65535
| uint32 │ 0 │ 4294967295
| uint16 │ 0 │ 65,535
| uint32 │ 0 │ 4,294,967,295
| uint64 │ 0 │ 1.8 × 10¹⁸
|
| int refers to int32 on 32-bit systems and int64 on 64-bit systems.
Expand Down

0 comments on commit 663cca6

Please sign in to comment.