strconv: Speed improvement to number parsing

Run underscore validation only if we have seen underscores.

Some performance results on my laptop:
name                   old time/op  new time/op  delta
Atof64Decimal-12       30.5ns ± 0%  23.8ns ± 0%  -22.02%  (p=0.016 n=5+4)
Atof64Float-12         39.0ns ± 0%  28.7ns ± 0%  -26.39%  (p=0.002 n=6+6)
Atof64FloatExp-12      64.4ns ± 1%  54.4ns ± 1%  -15.65%  (p=0.002 n=6+6)
Atof64Big-12            115ns ± 1%    87ns ± 1%  -24.45%  (p=0.002 n=6+6)
Atof64RandomBits-12     187ns ±14%   156ns ±19%  -16.46%  (p=0.032 n=6+6)
Atof64RandomFloats-12   126ns ± 0%   105ns ± 1%  -16.65%  (p=0.000 n=6+5)
Atof32Decimal-12       32.0ns ± 1%  24.0ns ± 1%  -24.97%  (p=0.002 n=6+6)
Atof32Float-12         37.1ns ± 1%  27.0ns ± 1%  -27.42%  (p=0.002 n=6+6)
Atof32FloatExp-12      68.4ns ± 1%  54.2ns ± 1%  -20.77%  (p=0.002 n=6+6)
Atof32Random-12        92.0ns ± 1%  77.4ns ± 0%  -15.81%  (p=0.000 n=6+5)
ParseInt/Pos/7bit-12   19.4ns ± 1%  13.8ns ±10%  -28.94%  (p=0.002 n=6+6)
ParseInt/Pos/26bit-12  29.1ns ± 1%  19.8ns ± 2%  -31.92%  (p=0.002 n=6+6)
ParseInt/Pos/31bit-12  33.1ns ± 0%  22.3ns ± 3%  -32.62%  (p=0.004 n=5+6)
ParseInt/Pos/56bit-12  47.8ns ± 1%  30.7ns ± 1%  -35.78%  (p=0.004 n=6+5)
ParseInt/Pos/63bit-12  51.9ns ± 1%  33.4ns ± 2%  -35.49%  (p=0.002 n=6+6)
ParseInt/Neg/7bit-12   18.5ns ± 4%  13.4ns ± 3%  -27.88%  (p=0.002 n=6+6)
ParseInt/Neg/26bit-12  28.4ns ± 3%  19.7ns ± 3%  -30.38%  (p=0.002 n=6+6)
ParseInt/Neg/31bit-12  31.9ns ± 1%  21.8ns ± 2%  -31.56%  (p=0.002 n=6+6)
ParseInt/Neg/56bit-12  46.2ns ± 0%  30.6ns ± 1%  -33.73%  (p=0.004 n=5+6)
ParseInt/Neg/63bit-12  50.2ns ± 1%  33.2ns ± 1%  -33.96%  (p=0.002 n=6+6)

Fixes #33330

Change-Id: I119da66457c2fbaf6e88bb90cf56417a16df8f0e
Reviewed-on: https://go-review.googlesource.com/c/go/+/187957
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
diff --git a/src/strconv/atof.go b/src/strconv/atof.go
index 190b25f..23de70b 100644
--- a/src/strconv/atof.go
+++ b/src/strconv/atof.go
@@ -84,7 +84,7 @@
 	for ; i < len(s); i++ {
 		switch {
 		case s[i] == '_':
-			// underscoreOK already called
+			// readFloat already checked underscores
 			continue
 		case s[i] == '.':
 			if sawdot {
@@ -140,7 +140,7 @@
 		e := 0
 		for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ {
 			if s[i] == '_' {
-				// underscoreOK already called
+				// readFloat already checked underscores
 				continue
 			}
 			if e < 10000 {
@@ -159,10 +159,11 @@
 }
 
 // readFloat reads a decimal mantissa and exponent from a float
-// string representation. It returns ok==false if the number could
-// not fit return types or is invalid.
+// string representation. It returns ok==false if the number
+// is invalid.
 func readFloat(s string) (mantissa uint64, exp int, neg, trunc, hex, ok bool) {
 	i := 0
+	underscores := false
 
 	// optional sign
 	if i >= len(s) {
@@ -195,7 +196,7 @@
 	for ; i < len(s); i++ {
 		switch c := s[i]; true {
 		case c == '_':
-			// underscoreOK already called
+			underscores = true
 			continue
 
 		case c == '.':
@@ -271,7 +272,7 @@
 		e := 0
 		for ; i < len(s) && ('0' <= s[i] && s[i] <= '9' || s[i] == '_'); i++ {
 			if s[i] == '_' {
-				// underscoreOK already called
+				underscores = true
 				continue
 			}
 			if e < 10000 {
@@ -291,6 +292,11 @@
 	if mantissa != 0 {
 		exp = dp - ndMant
 	}
+
+	if underscores && !underscoreOK(s) {
+		return
+	}
+
 	ok = true
 	return
 }
@@ -554,12 +560,16 @@
 	}
 
 	mantissa, exp, neg, trunc, hex, ok := readFloat(s)
-	if hex && ok {
+	if !ok {
+		return 0, syntaxError(fnParseFloat, s)
+	}
+
+	if hex {
 		f, err := atofHex(s, &float32info, mantissa, exp, neg, trunc)
 		return float32(f), err
 	}
 
-	if optimize && ok {
+	if optimize {
 		// Try pure floating-point arithmetic conversion.
 		if !trunc {
 			if f, ok := atof32exact(mantissa, exp, neg); ok {
@@ -597,11 +607,15 @@
 	}
 
 	mantissa, exp, neg, trunc, hex, ok := readFloat(s)
-	if hex && ok {
+	if !ok {
+		return 0, syntaxError(fnParseFloat, s)
+	}
+
+	if hex {
 		return atofHex(s, &float64info, mantissa, exp, neg, trunc)
 	}
 
-	if optimize && ok {
+	if optimize {
 		// Try pure floating-point arithmetic conversion.
 		if !trunc {
 			if f, ok := atof64exact(mantissa, exp, neg); ok {
@@ -658,9 +672,6 @@
 // ParseFloat recognizes the strings "NaN", "+Inf", and "-Inf" as their
 // respective special floating point values. It ignores case when matching.
 func ParseFloat(s string, bitSize int) (float64, error) {
-	if !underscoreOK(s) {
-		return 0, syntaxError(fnParseFloat, s)
-	}
 	if bitSize == 32 {
 		f, err := atof32(s)
 		return float64(f), err
diff --git a/src/strconv/atoi.go b/src/strconv/atoi.go
index e811bc4..131b088 100644
--- a/src/strconv/atoi.go
+++ b/src/strconv/atoi.go
@@ -58,7 +58,7 @@
 func ParseUint(s string, base int, bitSize int) (uint64, error) {
 	const fnParseUint = "ParseUint"
 
-	if s == "" || !underscoreOK(s) {
+	if s == "" {
 		return 0, syntaxError(fnParseUint, s)
 	}
 
@@ -113,12 +113,13 @@
 
 	maxVal := uint64(1)<<uint(bitSize) - 1
 
+	underscores := false
 	var n uint64
 	for _, c := range []byte(s) {
 		var d byte
 		switch {
 		case c == '_' && base0:
-			// underscoreOK already called
+			underscores = true
 			continue
 		case '0' <= c && c <= '9':
 			d = c - '0'
@@ -146,6 +147,10 @@
 		n = n1
 	}
 
+	if underscores && !underscoreOK(s0) {
+		return 0, syntaxError(fnParseUint, s0)
+	}
+
 	return n, nil
 }