diff --git a/fastfloat/parse.go b/fastfloat/parse.go index 5d4a7c7..b37838d 100644 --- a/fastfloat/parse.go +++ b/fastfloat/parse.go @@ -213,6 +213,12 @@ func ParseBestEffort(s string) float64 { } } + // the integer part might be elided to remain compliant + // with https://go.dev/ref/spec#Floating-point_literals + if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') { + return 0 + } + d := uint64(0) j := i for i < uint(len(s)) { @@ -232,7 +238,7 @@ func ParseBestEffort(s string) float64 { } break } - if i <= j { + if i <= j && s[i] != '.' { s = s[i:] if strings.HasPrefix(s, "+") { s = s[1:] @@ -263,7 +269,9 @@ func ParseBestEffort(s string) float64 { // Parse fractional part. i++ if i >= uint(len(s)) { - return 0 + // the fractional part may be elided to remain compliant + // with https://go.dev/ref/spec#Floating-point_literals + return f } k := i for i < uint(len(s)) { @@ -363,6 +371,12 @@ func Parse(s string) (float64, error) { } } + // the integer part might be elided to remain compliant + // with https://go.dev/ref/spec#Floating-point_literals + if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') { + return 0, fmt.Errorf("missing integer and fractional part in %q", s) + } + d := uint64(0) j := i for i < uint(len(s)) { @@ -382,7 +396,7 @@ func Parse(s string) (float64, error) { } break } - if i <= j { + if i <= j && s[i] != '.' { ss := s[i:] if strings.HasPrefix(ss, "+") { ss = ss[1:] @@ -413,7 +427,9 @@ func Parse(s string) (float64, error) { // Parse fractional part. i++ if i >= uint(len(s)) { - return 0, fmt.Errorf("cannot parse fractional part in %q", s) + // the fractional part might be elided to remain compliant + // with https://go.dev/ref/spec#Floating-point_literals + return f, nil } k := i for i < uint(len(s)) { diff --git a/fastfloat/parse_test.go b/fastfloat/parse_test.go index c7930a7..e4dfe2f 100644 --- a/fastfloat/parse_test.go +++ b/fastfloat/parse_test.go @@ -206,6 +206,7 @@ func TestParseBestEffort(t *testing.T) { f("-", 0) f("--", 0) f("-.", 0) + f(".", 0) f("-.e", 0) f("+112", 0) f("++", 0) @@ -214,7 +215,6 @@ func TestParseBestEffort(t *testing.T) { f("-e12", 0) f(".", 0) f("..34", 0) - f("-.32", 0) f("-.e3", 0) f(".e+3", 0) @@ -224,7 +224,6 @@ func TestParseBestEffort(t *testing.T) { f("12.34.56", 0) f("13e34.56", 0) f("12.34e56e4", 0) - f("12.", 0) f("123..45", 0) f("123ee34", 0) f("123e", 0) @@ -262,6 +261,9 @@ func TestParseBestEffort(t *testing.T) { f("-0.1", -0.1) f("-0.123", -0.123) f("1.66", 1.66) + f("12.", 12) + f(".12", 0.12) + f("-.12", -0.12) f("12345.12345678901", 12345.12345678901) f("12345.123456789012", 12345.123456789012) f("12345.1234567890123", 12345.1234567890123) @@ -338,6 +340,7 @@ func TestParseFailure(t *testing.T) { f(" bar ") f("-") f("--") + f(".") f("-.") f("-.e") f("+112") @@ -347,7 +350,6 @@ func TestParseFailure(t *testing.T) { f("-e12") f(".") f("..34") - f("-.32") f("-.e3") f(".e+3") @@ -357,7 +359,6 @@ func TestParseFailure(t *testing.T) { f("12.34.56") f("13e34.56") f("12.34e56e4") - f("12.") f("123..45") f("123ee34") f("123e") @@ -413,6 +414,9 @@ func TestParseSuccess(t *testing.T) { f("-0.1", -0.1) f("-0.123", -0.123) f("1.66", 1.66) + f("12.", 12) + f(".12", 0.12) + f("-.12", -0.12) f("12345.12345678901", 12345.12345678901) f("12345.123456789012", 12345.123456789012) f("12345.1234567890123", 12345.1234567890123) @@ -447,6 +451,8 @@ func TestParseSuccess(t *testing.T) { f("-123.456E-10", -123.456e-10) f("1.e4", 1.e4) f("-1.E-10", -1.e-10) + f(".1e3", 100) + f("-.12e3", -120) // inf and nan f("12345678909123456789012e45678", math.Inf(1)) diff --git a/parser_test.go b/parser_test.go index 0b6931c..fb6a8b3 100644 --- a/parser_test.go +++ b/parser_test.go @@ -35,6 +35,9 @@ func TestParseRawNumber(t *testing.T) { f("-12.345E+67 tail", "-12.345E+67", " tail") f("-12.345E-67,tail", "-12.345E-67", ",tail") f("-1234567.8e+90tail", "-1234567.8e+90", "tail") + f("12.tail", "12.", "tail") + f(".2tail", ".2", "tail") + f("-.2tail", "-.2", "tail") f("NaN", "NaN", "") f("nantail", "nan", "tail") f("inf", "inf", "")