benchproc: correctly parse signed numeric sort keys

The regular expression used by parseNum did not include an optional sign
and was not anchored to the input. As a result, a value such as "-1k"
was parsed as positive 1000, while strings such as "abc1Mxyz" and
"1kB/s" were accepted by extracting a numeric substring.

Anchor the suffixed-number expression and include an optional leading
sign. Continue to use strconv.ParseFloat to validate the numeric portion.

Add tests for signed SI and IEC values and for rejecting inputs with
unrelated prefixes or suffixes.

Fixes golang/go#80770

Change-Id: If90675c4cdeaac3ba56f6efd992ee9d90d9bc8a0
Reviewed-on: https://go-review.googlesource.com/c/perf/+/811620
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/benchproc/sort.go b/benchproc/sort.go
index 69c85fd..b835065 100644
--- a/benchproc/sort.go
+++ b/benchproc/sort.go
@@ -104,7 +104,7 @@
 
 const numPrefixes = `KMGTPEZY`
 
-var numRe = regexp.MustCompile(`([0-9.]+)([k` + numPrefixes + `]i?)?[bB]?`)
+var numRe = regexp.MustCompile(`^([+-]?[0-9.]+)([k` + numPrefixes + `]i?)?[bB]?$`)
 
 // parseNum is a fuzzy number parser. It supports common patterns,
 // such as SI prefixes.
diff --git a/benchproc/sort_test.go b/benchproc/sort_test.go
index a0f1886..456647e 100644
--- a/benchproc/sort_test.go
+++ b/benchproc/sort_test.go
@@ -113,6 +113,12 @@
 			t.Errorf("%s: want %v, got %v", x, want, got)
 		}
 	}
+	checkError := func(x string) {
+		t.Helper()
+		if got, err := parseNum(x); err == nil {
+			t.Errorf("%s: want error, got %v", x, got)
+		}
+	}
 
 	check("1", 1)
 	check("1B", 1)
@@ -130,4 +136,9 @@
 	check("1E", 1000000000000000000)
 	check("1Z", 1000000000000000000000)
 	check("1Y", 1000000000000000000000000)
+	check("-1k", -1000)
+	check("+2MiB", 2<<20)
+	check("-1.5G", -1.5e9)
+	checkError("abc1Mxyz")
+	checkError("1kB/s")
 }