Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of ParseUfloat #1865

Merged
merged 5 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 9 additions & 52 deletions bytesconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io"
"math"
"net"
"strconv"
"sync"
Expand Down Expand Up @@ -172,61 +171,19 @@ func parseUintBuf(b []byte) (int, int, error) {
return v, n, nil
}

var (
errEmptyFloat = errors.New("empty float number")
errDuplicateFloatPoint = errors.New("duplicate point found in float number")
errUnexpectedFloatEnd = errors.New("unexpected end of float number")
errInvalidFloatExponent = errors.New("invalid float number exponent")
errUnexpectedFloatChar = errors.New("unexpected char found in float number")
)

// ParseUfloat parses unsigned float from buf.
func ParseUfloat(buf []byte) (float64, error) {
if len(buf) == 0 {
return -1, errEmptyFloat
// The implementation of parsing a float string is not easy.
// We believe that the conservative approach is to call strconv.ParseFloat.
// https://github.com/valyala/fasthttp/pull/1865
res, err := strconv.ParseFloat(b2s(buf), 64)
if res < 0 {
return -1, errors.New("negative input is invalid")
}
b := buf
var v uint64
offset := 1.0
var pointFound bool
for i, c := range b {
if c < '0' || c > '9' {
if c == '.' {
if pointFound {
return -1, errDuplicateFloatPoint
}
pointFound = true
continue
}
if c == 'e' || c == 'E' {
if i+1 >= len(b) {
return -1, errUnexpectedFloatEnd
}
b = b[i+1:]
minus := -1
switch b[0] {
case '+':
b = b[1:]
minus = 1
case '-':
b = b[1:]
default:
minus = 1
}
vv, err := ParseUint(b)
if err != nil {
return -1, errInvalidFloatExponent
}
return float64(v) * offset * math.Pow10(minus*vv), nil
}
return -1, errUnexpectedFloatChar
}
v = 10*v + uint64(c-'0')
if pointFound {
offset /= 10
}
if err != nil {
return -1, err
}
return float64(v) * offset, nil
return res, err
}

var (
Expand Down
10 changes: 10 additions & 0 deletions bytesconv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@ func TestParseUfloatSuccess(t *testing.T) {
testParseUfloatSuccess(t, "1234e2", 1234e2)
testParseUfloatSuccess(t, "1234E-5", 1234e-5)
testParseUfloatSuccess(t, "1.234e+3", 1.234e+3)
testParseUfloatSuccess(t, "1234e23", 1234e23)
testParseUfloatSuccess(t, "1.234e+32", 1.234e+32)
testParseUfloatSuccess(t, "123456789123456789.987654321", 123456789123456789.987654321)
testParseUfloatSuccess(t, "1.23456789123456789987654321", 1.23456789123456789987654321)
testParseUfloatSuccess(t, "340282346638528859811704183484516925440", 340282346638528859811704183484516925440)
testParseUfloatSuccess(t, "00000000000000000001", 1)
}

func TestParseUfloatError(t *testing.T) {
Expand Down Expand Up @@ -263,6 +269,10 @@ func TestParseUfloatError(t *testing.T) {

// missing exponent
testParseUfloatError(t, "123534e")

// negative number
testParseUfloatError(t, "-1")
testParseUfloatError(t, "-Inf")
}

func testParseUfloatError(t *testing.T, s string) {
Expand Down
24 changes: 24 additions & 0 deletions bytesconv_timing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,27 @@ func BenchmarkAppendUnquotedArgSlowPath(b *testing.B) {
}
})
}

func BenchmarkParseUfloat(b *testing.B) {
src := [][]byte{
[]byte("0"),
[]byte("1234566789."),
[]byte(".1234556778"),
[]byte("123.456"),
[]byte("123456789"),
[]byte("1234e23"),
[]byte("1234E-51"),
[]byte("1.234e+32"),
[]byte("123456789123456789.987654321"),
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := range src {
_, err := ParseUfloat(src[i])
if err != nil {
b.Fatalf("unexpected error: %v", err)
}
}
}
})
}