mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-26 20:30:10 +01:00
c90752a8be
This should properly parse floating-point numbers with missing integer or fractional parts. For example, 123. or .123 Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3544
516 lines
11 KiB
Go
516 lines
11 KiB
Go
package fastfloat
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ParseUint64BestEffort parses uint64 number s.
|
|
//
|
|
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
|
|
//
|
|
// 0 is returned if the number cannot be parsed.
|
|
// See also ParseUint64, which returns parse error if the number cannot be parsed.
|
|
func ParseUint64BestEffort(s string) uint64 {
|
|
if len(s) == 0 {
|
|
return 0
|
|
}
|
|
i := uint(0)
|
|
d := uint64(0)
|
|
j := i
|
|
for i < uint(len(s)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + uint64(s[i]-'0')
|
|
i++
|
|
if i > 18 {
|
|
// The integer part may be out of range for uint64.
|
|
// Fall back to slow parsing.
|
|
dd, err := strconv.ParseUint(s, 10, 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return dd
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j {
|
|
return 0
|
|
}
|
|
if i < uint(len(s)) {
|
|
// Unparsed tail left.
|
|
return 0
|
|
}
|
|
return d
|
|
}
|
|
|
|
// ParseUint64 parses uint64 from s.
|
|
//
|
|
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
|
|
//
|
|
// See also ParseUint64BestEffort.
|
|
func ParseUint64(s string) (uint64, error) {
|
|
if len(s) == 0 {
|
|
return 0, fmt.Errorf("cannot parse uint64 from empty string")
|
|
}
|
|
i := uint(0)
|
|
d := uint64(0)
|
|
j := i
|
|
for i < uint(len(s)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + uint64(s[i]-'0')
|
|
i++
|
|
if i > 18 {
|
|
// The integer part may be out of range for uint64.
|
|
// Fall back to slow parsing.
|
|
dd, err := strconv.ParseUint(s, 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return dd, nil
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j {
|
|
return 0, fmt.Errorf("cannot parse uint64 from %q", s)
|
|
}
|
|
if i < uint(len(s)) {
|
|
// Unparsed tail left.
|
|
return 0, fmt.Errorf("unparsed tail left after parsing uint64 from %q: %q", s, s[i:])
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// ParseInt64BestEffort parses int64 number s.
|
|
//
|
|
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
|
|
//
|
|
// 0 is returned if the number cannot be parsed.
|
|
// See also ParseInt64, which returns parse error if the number cannot be parsed.
|
|
func ParseInt64BestEffort(s string) int64 {
|
|
if len(s) == 0 {
|
|
return 0
|
|
}
|
|
i := uint(0)
|
|
minus := s[0] == '-'
|
|
if minus {
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
d := int64(0)
|
|
j := i
|
|
for i < uint(len(s)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + int64(s[i]-'0')
|
|
i++
|
|
if i > 18 {
|
|
// The integer part may be out of range for int64.
|
|
// Fall back to slow parsing.
|
|
dd, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return dd
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j {
|
|
return 0
|
|
}
|
|
if i < uint(len(s)) {
|
|
// Unparsed tail left.
|
|
return 0
|
|
}
|
|
if minus {
|
|
d = -d
|
|
}
|
|
return d
|
|
}
|
|
|
|
// ParseInt64 parses int64 number s.
|
|
//
|
|
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
|
|
//
|
|
// See also ParseInt64BestEffort.
|
|
func ParseInt64(s string) (int64, error) {
|
|
if len(s) == 0 {
|
|
return 0, fmt.Errorf("cannot parse int64 from empty string")
|
|
}
|
|
i := uint(0)
|
|
minus := s[0] == '-'
|
|
if minus {
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0, fmt.Errorf("cannot parse int64 from %q", s)
|
|
}
|
|
}
|
|
|
|
d := int64(0)
|
|
j := i
|
|
for i < uint(len(s)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + int64(s[i]-'0')
|
|
i++
|
|
if i > 18 {
|
|
// The integer part may be out of range for int64.
|
|
// Fall back to slow parsing.
|
|
dd, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return dd, nil
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j {
|
|
return 0, fmt.Errorf("cannot parse int64 from %q", s)
|
|
}
|
|
if i < uint(len(s)) {
|
|
// Unparsed tail left.
|
|
return 0, fmt.Errorf("unparsed tail left after parsing int64 form %q: %q", s, s[i:])
|
|
}
|
|
if minus {
|
|
d = -d
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// Exact powers of 10.
|
|
//
|
|
// This works faster than math.Pow10, since it avoids additional multiplication.
|
|
var float64pow10 = [...]float64{
|
|
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
|
|
}
|
|
|
|
// ParseBestEffort parses floating-point number s.
|
|
//
|
|
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
|
|
//
|
|
// 0 is returned if the number cannot be parsed.
|
|
// See also Parse, which returns parse error if the number cannot be parsed.
|
|
func ParseBestEffort(s string) float64 {
|
|
if len(s) == 0 {
|
|
return 0
|
|
}
|
|
i := uint(0)
|
|
minus := s[0] == '-'
|
|
if minus {
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// 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)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + uint64(s[i]-'0')
|
|
i++
|
|
if i > 18 {
|
|
// The integer part may be out of range for uint64.
|
|
// Fall back to slow parsing.
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil && !math.IsInf(f, 0) {
|
|
return 0
|
|
}
|
|
return f
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j && s[i] != '.' {
|
|
s = s[i:]
|
|
if strings.HasPrefix(s, "+") {
|
|
s = s[1:]
|
|
}
|
|
// "infinity" is needed for OpenMetrics support.
|
|
// See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md
|
|
if strings.EqualFold(s, "inf") || strings.EqualFold(s, "infinity") {
|
|
if minus {
|
|
return -inf
|
|
}
|
|
return inf
|
|
}
|
|
if strings.EqualFold(s, "nan") {
|
|
return nan
|
|
}
|
|
return 0
|
|
}
|
|
f := float64(d)
|
|
if i >= uint(len(s)) {
|
|
// Fast path - just integer.
|
|
if minus {
|
|
f = -f
|
|
}
|
|
return f
|
|
}
|
|
|
|
if s[i] == '.' {
|
|
// Parse fractional part.
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
// 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)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + uint64(s[i]-'0')
|
|
i++
|
|
if i-j >= uint(len(float64pow10)) {
|
|
// The mantissa is out of range. Fall back to standard parsing.
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil && !math.IsInf(f, 0) {
|
|
return 0
|
|
}
|
|
return f
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i < k {
|
|
return 0
|
|
}
|
|
// Convert the entire mantissa to a float at once to avoid rounding errors.
|
|
f = float64(d) / float64pow10[i-k]
|
|
if i >= uint(len(s)) {
|
|
// Fast path - parsed fractional number.
|
|
if minus {
|
|
f = -f
|
|
}
|
|
return f
|
|
}
|
|
}
|
|
if s[i] == 'e' || s[i] == 'E' {
|
|
// Parse exponent part.
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0
|
|
}
|
|
expMinus := false
|
|
if s[i] == '+' || s[i] == '-' {
|
|
expMinus = s[i] == '-'
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0
|
|
}
|
|
}
|
|
exp := int16(0)
|
|
j := i
|
|
for i < uint(len(s)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
exp = exp*10 + int16(s[i]-'0')
|
|
i++
|
|
if exp > 300 {
|
|
// The exponent may be too big for float64.
|
|
// Fall back to standard parsing.
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil && !math.IsInf(f, 0) {
|
|
return 0
|
|
}
|
|
return f
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j {
|
|
return 0
|
|
}
|
|
if expMinus {
|
|
exp = -exp
|
|
}
|
|
f *= math.Pow10(int(exp))
|
|
if i >= uint(len(s)) {
|
|
if minus {
|
|
f = -f
|
|
}
|
|
return f
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Parse parses floating-point number s.
|
|
//
|
|
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
|
|
//
|
|
// See also ParseBestEffort.
|
|
func Parse(s string) (float64, error) {
|
|
if len(s) == 0 {
|
|
return 0, fmt.Errorf("cannot parse float64 from empty string")
|
|
}
|
|
i := uint(0)
|
|
minus := s[0] == '-'
|
|
if minus {
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0, fmt.Errorf("cannot parse float64 from %q", s)
|
|
}
|
|
}
|
|
|
|
// 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)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + uint64(s[i]-'0')
|
|
i++
|
|
if i > 18 {
|
|
// The integer part may be out of range for uint64.
|
|
// Fall back to slow parsing.
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil && !math.IsInf(f, 0) {
|
|
return 0, err
|
|
}
|
|
return f, nil
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j && s[i] != '.' {
|
|
ss := s[i:]
|
|
if strings.HasPrefix(ss, "+") {
|
|
ss = ss[1:]
|
|
}
|
|
// "infinity" is needed for OpenMetrics support.
|
|
// See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md
|
|
if strings.EqualFold(ss, "inf") || strings.EqualFold(ss, "infinity") {
|
|
if minus {
|
|
return -inf, nil
|
|
}
|
|
return inf, nil
|
|
}
|
|
if strings.EqualFold(ss, "nan") {
|
|
return nan, nil
|
|
}
|
|
return 0, fmt.Errorf("unparsed tail left after parsing float64 from %q: %q", s, ss)
|
|
}
|
|
f := float64(d)
|
|
if i >= uint(len(s)) {
|
|
// Fast path - just integer.
|
|
if minus {
|
|
f = -f
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
if s[i] == '.' {
|
|
// Parse fractional part.
|
|
i++
|
|
if i >= uint(len(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)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
d = d*10 + uint64(s[i]-'0')
|
|
i++
|
|
if i-j >= uint(len(float64pow10)) {
|
|
// The mantissa is out of range. Fall back to standard parsing.
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil && !math.IsInf(f, 0) {
|
|
return 0, fmt.Errorf("cannot parse mantissa in %q: %s", s, err)
|
|
}
|
|
return f, nil
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i < k {
|
|
return 0, fmt.Errorf("cannot find mantissa in %q", s)
|
|
}
|
|
// Convert the entire mantissa to a float at once to avoid rounding errors.
|
|
f = float64(d) / float64pow10[i-k]
|
|
if i >= uint(len(s)) {
|
|
// Fast path - parsed fractional number.
|
|
if minus {
|
|
f = -f
|
|
}
|
|
return f, nil
|
|
}
|
|
}
|
|
if s[i] == 'e' || s[i] == 'E' {
|
|
// Parse exponent part.
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0, fmt.Errorf("cannot parse exponent in %q", s)
|
|
}
|
|
expMinus := false
|
|
if s[i] == '+' || s[i] == '-' {
|
|
expMinus = s[i] == '-'
|
|
i++
|
|
if i >= uint(len(s)) {
|
|
return 0, fmt.Errorf("cannot parse exponent in %q", s)
|
|
}
|
|
}
|
|
exp := int16(0)
|
|
j := i
|
|
for i < uint(len(s)) {
|
|
if s[i] >= '0' && s[i] <= '9' {
|
|
exp = exp*10 + int16(s[i]-'0')
|
|
i++
|
|
if exp > 300 {
|
|
// The exponent may be too big for float64.
|
|
// Fall back to standard parsing.
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil && !math.IsInf(f, 0) {
|
|
return 0, fmt.Errorf("cannot parse exponent in %q: %s", s, err)
|
|
}
|
|
return f, nil
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if i <= j {
|
|
return 0, fmt.Errorf("cannot parse exponent in %q", s)
|
|
}
|
|
if expMinus {
|
|
exp = -exp
|
|
}
|
|
f *= math.Pow10(int(exp))
|
|
if i >= uint(len(s)) {
|
|
if minus {
|
|
f = -f
|
|
}
|
|
return f, nil
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("cannot parse float64 from %q", s)
|
|
}
|
|
|
|
var inf = math.Inf(1)
|
|
var nan = math.NaN()
|