Skip to content

Commit 75e13d9

Browse files
committed
Fix multiple large int64 handling issues.
- Fix int64 conversion ignoring unsigned int types. - Throw an error on float64 that don't fit it int64. - Return 0 for out of range values instead of undefined values. - Preserve precision on large int strings. Fixes #428 Fixes #433 Fixes #434 Fixes #435
1 parent b133504 commit 75e13d9

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

koanf.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding"
66
"fmt"
7+
"math"
78
"reflect"
89
"sort"
910
"strconv"
@@ -483,17 +484,53 @@ func toInt64(v any) (int64, error) {
483484
return int64(i), nil
484485
case int64:
485486
return i, nil
487+
case uint8:
488+
return int64(i), nil
489+
case uint16:
490+
return int64(i), nil
491+
case uint32:
492+
return int64(i), nil
493+
case uint:
494+
return uintToInt64(uint64(i))
495+
case uint64:
496+
return uintToInt64(i)
486497
}
487498

488499
// Force it to a string and try to convert.
489-
f, err := strconv.ParseFloat(fmt.Sprintf("%v", v), 64)
500+
s := fmt.Sprintf("%v", v)
501+
502+
// Try parsing as int64 first, and on failure, attempt float64 parsing.
503+
// Parsing directly as float64, when the number is beyond its upper limit (2^53)
504+
// causes unnecessary precision loss when the value is actually an int.
505+
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
506+
return i, nil
507+
}
508+
509+
f, err := strconv.ParseFloat(s, 64)
490510
if err != nil {
491511
return 0, err
492512
}
493513

514+
// int64(f) is undefined for numbers that are out of range and returns different
515+
// results on different architectures. float64 cannot exactly represent MaxInt64,
516+
// so check against the ACTUAL upper limit of 2^63.
517+
// If neither match, return an error instead of the undefined result.
518+
if math.IsNaN(f) || f < math.MinInt64 || f >= 1<<63 {
519+
return 0, fmt.Errorf("value %v overflows int64", v)
520+
}
521+
494522
return int64(f), nil
495523
}
496524

525+
// uintToInt64 converts an unsigned integer to int64 and throw an error on overflow.
526+
func uintToInt64(v uint64) (int64, error) {
527+
if v > math.MaxInt64 {
528+
return 0, fmt.Errorf("value %d overflows int64", v)
529+
}
530+
531+
return int64(v), nil
532+
}
533+
497534
// toInt64 takes a `v any` value and if it is a float type,
498535
// converts and returns a `float64`. If it's any other type, forces it to a
499536
// string and attempts to get a float out using `strconv.ParseFloat`.

tests/koanf_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"log"
99
"maps"
10+
"math"
1011
"os"
1112
"path/filepath"
1213
"regexp"
@@ -2108,3 +2109,44 @@ func TestGetNilPointer(t *testing.T) {
21082109
_, ok = gotSlice.(*[]string)
21092110
assert.True(ok, "expected type *[]string, got %T", gotSlice)
21102111
}
2112+
2113+
// TesConvertInt64 tests various int64 conversion/overflow scenarios.
2114+
func TesConvertInt64(t *testing.T) {
2115+
assert := assert.New(t)
2116+
k := koanf.New(delim)
2117+
2118+
assert.Nil(k.Load(confmap.Provider(map[string]any{
2119+
// Good values.
2120+
"int64": int64(math.MaxInt64),
2121+
"uint64": uint64(math.MaxInt64),
2122+
"strmaxint": "9223372036854775807",
2123+
"strminint": "-9223372036854775808",
2124+
"str2p53": "9007199254740993",
2125+
"float": 1.9,
2126+
"minfloat": float64(math.MinInt64),
2127+
"strfloat": "3.7",
2128+
2129+
// Overflows.
2130+
"bigfloat": float64(math.MaxInt64),
2131+
"hugefloat": 1e300,
2132+
"neghuge": -1e300,
2133+
"inf": math.Inf(1),
2134+
"neginf": math.Inf(-1),
2135+
"nan": math.NaN(),
2136+
"biguint64": uint64(math.MaxInt64) + 1,
2137+
}, delim), nil))
2138+
2139+
assert.Equal(int64(math.MaxInt64), k.Int64("int64"))
2140+
assert.Equal(int64(math.MaxInt64), k.Int64("uint64"))
2141+
assert.Equal(int64(math.MaxInt64), k.Int64("strmaxint"))
2142+
assert.Equal(int64(math.MinInt64), k.Int64("strminint"))
2143+
assert.Equal(int64(9007199254740993), k.Int64("str2p53"))
2144+
assert.Equal(int64(1), k.Int64("float"))
2145+
assert.Equal(int64(math.MinInt64), k.Int64("minfloat"))
2146+
assert.Equal(int64(3), k.Int64("strfloat"))
2147+
2148+
// Out of range must return 0 and not a wrapped value.
2149+
for _, key := range []string{"bigfloat", "hugefloat", "neghuge", "inf", "neginf", "nan", "biguint64"} {
2150+
assert.Equal(int64(0), k.Int64(key), "Int64(%q) must be 0", key)
2151+
}
2152+
}

0 commit comments

Comments
 (0)