Skip to content

Commit bd9a6f1

Browse files
committed
fastfloat: add Parse(), ParseUint64() and ParseInt64() functions, which return parse errors
1 parent 62557e7 commit bd9a6f1

File tree

3 files changed

+683
-2
lines changed

3 files changed

+683
-2
lines changed

fastfloat/parse.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fastfloat
22

33
import (
4+
"fmt"
45
"math"
56
"strconv"
67
"strings"
@@ -11,6 +12,7 @@ import (
1112
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
1213
//
1314
// 0 is returned if the number cannot be parsed.
15+
// See also ParseUint64, which returns parse error if the number cannot be parsed.
1416
func ParseUint64BestEffort(s string) uint64 {
1517
if len(s) == 0 {
1618
return 0
@@ -45,11 +47,51 @@ func ParseUint64BestEffort(s string) uint64 {
4547
return d
4648
}
4749

50+
// ParseUint64 parses uint64 from s.
51+
//
52+
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
53+
//
54+
// See also ParseUint64BestEffort.
55+
func ParseUint64(s string) (uint64, error) {
56+
if len(s) == 0 {
57+
return 0, fmt.Errorf("cannot parse uint64 from empty string")
58+
}
59+
i := uint(0)
60+
d := uint64(0)
61+
j := i
62+
for i < uint(len(s)) {
63+
if s[i] >= '0' && s[i] <= '9' {
64+
d = d*10 + uint64(s[i]-'0')
65+
i++
66+
if i > 18 {
67+
// The integer part may be out of range for uint64.
68+
// Fall back to slow parsing.
69+
dd, err := strconv.ParseUint(s, 10, 64)
70+
if err != nil {
71+
return 0, err
72+
}
73+
return dd, nil
74+
}
75+
continue
76+
}
77+
break
78+
}
79+
if i <= j {
80+
return 0, fmt.Errorf("cannot parse uint64 from %q", s)
81+
}
82+
if i < uint(len(s)) {
83+
// Unparsed tail left.
84+
return 0, fmt.Errorf("unparsed tail left after parsing uint64 from %q: %q", s, s[i:])
85+
}
86+
return d, nil
87+
}
88+
4889
// ParseInt64BestEffort parses int64 number s.
4990
//
5091
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
5192
//
5293
// 0 is returned if the number cannot be parsed.
94+
// See also ParseInt64, which returns parse error if the number cannot be parsed.
5395
func ParseInt64BestEffort(s string) int64 {
5496
if len(s) == 0 {
5597
return 0
@@ -95,6 +137,56 @@ func ParseInt64BestEffort(s string) int64 {
95137
return d
96138
}
97139

140+
// ParseInt64 parses int64 number s.
141+
//
142+
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
143+
//
144+
// See also ParseInt64BestEffort.
145+
func ParseInt64(s string) (int64, error) {
146+
if len(s) == 0 {
147+
return 0, fmt.Errorf("cannot parse int64 from empty string")
148+
}
149+
i := uint(0)
150+
minus := s[0] == '-'
151+
if minus {
152+
i++
153+
if i >= uint(len(s)) {
154+
return 0, fmt.Errorf("cannot parse int64 from %q", s)
155+
}
156+
}
157+
158+
d := int64(0)
159+
j := i
160+
for i < uint(len(s)) {
161+
if s[i] >= '0' && s[i] <= '9' {
162+
d = d*10 + int64(s[i]-'0')
163+
i++
164+
if i > 18 {
165+
// The integer part may be out of range for int64.
166+
// Fall back to slow parsing.
167+
dd, err := strconv.ParseInt(s, 10, 64)
168+
if err != nil {
169+
return 0, err
170+
}
171+
return dd, nil
172+
}
173+
continue
174+
}
175+
break
176+
}
177+
if i <= j {
178+
return 0, fmt.Errorf("cannot parse int64 from %q", s)
179+
}
180+
if i < uint(len(s)) {
181+
// Unparsed tail left.
182+
return 0, fmt.Errorf("unparsed tail left after parsing int64 form %q: %q", s, s[i:])
183+
}
184+
if minus {
185+
d = -d
186+
}
187+
return d, nil
188+
}
189+
98190
// Exact powers of 10.
99191
//
100192
// This works faster than math.Pow10, since it avoids additional multiplication.
@@ -107,6 +199,7 @@ var float64pow10 = [...]float64{
107199
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
108200
//
109201
// 0 is returned if the number cannot be parsed.
202+
// See also Parse, which returns parse error if the number cannot be parsed.
110203
func ParseBestEffort(s string) float64 {
111204
if len(s) == 0 {
112205
return 0
@@ -250,5 +343,153 @@ func ParseBestEffort(s string) float64 {
250343
return 0
251344
}
252345

346+
// Parse parses floating-point number s.
347+
//
348+
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
349+
//
350+
// See also ParseBestEffort.
351+
func Parse(s string) (float64, error) {
352+
if len(s) == 0 {
353+
return 0, fmt.Errorf("cannot parse float64 from empty string")
354+
}
355+
i := uint(0)
356+
minus := s[0] == '-'
357+
if minus {
358+
i++
359+
if i >= uint(len(s)) {
360+
return 0, fmt.Errorf("cannot parse float64 from %q", s)
361+
}
362+
}
363+
364+
d := uint64(0)
365+
j := i
366+
for i < uint(len(s)) {
367+
if s[i] >= '0' && s[i] <= '9' {
368+
d = d*10 + uint64(s[i]-'0')
369+
i++
370+
if i > 18 {
371+
// The integer part may be out of range for uint64.
372+
// Fall back to slow parsing.
373+
f, err := strconv.ParseFloat(s, 64)
374+
if err != nil && !math.IsInf(f, 0) {
375+
return 0, err
376+
}
377+
return f, nil
378+
}
379+
continue
380+
}
381+
break
382+
}
383+
if i <= j {
384+
ss := s[i:]
385+
if strings.HasPrefix(ss, "+") {
386+
ss = ss[1:]
387+
}
388+
if strings.EqualFold(ss, "inf") {
389+
if minus {
390+
return -inf, nil
391+
}
392+
return inf, nil
393+
}
394+
if strings.EqualFold(ss, "nan") {
395+
return nan, nil
396+
}
397+
return 0, fmt.Errorf("unparsed tail left after parsing float64 from %q: %q", s, ss)
398+
}
399+
f := float64(d)
400+
if i >= uint(len(s)) {
401+
// Fast path - just integer.
402+
if minus {
403+
f = -f
404+
}
405+
return f, nil
406+
}
407+
408+
if s[i] == '.' {
409+
// Parse fractional part.
410+
i++
411+
if i >= uint(len(s)) {
412+
return 0, fmt.Errorf("cannot parse fractional part in %q", s)
413+
}
414+
k := i
415+
for i < uint(len(s)) {
416+
if s[i] >= '0' && s[i] <= '9' {
417+
d = d*10 + uint64(s[i]-'0')
418+
i++
419+
if i-j >= uint(len(float64pow10)) {
420+
// The mantissa is out of range. Fall back to standard parsing.
421+
f, err := strconv.ParseFloat(s, 64)
422+
if err != nil && !math.IsInf(f, 0) {
423+
return 0, fmt.Errorf("cannot parse mantissa in %q: %s", s, err)
424+
}
425+
return f, nil
426+
}
427+
continue
428+
}
429+
break
430+
}
431+
if i < k {
432+
return 0, fmt.Errorf("cannot find mantissa in %q", s)
433+
}
434+
// Convert the entire mantissa to a float at once to avoid rounding errors.
435+
f = float64(d) / float64pow10[i-k]
436+
if i >= uint(len(s)) {
437+
// Fast path - parsed fractional number.
438+
if minus {
439+
f = -f
440+
}
441+
return f, nil
442+
}
443+
}
444+
if s[i] == 'e' || s[i] == 'E' {
445+
// Parse exponent part.
446+
i++
447+
if i >= uint(len(s)) {
448+
return 0, fmt.Errorf("cannot parse exponent in %q", s)
449+
}
450+
expMinus := false
451+
if s[i] == '+' || s[i] == '-' {
452+
expMinus = s[i] == '-'
453+
i++
454+
if i >= uint(len(s)) {
455+
return 0, fmt.Errorf("cannot parse exponent in %q", s)
456+
}
457+
}
458+
exp := int16(0)
459+
j := i
460+
for i < uint(len(s)) {
461+
if s[i] >= '0' && s[i] <= '9' {
462+
exp = exp*10 + int16(s[i]-'0')
463+
i++
464+
if exp > 300 {
465+
// The exponent may be too big for float64.
466+
// Fall back to standard parsing.
467+
f, err := strconv.ParseFloat(s, 64)
468+
if err != nil && !math.IsInf(f, 0) {
469+
return 0, fmt.Errorf("cannot parse exponent in %q: %s", s, err)
470+
}
471+
return f, nil
472+
}
473+
continue
474+
}
475+
break
476+
}
477+
if i <= j {
478+
return 0, fmt.Errorf("cannot parse exponent in %q", s)
479+
}
480+
if expMinus {
481+
exp = -exp
482+
}
483+
f *= math.Pow10(int(exp))
484+
if i >= uint(len(s)) {
485+
if minus {
486+
f = -f
487+
}
488+
return f, nil
489+
}
490+
}
491+
return 0, fmt.Errorf("cannot parse float64 from %q", s)
492+
}
493+
253494
var inf = math.Inf(1)
254495
var nan = math.NaN()

0 commit comments

Comments
 (0)