blob: 92a663c6e331b47f2990e9bb17a58758d9c1d383 [file] [log] [blame]
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package strconv_test
import (
_ "embed"
"flag"
"fmt"
. "internal/strconv"
"io"
"net/http"
"os"
"strings"
"testing"
)
func pow2(i int) float64 {
switch {
case i < 0:
return 1 / pow2(-i)
case i == 0:
return 1
case i == 1:
return 2
}
return pow2(i/2) * pow2(i-i/2)
}
// 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 := ParseInt(mant, 10, 64)
if err != nil {
return 0, false
}
e, err1 := Atoi(exp)
if err1 != nil {
println("bad e", exp)
return 0, false
}
v := float64(n)
// We expect that v*pow2(e) fits in a float64,
// but pow2(e) by itself may not. Be careful.
if e <= -1000 {
v *= pow2(-1000)
e += 1000
for e < 0 {
v /= 2
e++
}
return v, true
}
if e >= 1000 {
v *= pow2(1000)
e -= 1000
for e > 0 {
v *= 2
e--
}
return v, true
}
return v * pow2(e), true
}
f1, err := ParseFloat(s, 64)
if err != nil {
return 0, false
}
return f1, true
}
// Wrapper around strconv.ParseFloat(x, 32). Handles dddddp+ddd (binary exponent)
// 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 := Atoi(mant)
if err != nil {
println("bad n", mant)
return 0, false
}
e, err1 := Atoi(exp)
if err1 != nil {
println("bad p", exp)
return 0, false
}
return float32(float64(n) * pow2(e)), true
}
f64, err1 := ParseFloat(s, 32)
f1 := float32(f64)
if err1 != nil {
return 0, false
}
return f1, true
}
//go:embed testdata/testfp.txt
var testfp string
func TestFp(t *testing.T) {
lineno := 0
for line := range strings.Lines(testfp) {
lineno++
line, _, _ = strings.Cut(line, "#")
line = strings.TrimSpace(line)
if line == "" {
continue
}
a := strings.Split(line, " ")
if len(a) != 4 {
t.Errorf("testdata/testfp.txt:%d: wrong field count", lineno)
continue
}
var s string
var v float64
switch a[0] {
case "float64":
var ok bool
v, ok = myatof64(a[2])
if !ok {
t.Errorf("testdata/testfp.txt:%d: cannot atof64 %s", lineno, a[2])
continue
}
s = fmt.Sprintf(a[1], v)
case "float32":
v1, ok := myatof32(a[2])
if !ok {
t.Errorf("testdata/testfp.txt:%d: cannot atof32 %s", lineno, a[2])
continue
}
s = fmt.Sprintf(a[1], v1)
v = float64(v1)
}
if s != a[3] {
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])
}
}
}
// 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")
}
}
}
}
}