mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
internal/strconv: add testbase tests
Add ability to test against inputs chosen by from the stress tests computed by Vern Paxson's testbase program. Checked that 'go test -testbase' passes. Change-Id: I81057e55df6cd369b40ce623a59884e6ead0ed76 Reviewed-on: https://go-review.googlesource.com/c/go/+/719620 Reviewed-by: Alan Donovan <adonovan@google.com> Auto-Submit: Russ Cox <rsc@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
6954be0baa
commit
89f6dba7e6
6 changed files with 2174 additions and 16 deletions
|
|
@ -5,10 +5,13 @@
|
|||
package strconv_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"internal/strconv"
|
||||
. "internal/strconv"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
|
@ -25,15 +28,15 @@ func pow2(i int) float64 {
|
|||
return pow2(i/2) * pow2(i-i/2)
|
||||
}
|
||||
|
||||
// Wrapper around strconv.ParseFloat(x, 64). Handles dddddp+ddd (binary exponent)
|
||||
// itself, passes the rest on to strconv.ParseFloat.
|
||||
// Wrapper around ParseFloat(x, 64). Handles dddddp+ddd (binary exponent)
|
||||
// itself, passes the rest on to ParseFloat.
|
||||
func myatof64(s string) (f float64, ok bool) {
|
||||
if mant, exp, ok := strings.Cut(s, "p"); ok {
|
||||
n, err := strconv.ParseInt(mant, 10, 64)
|
||||
n, err := ParseInt(mant, 10, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
e, err1 := strconv.Atoi(exp)
|
||||
e, err1 := Atoi(exp)
|
||||
if err1 != nil {
|
||||
println("bad e", exp)
|
||||
return 0, false
|
||||
|
|
@ -61,7 +64,7 @@ func myatof64(s string) (f float64, ok bool) {
|
|||
}
|
||||
return v * pow2(e), true
|
||||
}
|
||||
f1, err := strconv.ParseFloat(s, 64)
|
||||
f1, err := ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
|
@ -72,19 +75,19 @@ func myatof64(s string) (f float64, ok bool) {
|
|||
// itself, passes the rest on to strconv.ParseFloat.
|
||||
func myatof32(s string) (f float32, ok bool) {
|
||||
if mant, exp, ok := strings.Cut(s, "p"); ok {
|
||||
n, err := strconv.Atoi(mant)
|
||||
n, err := Atoi(mant)
|
||||
if err != nil {
|
||||
println("bad n", mant)
|
||||
return 0, false
|
||||
}
|
||||
e, err1 := strconv.Atoi(exp)
|
||||
e, err1 := Atoi(exp)
|
||||
if err1 != nil {
|
||||
println("bad p", exp)
|
||||
return 0, false
|
||||
}
|
||||
return float32(float64(n) * pow2(e)), true
|
||||
}
|
||||
f64, err1 := strconv.ParseFloat(s, 32)
|
||||
f64, err1 := ParseFloat(s, 32)
|
||||
f1 := float32(f64)
|
||||
if err1 != nil {
|
||||
return 0, false
|
||||
|
|
@ -96,9 +99,9 @@ func myatof32(s string) (f float32, ok bool) {
|
|||
var testfp string
|
||||
|
||||
func TestFp(t *testing.T) {
|
||||
s := bufio.NewScanner(strings.NewReader(testfp))
|
||||
for lineno := 1; s.Scan(); lineno++ {
|
||||
line := s.Text()
|
||||
lineno := 0
|
||||
for line := range strings.Lines(testfp) {
|
||||
lineno++
|
||||
line, _, _ = strings.Cut(line, "#")
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
|
|
@ -133,7 +136,128 @@ func TestFp(t *testing.T) {
|
|||
t.Errorf("testdata/testfp.txt:%d: %s %s %s %s: have %s want %s", lineno, a[0], a[1], a[2], a[3], s, a[3])
|
||||
}
|
||||
}
|
||||
if s.Err() != nil {
|
||||
t.Fatal("testfp: read testdata/testfp.txt: ", s.Err())
|
||||
}
|
||||
|
||||
// The -testbase flag runs the full testbase input set instead of the
|
||||
// random sample in testdata/*1k.txt. See testdata/README for details.
|
||||
var testbase = flag.Bool("testbase", false, "download and test full testbase testdata")
|
||||
|
||||
// testbaseURL is the URL for downloading the full testbase testdata.
|
||||
// There is also a copy on "https://swtch.com/testbase/".
|
||||
var testbaseURL = "https://gist.githubusercontent.com/rsc/606b378b0bf95c24a6fd6cef99e262e1/raw/128a03890e536bdf403e6cc768b0737405c6734d/"
|
||||
|
||||
//go:embed testdata/atof1k.txt
|
||||
var atof1ktxt string
|
||||
|
||||
//go:embed testdata/ftoa1k.txt
|
||||
var ftoa1ktxt string
|
||||
|
||||
// openTestbase opens the named testbase data file.
|
||||
// By default it opens testdata/name1k.txt,
|
||||
// but if the -testbase flag has been set,
|
||||
// then it opens the full testdata/name.txt,
|
||||
// downloading that file if necessary.
|
||||
func openTestbase(t *testing.T, name string) (file, data string) {
|
||||
if !*testbase {
|
||||
switch name {
|
||||
case "atof":
|
||||
return "testdata/atof1k.txt", atof1ktxt
|
||||
case "ftoa":
|
||||
return "testdata/ftoa1k.txt", ftoa1ktxt
|
||||
}
|
||||
t.Fatalf("unknown file %s", name)
|
||||
}
|
||||
|
||||
// Use cached copy if present.
|
||||
file = "testdata/" + name + ".txt"
|
||||
if data, err := os.ReadFile(file); err == nil {
|
||||
return file, string(data)
|
||||
}
|
||||
|
||||
// Download copy.
|
||||
url := testbaseURL + name + ".txt"
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %s", url, err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("%s: %s", url, resp.Status)
|
||||
}
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %s", url, err)
|
||||
}
|
||||
if err := os.WriteFile(file, bytes, 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return file, string(bytes)
|
||||
}
|
||||
|
||||
func TestParseFloatTestdata(t *testing.T) {
|
||||
// Test testbase inputs, optimized against not.
|
||||
name, data := openTestbase(t, "atof")
|
||||
fail := 0
|
||||
lineno := 0
|
||||
for line := range strings.Lines(data) {
|
||||
lineno++
|
||||
s := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(s, "#") || s == "" {
|
||||
continue
|
||||
}
|
||||
SetOptimize(false)
|
||||
want, err1 := ParseFloat(s, 64)
|
||||
SetOptimize(true)
|
||||
have, err2 := ParseFloat(s, 64)
|
||||
if err1 != nil {
|
||||
// Error in test data; should not happen.
|
||||
t.Errorf("%s:%d: ParseFloat(%#q): %v", name, lineno, s, err1)
|
||||
continue
|
||||
}
|
||||
if err2 != nil {
|
||||
t.Errorf("ParseFloat(%#q): %v", s, err2)
|
||||
if fail++; fail > 100 {
|
||||
t.Fatalf("too many failures")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if have != want {
|
||||
t.Errorf("ParseFloat(%#q) = %#x, want %#x", s, have, want)
|
||||
if fail++; fail > 100 {
|
||||
t.Fatalf("too many failures")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatFloatTestdata(t *testing.T) {
|
||||
// Test testbase inputs, optimized against not.
|
||||
name, data := openTestbase(t, "ftoa")
|
||||
fail := 0
|
||||
lineno := 0
|
||||
for line := range strings.Lines(data) {
|
||||
lineno++
|
||||
s := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(s, "#") || s == "" {
|
||||
continue
|
||||
}
|
||||
f, err := ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
// Error in test data; should not happen.
|
||||
t.Errorf("%s:%d: ParseFloat(%#q): %v", name, lineno, s, err)
|
||||
continue
|
||||
}
|
||||
for i := range 19 {
|
||||
SetOptimize(false)
|
||||
want := FormatFloat(f, 'e', i, 64)
|
||||
SetOptimize(true)
|
||||
have := FormatFloat(f, 'e', i, 64)
|
||||
if have != want {
|
||||
t.Errorf("FormatFloat(%#x, 'e', %d) = %s, want %s", f, i, have, want)
|
||||
if fail++; fail > 100 {
|
||||
t.Fatalf("too many failures")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,10 +206,17 @@ var ftoatests = []ftoaTest{
|
|||
{8393378656576888. * (1 << 1), 'e', 15, "1.678675731315378e+16"},
|
||||
{8738676561280626. * (1 << 4), 'e', 16, "1.3981882498049002e+17"},
|
||||
{8291032395191335. / (1 << 30), 'e', 5, "7.72163e+06"},
|
||||
{8880392441509914. / (1 << 80), 'e', 16, "7.3456884594794477e-09"},
|
||||
|
||||
// Exercise divisiblePow5 case in fixedFtoa
|
||||
{2384185791015625. * (1 << 12), 'e', 5, "9.76562e+18"},
|
||||
{2384185791015625. * (1 << 13), 'e', 5, "1.95312e+19"},
|
||||
|
||||
// Exercise potential mistakes in fixedFtoa.
|
||||
// Found by introducing mistakes and running 'go test -testbase'.
|
||||
{0x1.000000000005p+71, 'e', 16, "2.3611832414348645e+21"},
|
||||
{0x1.0000p-27, 'e', 17, "7.45058059692382812e-09"},
|
||||
{0x1.0000p-41, 'e', 17, "4.54747350886464119e-13"},
|
||||
}
|
||||
|
||||
func TestFtoa(t *testing.T) {
|
||||
|
|
|
|||
27
src/internal/strconv/testdata/README
vendored
Normal file
27
src/internal/strconv/testdata/README
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
testfp.txt contains conversion tests from Vern Paxson's paper
|
||||
“A Program for Testing IEEE Decimal-Binary Conversion”
|
||||
https://www.icir.org/vern/papers/testbase-report.pdf
|
||||
|
||||
That paper from 1991 describes a tester called 'testbase',
|
||||
written in non-64-bit-safe pre-ANSI C.
|
||||
As of 2025, it is still available at ftp://ftp.ee.lbl.gov/testbase.tar.Z.
|
||||
|
||||
The files
|
||||
|
||||
https://swtch.com/testbase/atof.txt
|
||||
https://swtch.com/testbase/ftoa.txt
|
||||
|
||||
are the test inputs that testbase generates and checks,
|
||||
logged during an actual run, totaling about 10 MB.
|
||||
|
||||
The files atof1k.txt and ftoa1k.txt in this directory each contain
|
||||
1000 random samples of the full trace. They are used during
|
||||
'go test internal/strconv'.
|
||||
|
||||
Running 'go test internal/strconv -testbase' downloads the
|
||||
complete files into this directory as atof.txt and ftoa.txt and
|
||||
then uses those instead of the sampled versions.
|
||||
The complete tests take about 10 seconds on a Macbook Pro.
|
||||
|
||||
Backup copies of the files are also posted at
|
||||
https://gist.github.com/rsc/606b378b0bf95c24a6fd6cef99e262e1.
|
||||
1000
src/internal/strconv/testdata/atof1k.txt
vendored
Normal file
1000
src/internal/strconv/testdata/atof1k.txt
vendored
Normal file
File diff suppressed because it is too large
Load diff
1000
src/internal/strconv/testdata/ftoa1k.txt
vendored
Normal file
1000
src/internal/strconv/testdata/ftoa1k.txt
vendored
Normal file
File diff suppressed because it is too large
Load diff
2
src/internal/strconv/testdata/testfp.txt
vendored
2
src/internal/strconv/testdata/testfp.txt
vendored
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
# Difficult boundary cases, derived from tables given in
|
||||
# Vern Paxson, A Program for Testing IEEE Decimal-Binary Conversion
|
||||
# ftp://ftp.ee.lbl.gov/testbase-report.ps.Z
|
||||
# https://www.icir.org/vern/papers/testbase-report.pdf
|
||||
|
||||
# Table 1: Stress Inputs for Conversion to 53-bit Binary, < 1/2 ULP
|
||||
float64 %b 5e+125 6653062250012735p+365
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue