Source file src/internal/strconv/fp_test.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package strconv_test
     6  
     7  import (
     8  	_ "embed"
     9  	"flag"
    10  	"fmt"
    11  	. "internal/strconv"
    12  	"io"
    13  	"net/http"
    14  	"os"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  func pow2(i int) float64 {
    20  	switch {
    21  	case i < 0:
    22  		return 1 / pow2(-i)
    23  	case i == 0:
    24  		return 1
    25  	case i == 1:
    26  		return 2
    27  	}
    28  	return pow2(i/2) * pow2(i-i/2)
    29  }
    30  
    31  // Wrapper around ParseFloat(x, 64).  Handles dddddp+ddd (binary exponent)
    32  // itself, passes the rest on to ParseFloat.
    33  func myatof64(s string) (f float64, ok bool) {
    34  	if mant, exp, ok := strings.Cut(s, "p"); ok {
    35  		n, err := ParseInt(mant, 10, 64)
    36  		if err != nil {
    37  			return 0, false
    38  		}
    39  		e, err1 := Atoi(exp)
    40  		if err1 != nil {
    41  			println("bad e", exp)
    42  			return 0, false
    43  		}
    44  		v := float64(n)
    45  		// We expect that v*pow2(e) fits in a float64,
    46  		// but pow2(e) by itself may not. Be careful.
    47  		if e <= -1000 {
    48  			v *= pow2(-1000)
    49  			e += 1000
    50  			for e < 0 {
    51  				v /= 2
    52  				e++
    53  			}
    54  			return v, true
    55  		}
    56  		if e >= 1000 {
    57  			v *= pow2(1000)
    58  			e -= 1000
    59  			for e > 0 {
    60  				v *= 2
    61  				e--
    62  			}
    63  			return v, true
    64  		}
    65  		return v * pow2(e), true
    66  	}
    67  	f1, err := ParseFloat(s, 64)
    68  	if err != nil {
    69  		return 0, false
    70  	}
    71  	return f1, true
    72  }
    73  
    74  // Wrapper around strconv.ParseFloat(x, 32).  Handles dddddp+ddd (binary exponent)
    75  // itself, passes the rest on to strconv.ParseFloat.
    76  func myatof32(s string) (f float32, ok bool) {
    77  	if mant, exp, ok := strings.Cut(s, "p"); ok {
    78  		n, err := Atoi(mant)
    79  		if err != nil {
    80  			println("bad n", mant)
    81  			return 0, false
    82  		}
    83  		e, err1 := Atoi(exp)
    84  		if err1 != nil {
    85  			println("bad p", exp)
    86  			return 0, false
    87  		}
    88  		return float32(float64(n) * pow2(e)), true
    89  	}
    90  	f64, err1 := ParseFloat(s, 32)
    91  	f1 := float32(f64)
    92  	if err1 != nil {
    93  		return 0, false
    94  	}
    95  	return f1, true
    96  }
    97  
    98  //go:embed testdata/testfp.txt
    99  var testfp string
   100  
   101  func TestFp(t *testing.T) {
   102  	lineno := 0
   103  	for line := range strings.Lines(testfp) {
   104  		lineno++
   105  		line, _, _ = strings.Cut(line, "#")
   106  		line = strings.TrimSpace(line)
   107  		if line == "" {
   108  			continue
   109  		}
   110  		a := strings.Split(line, " ")
   111  		if len(a) != 4 {
   112  			t.Errorf("testdata/testfp.txt:%d: wrong field count", lineno)
   113  			continue
   114  		}
   115  		var s string
   116  		var v float64
   117  		switch a[0] {
   118  		case "float64":
   119  			var ok bool
   120  			v, ok = myatof64(a[2])
   121  			if !ok {
   122  				t.Errorf("testdata/testfp.txt:%d: cannot atof64 %s", lineno, a[2])
   123  				continue
   124  			}
   125  			s = fmt.Sprintf(a[1], v)
   126  		case "float32":
   127  			v1, ok := myatof32(a[2])
   128  			if !ok {
   129  				t.Errorf("testdata/testfp.txt:%d: cannot atof32 %s", lineno, a[2])
   130  				continue
   131  			}
   132  			s = fmt.Sprintf(a[1], v1)
   133  			v = float64(v1)
   134  		}
   135  		if s != a[3] {
   136  			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])
   137  		}
   138  	}
   139  }
   140  
   141  // The -testbase flag runs the full testbase input set instead of the
   142  // random sample in testdata/*1k.txt. See testdata/README for details.
   143  var testbase = flag.Bool("testbase", false, "download and test full testbase testdata")
   144  
   145  // testbaseURL is the URL for downloading the full testbase testdata.
   146  // There is also a copy on "https://swtch.com/testbase/".
   147  var testbaseURL = "https://gist.githubusercontent.com/rsc/606b378b0bf95c24a6fd6cef99e262e1/raw/128a03890e536bdf403e6cc768b0737405c6734d/"
   148  
   149  //go:embed testdata/atof1k.txt
   150  var atof1ktxt string
   151  
   152  //go:embed testdata/ftoa1k.txt
   153  var ftoa1ktxt string
   154  
   155  // openTestbase opens the named testbase data file.
   156  // By default it opens testdata/name1k.txt,
   157  // but if the -testbase flag has been set,
   158  // then it opens the full testdata/name.txt,
   159  // downloading that file if necessary.
   160  func openTestbase(t *testing.T, name string) (file, data string) {
   161  	if !*testbase {
   162  		switch name {
   163  		case "atof":
   164  			return "testdata/atof1k.txt", atof1ktxt
   165  		case "ftoa":
   166  			return "testdata/ftoa1k.txt", ftoa1ktxt
   167  		}
   168  		t.Fatalf("unknown file %s", name)
   169  	}
   170  
   171  	// Use cached copy if present.
   172  	file = "testdata/" + name + ".txt"
   173  	if data, err := os.ReadFile(file); err == nil {
   174  		return file, string(data)
   175  	}
   176  
   177  	// Download copy.
   178  	url := testbaseURL + name + ".txt"
   179  	resp, err := http.Get(url)
   180  	if err != nil {
   181  		t.Fatalf("%s: %s", url, err)
   182  	}
   183  	if resp.StatusCode != 200 {
   184  		t.Fatalf("%s: %s", url, resp.Status)
   185  	}
   186  	bytes, err := io.ReadAll(resp.Body)
   187  	resp.Body.Close()
   188  	if err != nil {
   189  		t.Fatalf("%s: %s", url, err)
   190  	}
   191  	if err := os.WriteFile(file, bytes, 0666); err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	return file, string(bytes)
   195  }
   196  
   197  func TestParseFloatTestdata(t *testing.T) {
   198  	// Test testbase inputs, optimized against not.
   199  	name, data := openTestbase(t, "atof")
   200  	fail := 0
   201  	lineno := 0
   202  	for line := range strings.Lines(data) {
   203  		lineno++
   204  		s := strings.TrimSpace(line)
   205  		if strings.HasPrefix(s, "#") || s == "" {
   206  			continue
   207  		}
   208  		SetOptimize(false)
   209  		want, err1 := ParseFloat(s, 64)
   210  		SetOptimize(true)
   211  		have, err2 := ParseFloat(s, 64)
   212  		if err1 != nil {
   213  			// Error in test data; should not happen.
   214  			t.Errorf("%s:%d: ParseFloat(%#q): %v", name, lineno, s, err1)
   215  			continue
   216  		}
   217  		if err2 != nil {
   218  			t.Errorf("ParseFloat(%#q): %v", s, err2)
   219  			if fail++; fail > 100 {
   220  				t.Fatalf("too many failures")
   221  			}
   222  			continue
   223  		}
   224  		if have != want {
   225  			t.Errorf("ParseFloat(%#q) = %#x, want %#x", s, have, want)
   226  			if fail++; fail > 100 {
   227  				t.Fatalf("too many failures")
   228  			}
   229  		}
   230  	}
   231  }
   232  
   233  func TestFormatFloatTestdata(t *testing.T) {
   234  	// Test testbase inputs, optimized against not.
   235  	name, data := openTestbase(t, "ftoa")
   236  	fail := 0
   237  	lineno := 0
   238  	for line := range strings.Lines(data) {
   239  		lineno++
   240  		s := strings.TrimSpace(line)
   241  		if strings.HasPrefix(s, "#") || s == "" {
   242  			continue
   243  		}
   244  		f, err := ParseFloat(s, 64)
   245  		if err != nil {
   246  			// Error in test data; should not happen.
   247  			t.Errorf("%s:%d: ParseFloat(%#q): %v", name, lineno, s, err)
   248  			continue
   249  		}
   250  		for i := range 19 {
   251  			SetOptimize(false)
   252  			want := FormatFloat(f, 'e', i, 64)
   253  			SetOptimize(true)
   254  			have := FormatFloat(f, 'e', i, 64)
   255  			if have != want {
   256  				t.Errorf("FormatFloat(%#x, 'e', %d) = %s, want %s", f, i, have, want)
   257  				if fail++; fail > 100 {
   258  					t.Fatalf("too many failures")
   259  				}
   260  			}
   261  		}
   262  	}
   263  }
   264  

View as plain text