diff --git a/helper/helper.go b/helper/helper.go new file mode 100644 index 00000000..7047c8ae --- /dev/null +++ b/helper/helper.go @@ -0,0 +1,11 @@ +package helper + +import "github.com/redis/go-redis/v9/internal/util" + +func ParseFloat(s string) (float64, error) { + return util.ParseStringToFloat(s) +} + +func MustParseFloat(s string) float64 { + return util.MustParseFloat(s) +} diff --git a/internal/util/convert.go b/internal/util/convert.go new file mode 100644 index 00000000..d326d50d --- /dev/null +++ b/internal/util/convert.go @@ -0,0 +1,30 @@ +package util + +import ( + "fmt" + "math" + "strconv" +) + +// ParseFloat parses a Redis RESP3 float reply into a Go float64, +// handling "inf", "-inf", "nan" per Redis conventions. +func ParseStringToFloat(s string) (float64, error) { + switch s { + case "inf": + return math.Inf(1), nil + case "-inf": + return math.Inf(-1), nil + case "nan", "-nan": + return math.NaN(), nil + } + return strconv.ParseFloat(s, 64) +} + +// MustParseFloat is like ParseFloat but panics on parse errors. +func MustParseFloat(s string) float64 { + f, err := ParseStringToFloat(s) + if err != nil { + panic(fmt.Sprintf("redis: failed to parse float %q: %v", s, err)) + } + return f +} diff --git a/internal/util/convert_test.go b/internal/util/convert_test.go new file mode 100644 index 00000000..ffa3ee9f --- /dev/null +++ b/internal/util/convert_test.go @@ -0,0 +1,40 @@ +package util + +import ( + "math" + "testing" +) + +func TestParseStringToFloat(t *testing.T) { + tests := []struct { + in string + want float64 + ok bool + }{ + {"1.23", 1.23, true}, + {"inf", math.Inf(1), true}, + {"-inf", math.Inf(-1), true}, + {"nan", math.NaN(), true}, + {"oops", 0, false}, + } + + for _, tc := range tests { + got, err := ParseStringToFloat(tc.in) + if tc.ok { + if err != nil { + t.Fatalf("ParseFloat(%q) error: %v", tc.in, err) + } + if math.IsNaN(tc.want) { + if !math.IsNaN(got) { + t.Errorf("ParseFloat(%q) = %v; want NaN", tc.in, got) + } + } else if got != tc.want { + t.Errorf("ParseFloat(%q) = %v; want %v", tc.in, got, tc.want) + } + } else { + if err == nil { + t.Errorf("ParseFloat(%q) expected error, got nil", tc.in) + } + } + } +}