From 49585e13bc35791a63287879ae6dab10e03886f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sun, 13 Jul 2025 21:29:28 +0200 Subject: [PATCH 1/4] parse functions error instead of boolean return --- parse.go | 124 ++++++++++++++-------------- parse_test.go | 224 +++++++++++++++++++++++++------------------------- 2 files changed, 176 insertions(+), 172 deletions(-) diff --git a/parse.go b/parse.go index 6618725..cb84fdd 100644 --- a/parse.go +++ b/parse.go @@ -2,6 +2,7 @@ package utils import ( "math" + "strconv" ) const maxFracDigits = 16 @@ -16,69 +17,69 @@ type Unsigned interface { // ParseUint parses a decimal ASCII string or byte slice into a uint64. // It returns the parsed value and true on success. // If the input contains non-digit characters, it returns 0 and false. -func ParseUint[S byteSeq](s S) (uint64, bool) { - return parseUnsigned[S, uint64](s, uint64(math.MaxUint64)) +func ParseUint[S byteSeq](s S) (uint64, error) { + return parseUnsigned[S, uint64]("ParseUint", s, uint64(math.MaxUint64)) } // ParseInt parses a decimal ASCII string or byte slice into an int64. // Returns the parsed value and true on success, else 0 and false. -func ParseInt[S byteSeq](s S) (int64, bool) { - return parseSigned[S, int64](s, math.MinInt64, math.MaxInt64) +func ParseInt[S byteSeq](s S) (int64, error) { + return parseSigned[S, int64]("ParseInt", s, math.MinInt64, math.MaxInt64) } // ParseInt32 parses a decimal ASCII string or byte slice into an int32. -func ParseInt32[S byteSeq](s S) (int32, bool) { - return parseSigned[S, int32](s, math.MinInt32, math.MaxInt32) +func ParseInt32[S byteSeq](s S) (int32, error) { + return parseSigned[S, int32]("ParseInt", s, math.MinInt32, math.MaxInt32) } // ParseInt16 parses a decimal ASCII string or byte slice into an int16. -func ParseInt16[S byteSeq](s S) (int16, bool) { - return parseSigned[S, int16](s, math.MinInt16, math.MaxInt16) +func ParseInt16[S byteSeq](s S) (int16, error) { + return parseSigned[S, int16]("ParseInt", s, math.MinInt16, math.MaxInt16) } // ParseInt8 parses a decimal ASCII string or byte slice into an int8. -func ParseInt8[S byteSeq](s S) (int8, bool) { - return parseSigned[S, int8](s, math.MinInt8, math.MaxInt8) +func ParseInt8[S byteSeq](s S) (int8, error) { + return parseSigned[S, int8]("ParseInt", s, math.MinInt8, math.MaxInt8) } // ParseUint32 parses a decimal ASCII string or byte slice into a uint32. -func ParseUint32[S byteSeq](s S) (uint32, bool) { - return parseUnsigned[S, uint32](s, uint32(math.MaxUint32)) +func ParseUint32[S byteSeq](s S) (uint32, error) { + return parseUnsigned[S, uint32]("ParseUint", s, uint32(math.MaxUint32)) } // ParseUint16 parses a decimal ASCII string or byte slice into a uint16. -func ParseUint16[S byteSeq](s S) (uint16, bool) { - return parseUnsigned[S, uint16](s, uint16(math.MaxUint16)) +func ParseUint16[S byteSeq](s S) (uint16, error) { + return parseUnsigned[S, uint16]("ParseUint", s, uint16(math.MaxUint16)) } // ParseUint8 parses a decimal ASCII string or byte slice into a uint8. -func ParseUint8[S byteSeq](s S) (uint8, bool) { - return parseUnsigned[S, uint8](s, uint8(math.MaxUint8)) +func ParseUint8[S byteSeq](s S) (uint8, error) { + return parseUnsigned[S, uint8]("ParseUint", s, uint8(math.MaxUint8)) } -// parseDigits parses a sequence of digits and returns the uint64 value and success. -// Returns (0, false) if any non-digit is encountered or overflow happens. -func parseDigits[S byteSeq](s S, i int) (uint64, bool) { +// parseDigits parses a sequence of digits and returns the uint64 value. +// It returns an error if any non-digit is encountered or overflow happens. +func parseDigits[S byteSeq](s S, i int) (uint64, error) { var n uint64 for ; i < len(s); i++ { c := s[i] - '0' if c > 9 { - return 0, false + return 0, strconv.ErrSyntax } nn := n*10 + uint64(c) if nn < n { - return 0, false + return 0, strconv.ErrRange } n = nn } - return n, true + return n, nil } // parseSigned parses a decimal ASCII string or byte slice into a signed integer type T. -// It supports optional '+' or '-' prefix, checks for overflow and underflow, and returns (0, false) on error. -func parseSigned[S byteSeq, T Signed](s S, minRange, maxRange T) (T, bool) { +// It supports optional '+' or '-' prefix, checks for overflow and underflow, and returns (0, error) on error. +func parseSigned[S byteSeq, T Signed](fn string, s S, minRange, maxRange T) (T, error) { if len(s) == 0 { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } neg := false @@ -91,56 +92,59 @@ func parseSigned[S byteSeq, T Signed](s S, minRange, maxRange T) (T, bool) { i++ } if i == len(s) { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } // Parse digits - n, ok := parseDigits(s, i) - if !ok { - return 0, false + n, err := parseDigits(s, i) + if err != nil { + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: err} } if !neg { // Check for overflow if n > uint64(int64(maxRange)) { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrRange} } - return T(n), true + return T(n), nil } // Check for underflow minAbs := uint64(-int64(minRange)) if n > minAbs { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrRange} } - return T(-int64(n)), true + return T(-int64(n)), nil } // parseUnsigned parses a decimal ASCII string or byte slice into an unsigned integer type T. -// It does not support sign prefixes, checks for overflow, and returns (0, false) on error. -func parseUnsigned[S byteSeq, T Unsigned](s S, maxRange T) (T, bool) { +// It does not support sign prefixes, checks for overflow, and returns (0, error) on error. +func parseUnsigned[S byteSeq, T Unsigned](fn string, s S, maxRange T) (T, error) { if len(s) == 0 { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } i := 0 // Parse digits - n, ok := parseDigits(s, i) + n, err := parseDigits(s, i) // Check for overflow - if !ok || n > uint64(maxRange) { - return 0, false + if err != nil { + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: err} } - return T(n), true + if n > uint64(maxRange) { + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrRange} + } + return T(n), nil } // parseFloat parses a decimal ASCII string or byte slice into a float64. -// It supports optional sign, fractional part and exponent. It returns (0, false) +// It supports optional sign, fractional part and exponent. It returns (0, error) // on error or overflow. -func parseFloat[S byteSeq](s S) (float64, bool) { +func parseFloat[S byteSeq](fn string, s S) (float64, error) { if len(s) == 0 { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } i := 0 neg := false @@ -152,7 +156,7 @@ func parseFloat[S byteSeq](s S) (float64, bool) { i++ } if i == len(s) { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } var intPart uint64 @@ -163,7 +167,7 @@ func parseFloat[S byteSeq](s S) (float64, bool) { } nn := intPart*10 + uint64(c) if nn < intPart { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrRange} } intPart = nn i++ @@ -180,7 +184,7 @@ func parseFloat[S byteSeq](s S) (float64, bool) { break } if fracDigits >= maxFracDigits { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrRange} } fracPart = fracPart*10 + uint64(c) fracDiv *= 10 @@ -194,7 +198,7 @@ func parseFloat[S byteSeq](s S) (float64, bool) { if i < len(s) && (s[i] == 'e' || s[i] == 'E') { i++ if i == len(s) { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } switch s[i] { case '-': @@ -204,12 +208,12 @@ func parseFloat[S byteSeq](s S) (float64, bool) { i++ } if i == len(s) { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } for i < len(s) { c := s[i] - '0' if c > 9 { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } exp = exp*10 + int64(c) if !expSign && exp > 308 { @@ -223,7 +227,7 @@ func parseFloat[S byteSeq](s S) (float64, bool) { } if i != len(s) { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrSyntax} } if expSign { exp = -exp @@ -240,26 +244,26 @@ func parseFloat[S byteSeq](s S) (float64, bool) { f = -f } if math.IsInf(f, 0) || math.IsNaN(f) { - return 0, false + return 0, &strconv.NumError{Func: fn, Num: string(s), Err: strconv.ErrRange} } - return f, true + return f, nil } // ParseFloat64 parses a decimal ASCII string or byte slice into a float64. It // delegates the actual parsing to parseFloat. -func ParseFloat64[S byteSeq](s S) (float64, bool) { - return parseFloat[S](s) +func ParseFloat64[S byteSeq](s S) (float64, error) { + return parseFloat[S]("ParseFloat", s) } // ParseFloat32 parses a decimal ASCII string or byte slice into a float32. It // returns (0, false) on error or if the parsed value overflows float32. -func ParseFloat32[S byteSeq](s S) (float32, bool) { - f, ok := parseFloat[S](s) - if !ok { - return 0, false +func ParseFloat32[S byteSeq](s S) (float32, error) { + f, err := parseFloat[S]("ParseFloat", s) + if err != nil { + return 0, err } if f > math.MaxFloat32 || f < -math.MaxFloat32 { - return 0, false + return 0, &strconv.NumError{Func: "ParseFloat", Num: string(s), Err: strconv.ErrRange} } - return float32(f), true + return float32(f), nil } diff --git a/parse_test.go b/parse_test.go index 42023fe..406d291 100644 --- a/parse_test.go +++ b/parse_test.go @@ -26,14 +26,14 @@ func Test_ParseUint(t *testing.T) { {"12a", 0, false}, } for _, tt := range tests { - v, ok := ParseUint(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseUint(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - b, ok := ParseUint([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + b, err := ParseUint([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, b) } } @@ -42,8 +42,8 @@ func Test_ParseUint(t *testing.T) { func Test_ParseUint_Whitespace(t *testing.T) { t.Parallel() - v, ok := ParseUint(" 123") - require.False(t, ok) + v, err := ParseUint(" 123") + require.Error(t, err) require.Equal(t, uint64(0), v) } @@ -53,8 +53,8 @@ func Benchmark_ParseUint(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint(input) - if !ok { + _, err := ParseUint(input) + if err != nil { b.Fatal("failed to parse uint") } } @@ -62,8 +62,8 @@ func Benchmark_ParseUint(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint([]byte(input)) - if !ok { + _, err := ParseUint([]byte(input)) + if err != nil { b.Fatal("failed to parse uint from bytes") } } @@ -100,14 +100,14 @@ func Test_ParseInt(t *testing.T) { {"-", 0, false}, } for _, tt := range tests { - v, ok := ParseInt(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseInt(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - b, ok := ParseInt([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + b, err := ParseInt([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, b) } } @@ -118,11 +118,11 @@ func Test_ParseInt_SignOnly(t *testing.T) { tests := []string{"+", "-"} for _, in := range tests { - v, ok := ParseInt(in) - require.False(t, ok) + v, err := ParseInt(in) + require.Error(t, err) require.Equal(t, int64(0), v) - b, ok := ParseInt([]byte(in)) - require.False(t, ok) + b, err := ParseInt([]byte(in)) + require.Error(t, err) require.Equal(t, int64(0), b) } } @@ -130,8 +130,8 @@ func Test_ParseInt_SignOnly(t *testing.T) { func Test_ParseInt_Whitespace(t *testing.T) { t.Parallel() - v, ok := ParseInt(" 42") - require.False(t, ok) + v, err := ParseInt(" 42") + require.Error(t, err) require.Equal(t, int64(0), v) } @@ -139,11 +139,11 @@ func Test_ParseUnsigned_SignOnly(t *testing.T) { t.Parallel() in := "+" - v, ok := ParseUint(in) - require.False(t, ok) + v, err := ParseUint(in) + require.Error(t, err) require.Equal(t, uint64(0), v) - b, ok := ParseUint([]byte(in)) - require.False(t, ok) + b, err := ParseUint([]byte(in)) + require.Error(t, err) require.Equal(t, uint64(0), b) } @@ -153,8 +153,8 @@ func Benchmark_ParseInt(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt(input) - if !ok { + _, err := ParseInt(input) + if err != nil { b.Fatal("failed to parse int") } } @@ -162,8 +162,8 @@ func Benchmark_ParseInt(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt([]byte(input)) - if !ok { + _, err := ParseInt([]byte(input)) + if err != nil { b.Fatal("failed to parse int from bytes") } } @@ -194,14 +194,14 @@ func Test_ParseInt32(t *testing.T) { {"-2147483649", 0, false}, } for _, tt := range tests { - v, ok := ParseInt32(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseInt32(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - b, ok := ParseInt32([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + b, err := ParseInt32([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, b) } } @@ -213,8 +213,8 @@ func Benchmark_ParseInt32(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt32(input) - if !ok { + _, err := ParseInt32(input) + if err != nil { b.Fatal("failed to parse int32") } } @@ -222,8 +222,8 @@ func Benchmark_ParseInt32(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt32([]byte(input)) - if !ok { + _, err := ParseInt32([]byte(input)) + if err != nil { b.Fatal("failed to parse int32 from bytes") } } @@ -254,14 +254,14 @@ func Test_ParseInt16(t *testing.T) { {"-32769", 0, false}, } for _, tt := range tests { - v, ok := ParseInt16(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseInt16(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - bts, ok := ParseInt16([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + bts, err := ParseInt16([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, bts) } } @@ -273,8 +273,8 @@ func Benchmark_ParseInt16(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt16(input) - if !ok { + _, err := ParseInt16(input) + if err != nil { b.Fatal("failed to parse int16") } } @@ -282,8 +282,8 @@ func Benchmark_ParseInt16(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt16([]byte(input)) - if !ok { + _, err := ParseInt16([]byte(input)) + if err != nil { b.Fatal("failed to parse int16 from bytes") } } @@ -314,14 +314,14 @@ func Test_ParseInt8(t *testing.T) { {"-129", 0, false}, } for _, tt := range tests { - v, ok := ParseInt8(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseInt8(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - b, ok := ParseInt8([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + b, err := ParseInt8([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, b) } } @@ -333,8 +333,8 @@ func Benchmark_ParseInt8(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt8(input) - if !ok { + _, err := ParseInt8(input) + if err != nil { b.Fatal("failed to parse int8") } } @@ -342,8 +342,8 @@ func Benchmark_ParseInt8(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseInt8([]byte(input)) - if !ok { + _, err := ParseInt8([]byte(input)) + if err != nil { b.Fatal("failed to parse int8 from bytes") } } @@ -373,14 +373,14 @@ func Test_ParseUint32(t *testing.T) { {"-1", 0, false}, } for _, tt := range tests { - v, ok := ParseUint32(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseUint32(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - b, ok := ParseUint32([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + b, err := ParseUint32([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, b) } } @@ -392,8 +392,8 @@ func Benchmark_ParseUint32(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint32(input) - if !ok { + _, err := ParseUint32(input) + if err != nil { b.Fatal("failed to parse uint32") } } @@ -401,8 +401,8 @@ func Benchmark_ParseUint32(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint32([]byte(input)) - if !ok { + _, err := ParseUint32([]byte(input)) + if err != nil { b.Fatal("failed to parse uint32 from bytes") } } @@ -432,14 +432,14 @@ func Test_ParseUint16(t *testing.T) { {"-1", 0, false}, } for _, tt := range tests { - v, ok := ParseUint16(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseUint16(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - bts, ok := ParseUint16([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + bts, err := ParseUint16([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, bts) } } @@ -451,8 +451,8 @@ func Benchmark_ParseUint16(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint16(input) - if !ok { + _, err := ParseUint16(input) + if err != nil { b.Fatal("failed to parse uint16") } } @@ -460,8 +460,8 @@ func Benchmark_ParseUint16(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint16([]byte(input)) - if !ok { + _, err := ParseUint16([]byte(input)) + if err != nil { b.Fatal("failed to parse uint16 from bytes") } } @@ -491,14 +491,14 @@ func Test_ParseUint8(t *testing.T) { {"-1", 0, false}, } for _, tt := range tests { - v, ok := ParseUint8(tt.in) - require.Equal(t, tt.success, ok) - if ok { + v, err := ParseUint8(tt.in) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, v) } - b, ok := ParseUint8([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + b, err := ParseUint8([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { require.Equal(t, tt.val, b) } } @@ -510,8 +510,8 @@ func Benchmark_ParseUint8(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint8(input) - if !ok { + _, err := ParseUint8(input) + if err != nil { b.Fatal("failed to parse uint8") } } @@ -519,8 +519,8 @@ func Benchmark_ParseUint8(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseUint8([]byte(input)) - if !ok { + _, err := ParseUint8([]byte(input)) + if err != nil { b.Fatal("failed to parse uint8 from bytes") } } @@ -578,18 +578,18 @@ func Test_ParseFloat64(t *testing.T) { {"1.2.3", 0, false}, } for _, tt := range tests { - v, ok := ParseFloat64(tt.in) - require.Equal(t, tt.success, ok, "input: %s", tt.in) - if ok { + v, err := ParseFloat64(tt.in) + require.Equal(t, tt.success, err == nil, "input: %s", tt.in) + if err == nil { if tt.val == 0 { require.InDelta(t, tt.val, v, 1e-9, "input: %s", tt.in) } else { require.InEpsilon(t, tt.val, v, 1e-9, "input: %s", tt.in) } } - bts, ok := ParseFloat64([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + bts, err := ParseFloat64([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { if tt.val == 0 { require.InDelta(t, tt.val, bts, 1e-9, "input: %s", tt.in) } else { @@ -605,8 +605,8 @@ func Benchmark_ParseFloat64(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseFloat64(input) - if !ok { + _, err := ParseFloat64(input) + if err != nil { b.Fatal("failed to parse float") } } @@ -614,8 +614,8 @@ func Benchmark_ParseFloat64(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseFloat64([]byte(input)) - if !ok { + _, err := ParseFloat64([]byte(input)) + if err != nil { b.Fatal("failed to parse float from bytes") } } @@ -675,9 +675,9 @@ func Test_ParseFloat32(t *testing.T) { {"1.2.3", 0, false, false}, } for _, tt := range tests { - v, ok := ParseFloat32(tt.in) - require.Equal(t, tt.success, ok, "input: %s", tt.in) - if ok { + v, err := ParseFloat32(tt.in) + require.Equal(t, tt.success, err == nil, "input: %s", tt.in) + if err == nil { if tt.negzero { require.InDelta(t, float32(0), v, 1e-6, "input: %s", tt.in) require.True(t, math.Signbit(float64(v))) @@ -687,9 +687,9 @@ func Test_ParseFloat32(t *testing.T) { require.InEpsilon(t, tt.val, v, 1e-6, "input: %s", tt.in) } } - bts, ok := ParseFloat32([]byte(tt.in)) - require.Equal(t, tt.success, ok) - if ok { + bts, err := ParseFloat32([]byte(tt.in)) + require.Equal(t, tt.success, err == nil) + if err == nil { if tt.negzero { require.InDelta(t, float32(0), bts, 1e-6, "input: %s", tt.in) require.True(t, math.Signbit(float64(bts))) @@ -708,8 +708,8 @@ func Benchmark_ParseFloat32(b *testing.B) { b.Run("fiber", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseFloat32(input) - if !ok { + _, err := ParseFloat32(input) + if err != nil { b.Fatal("failed to parse float32") } } @@ -717,8 +717,8 @@ func Benchmark_ParseFloat32(b *testing.B) { b.Run("fiber_bytes", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { - _, ok := ParseFloat32([]byte(input)) - if !ok { + _, err := ParseFloat32([]byte(input)) + if err != nil { b.Fatal("failed to parse float32 from bytes") } } From bc50526842931215a16b81cfdad656f85099e702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sun, 13 Jul 2025 21:50:15 +0200 Subject: [PATCH 2/4] parse functions error instead of boolean return --- parse.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/parse.go b/parse.go index cb84fdd..349ab13 100644 --- a/parse.go +++ b/parse.go @@ -29,32 +29,32 @@ func ParseInt[S byteSeq](s S) (int64, error) { // ParseInt32 parses a decimal ASCII string or byte slice into an int32. func ParseInt32[S byteSeq](s S) (int32, error) { - return parseSigned[S, int32]("ParseInt", s, math.MinInt32, math.MaxInt32) + return parseSigned[S, int32]("ParseInt32", s, math.MinInt32, math.MaxInt32) } // ParseInt16 parses a decimal ASCII string or byte slice into an int16. func ParseInt16[S byteSeq](s S) (int16, error) { - return parseSigned[S, int16]("ParseInt", s, math.MinInt16, math.MaxInt16) + return parseSigned[S, int16]("ParseInt16", s, math.MinInt16, math.MaxInt16) } // ParseInt8 parses a decimal ASCII string or byte slice into an int8. func ParseInt8[S byteSeq](s S) (int8, error) { - return parseSigned[S, int8]("ParseInt", s, math.MinInt8, math.MaxInt8) + return parseSigned[S, int8]("ParseInt8", s, math.MinInt8, math.MaxInt8) } // ParseUint32 parses a decimal ASCII string or byte slice into a uint32. func ParseUint32[S byteSeq](s S) (uint32, error) { - return parseUnsigned[S, uint32]("ParseUint", s, uint32(math.MaxUint32)) + return parseUnsigned[S, uint32]("ParseUint32", s, uint32(math.MaxUint32)) } // ParseUint16 parses a decimal ASCII string or byte slice into a uint16. func ParseUint16[S byteSeq](s S) (uint16, error) { - return parseUnsigned[S, uint16]("ParseUint", s, uint16(math.MaxUint16)) + return parseUnsigned[S, uint16]("ParseUint16", s, uint16(math.MaxUint16)) } // ParseUint8 parses a decimal ASCII string or byte slice into a uint8. func ParseUint8[S byteSeq](s S) (uint8, error) { - return parseUnsigned[S, uint8]("ParseUint", s, uint8(math.MaxUint8)) + return parseUnsigned[S, uint8]("ParseUint8", s, uint8(math.MaxUint8)) } // parseDigits parses a sequence of digits and returns the uint64 value. @@ -252,18 +252,18 @@ func parseFloat[S byteSeq](fn string, s S) (float64, error) { // ParseFloat64 parses a decimal ASCII string or byte slice into a float64. It // delegates the actual parsing to parseFloat. func ParseFloat64[S byteSeq](s S) (float64, error) { - return parseFloat[S]("ParseFloat", s) + return parseFloat[S]("ParseFloat64", s) } // ParseFloat32 parses a decimal ASCII string or byte slice into a float32. It // returns (0, false) on error or if the parsed value overflows float32. func ParseFloat32[S byteSeq](s S) (float32, error) { - f, err := parseFloat[S]("ParseFloat", s) + f, err := parseFloat[S]("ParseFloat32", s) if err != nil { return 0, err } if f > math.MaxFloat32 || f < -math.MaxFloat32 { - return 0, &strconv.NumError{Func: "ParseFloat", Num: string(s), Err: strconv.ErrRange} + return 0, &strconv.NumError{Func: "ParseFloat32", Num: string(s), Err: strconv.ErrRange} } return float32(f), nil } From 862e7a044e6b23e5c5d1b283cbf07509de98ec15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sun, 13 Jul 2025 22:06:20 +0200 Subject: [PATCH 3/4] parse functions error instead of boolean return --- README.md | 124 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index afb6111..1cbf0d8 100644 --- a/README.md +++ b/README.md @@ -135,61 +135,75 @@ Benchmark_CalculateTimestamp/fiber-12 1000000000 0.2935 ns/op Benchmark_CalculateTimestamp/default-12 15740576 73.79 ns/op 0 B/op 0 allocs/op Benchmark_CalculateTimestamp/default-12 15789036 71.12 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint/fiber-12 190390941 6.292 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint/fiber-12 187968758 6.400 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint/fiber_bytes-12 181957326 6.809 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint/fiber_bytes-12 182275550 6.558 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint/default-12 88281543 13.52 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint/default-12 88967146 13.41 ns/op 0 B/op 0 allocs/op - -Benchmark_ParseInt/fiber-12 181353142 6.723 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt/fiber-12 180631305 6.578 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt/fiber_bytes-12 175220041 6.892 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt/fiber_bytes-12 171838354 7.020 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt/default-12 76055068 15.77 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt/default-12 75963992 15.55 ns/op 0 B/op 0 allocs/op - -Benchmark_ParseInt32/fiber-12 179962680 6.631 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt32/fiber-12 181285437 6.570 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt32/fiber_bytes-12 173786900 6.901 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt32/fiber_bytes-12 171283489 7.069 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt32/default-12 69845103 15.75 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt32/default-12 76438194 15.66 ns/op 0 B/op 0 allocs/op - -Benchmark_ParseInt8/fiber-12 286492362 4.148 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt8/fiber-12 282957276 4.147 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt8/fiber_bytes-12 270179119 4.481 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt8/fiber_bytes-12 258238294 4.522 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt8/default-12 135063286 8.831 ns/op 0 B/op 0 allocs/op -Benchmark_ParseInt8/default-12 140703313 8.528 ns/op 0 B/op 0 allocs/op - -Benchmark_ParseUint32/fiber-12 184411585 6.568 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint32/fiber-12 184338627 6.543 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint32/fiber_bytes-12 178475793 6.759 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint32/fiber_bytes-12 178517788 7.052 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint32/default-12 83775481 13.41 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint32/default-12 88117585 13.51 ns/op 0 B/op 0 allocs/op - -Benchmark_ParseUint8/fiber-12 401799110 3.046 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint8/fiber-12 380578648 3.036 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint8/fiber_bytes-12 363442573 3.344 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint8/fiber_bytes-12 357869246 3.346 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint8/default-12 184238403 6.788 ns/op 0 B/op 0 allocs/op -Benchmark_ParseUint8/default-12 186525054 6.454 ns/op 0 B/op 0 allocs/op - -Benchmark_ParseFloat64/fiber-12 130381844 9.355 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat64/fiber-12 129132879 9.170 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat64/fiber_bytes-12 125142489 9.786 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat64/fiber_bytes-12 125434107 9.617 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat64/default-12 49301054 24.17 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat64/default-12 48115717 24.16 ns/op 0 B/op 0 allocs/op - -Benchmark_ParseFloat32/fiber-12 100000000 10.11 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat32/fiber-12 100000000 10.14 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat32/fiber_bytes-12 100000000 10.44 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat32/fiber_bytes-12 100000000 10.61 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat32/default-12 46581891 25.97 ns/op 0 B/op 0 allocs/op -Benchmark_ParseFloat32/default-12 44455900 26.94 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint/fiber-12 172685805 6.949 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint/fiber-12 172395474 6.984 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint/fiber_bytes-12 167209564 7.162 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint/fiber_bytes-12 161316883 7.248 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint/default-12 89826800 13.52 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint/default-12 89406565 13.68 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseInt/fiber-12 158532987 7.442 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt/fiber-12 154777971 7.710 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt/fiber_bytes-12 157400030 7.453 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt/fiber_bytes-12 148624418 7.400 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt/default-12 78927678 15.97 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt/default-12 78261080 16.26 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseInt32/fiber-12 164397682 7.234 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt32/fiber-12 164329150 7.319 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt32/fiber_bytes-12 159049194 7.556 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt32/fiber_bytes-12 155705494 7.697 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt32/default-12 71818300 16.92 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt32/default-12 78039262 15.83 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseInt16/fiber-12 232986352 5.165 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt16/fiber-12 232214074 5.256 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt16/fiber_bytes-12 220412829 5.398 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt16/fiber_bytes-12 222333234 5.409 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt16/default-12 100000000 11.07 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt16/default-12 100000000 11.08 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseInt8/fiber-12 260329051 4.543 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt8/fiber-12 265292354 4.541 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt8/fiber_bytes-12 260297640 4.635 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt8/fiber_bytes-12 260662333 4.669 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt8/default-12 134202080 8.700 ns/op 0 B/op 0 allocs/op +Benchmark_ParseInt8/default-12 137497462 8.702 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseUint32/fiber-12 166919528 6.991 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint32/fiber-12 172230549 7.004 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint32/fiber_bytes-12 168104906 7.182 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint32/fiber_bytes-12 166743417 7.189 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint32/default-12 88639659 13.70 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint32/default-12 78153198 13.74 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseUint16/fiber-12 265107002 4.425 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint16/fiber-12 265636831 4.517 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint16/fiber_bytes-12 255349777 4.674 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint16/fiber_bytes-12 250084923 4.722 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint16/default-12 133589893 9.006 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint16/default-12 136365088 8.863 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseUint8/fiber-12 326680580 3.719 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint8/fiber-12 313552454 3.739 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint8/fiber_bytes-12 325318082 3.697 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint8/fiber_bytes-12 321770954 3.699 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint8/default-12 182636923 6.678 ns/op 0 B/op 0 allocs/op +Benchmark_ParseUint8/default-12 184060842 6.756 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseFloat64/fiber-12 100000000 11.11 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat64/fiber-12 100000000 11.09 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat64/fiber_bytes-12 127174759 9.525 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat64/fiber_bytes-12 124214686 9.577 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat64/default-12 50066755 24.16 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat64/default-12 49396011 24.80 ns/op 0 B/op 0 allocs/op + +Benchmark_ParseFloat32/fiber-12 100000000 11.71 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat32/fiber-12 100000000 11.85 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat32/fiber_bytes-12 100000000 11.17 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat32/fiber_bytes-12 100000000 10.43 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat32/default-12 46055755 25.65 ns/op 0 B/op 0 allocs/op +Benchmark_ParseFloat32/default-12 45263090 25.78 ns/op 0 B/op 0 allocs/op ``` See all the benchmarks under From 0df52a08919755769d8cfaa8bc3c163d91e21237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sun, 13 Jul 2025 22:08:34 +0200 Subject: [PATCH 4/4] parse functions error instead of boolean return --- parse.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/parse.go b/parse.go index 349ab13..5a3c96c 100644 --- a/parse.go +++ b/parse.go @@ -15,14 +15,14 @@ type Unsigned interface { } // ParseUint parses a decimal ASCII string or byte slice into a uint64. -// It returns the parsed value and true on success. -// If the input contains non-digit characters, it returns 0 and false. +// It returns the parsed value and nil on success. +// If the input contains non-digit characters, it returns 0 and an error. func ParseUint[S byteSeq](s S) (uint64, error) { return parseUnsigned[S, uint64]("ParseUint", s, uint64(math.MaxUint64)) } // ParseInt parses a decimal ASCII string or byte slice into an int64. -// Returns the parsed value and true on success, else 0 and false. +// Returns the parsed value and nil on success, else 0 and an error. func ParseInt[S byteSeq](s S) (int64, error) { return parseSigned[S, int64]("ParseInt", s, math.MinInt64, math.MaxInt64) }