]> Cypherpunks repositories - gostls13.git/commitdiff
math/big: remove (*Float).Scan, ScanFloat; more robust (*Float).Parse
authorRobert Griesemer <gri@golang.org>
Fri, 29 May 2015 00:38:05 +0000 (17:38 -0700)
committerRobert Griesemer <gri@golang.org>
Fri, 29 May 2015 17:11:43 +0000 (17:11 +0000)
- (*Float).Scan conflicted with fmt.Scanner.Scan; it was also only used
  internally. Removed it, as well as the companion ScanFloat function.

- (*Float).Parse (and thus ParseFloat) can now also parse infinities.
  As a result, more code could be simplified.

- Fixed a bug in rounding (round may implicitly be called for infinite
  values). Found via existing test cases, after simplifying some code.

- Added more test cases.

Fixes issue #10938.

Change-Id: I1df97821654f034965ba8b82b272e52e6dc427f1
Reviewed-on: https://go-review.googlesource.com/10498
Reviewed-by: Alan Donovan <adonovan@google.com>
src/math/big/float.go
src/math/big/float_test.go
src/math/big/floatconv.go
src/math/big/floatconv_test.go

index dff40545d590e4182565ad6387fabd4dec335786..b13fea6a6a67188a7fad72c7f9101358e1bf122e 100644 (file)
@@ -381,14 +381,11 @@ func (x *Float) validate() {
 func (z *Float) round(sbit uint) {
        if debugFloat {
                z.validate()
-               if z.form > finite {
-                       panic(fmt.Sprintf("round called for non-finite value %s", z))
-               }
        }
-       // z.form <= finite
 
        z.acc = Exact
-       if z.form == zero {
+       if z.form != finite {
+               // ±0 or ±Inf => nothing left to do
                return
        }
        // z.form == finite && len(z.mant) > 0
index 23abe18baa08c09ae81807e4f1f7374bcef12d74..d3b214b631db1e3b5c437a7daa1e5a4855a44a7f 100644 (file)
@@ -92,24 +92,11 @@ func TestFloatZeroValue(t *testing.T) {
 }
 
 func makeFloat(s string) *Float {
-       var x Float
-
-       switch s {
-       case "0":
-               return &x
-       case "-0":
-               return x.Neg(&x)
-       case "Inf", "+Inf":
-               return x.SetInf(false)
-       case "-Inf":
-               return x.SetInf(true)
-       }
-
-       x.SetPrec(1000)
-       if _, ok := x.SetString(s); !ok {
-               panic(fmt.Sprintf("%q is not a valid float", s))
+       x, _, err := ParseFloat(s, 0, 1000, ToNearestEven)
+       if err != nil {
+               panic(err)
        }
-       return &x
+       return x
 }
 
 func TestFloatSetPrec(t *testing.T) {
index dc62b450db031628baea1990656866dd0c5d77ee..4a070ca64d4c835aedad131bb7abb2c6409a6466 100644 (file)
@@ -14,60 +14,19 @@ import (
 
 // SetString sets z to the value of s and returns z and a boolean indicating
 // success. s must be a floating-point number of the same format as accepted
-// by Scan, with number prefixes permitted.
+// by Parse, with base argument 0.
 func (z *Float) SetString(s string) (*Float, bool) {
-       r := strings.NewReader(s)
-
-       f, _, err := z.Scan(r, 0)
-       if err != nil {
-               return nil, false
-       }
-
-       // there should be no unread characters left
-       if _, err = r.ReadByte(); err != io.EOF {
-               return nil, false
+       if f, _, err := z.Parse(s, 0); err == nil {
+               return f, true
        }
-
-       return f, true
+       return nil, false
 }
 
-// Scan scans the number corresponding to the longest possible prefix
-// of r representing a floating-point number with a mantissa in the
-// given conversion base (the exponent is always a decimal number).
-// It sets z to the (possibly rounded) value of the corresponding
-// floating-point number, and returns z, the actual base b, and an
-// error err, if any. If z's precision is 0, it is changed to 64
-// before rounding takes effect. The number must be of the form:
-//
-//     number   = [ sign ] [ prefix ] mantissa [ exponent ] .
-//     sign     = "+" | "-" .
-//      prefix   = "0" ( "x" | "X" | "b" | "B" ) .
-//     mantissa = digits | digits "." [ digits ] | "." digits .
-//     exponent = ( "E" | "e" | "p" ) [ sign ] digits .
-//     digits   = digit { digit } .
-//     digit    = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
-//
-// The base argument must be 0, 2, 10, or 16. Providing an invalid base
-// argument will lead to a run-time panic.
-//
-// For base 0, the number prefix determines the actual base: A prefix of
-// "0x" or "0X" selects base 16, and a "0b" or "0B" prefix selects
-// base 2; otherwise, the actual base is 10 and no prefix is accepted.
-// The octal prefix "0" is not supported (a leading "0" is simply
-// considered a "0").
-//
-// A "p" exponent indicates a binary (rather then decimal) exponent;
-// for instance "0x1.fffffffffffffp1023" (using base 0) represents the
-// maximum float64 value. For hexadecimal mantissae, the exponent must
-// be binary, if present (an "e" or "E" exponent indicator cannot be
-// distinguished from a mantissa digit).
-//
-// The returned *Float f is nil and the value of z is valid but not
-// defined if an error is reported.
-//
-// BUG(gri) The Float.Scan signature conflicts with Scan(s fmt.ScanState, ch rune) error.
-//          (https://github.com/golang/go/issues/10938)
-func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
+// scan is like Parse but reads the longest possible prefix representing a valid
+// floating point number from an io.ByteScanner rather than a string. It serves
+// as the implementation of Parse. It does not recognize ±Inf and does not expect
+// EOF at the end.
+func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
        prec := z.prec
        if prec == 0 {
                prec = 64
@@ -211,14 +170,55 @@ func (z *Float) pow10(n int64) *Float {
        return z
 }
 
-// Parse is like z.Scan(r, base), but instead of reading from an
-// io.ByteScanner, it parses the string s. An error is also returned
-// if the string contains invalid or trailing bytes not belonging to
-// the number.
+// Parse parses s which must contain a text representation of a floating-
+// point number with a mantissa in the given conversion base (the exponent
+// is always a decimal number), or a string representing an infinite value.
+//
+// It sets z to the (possibly rounded) value of the corresponding floating-
+// point value, and returns z, the actual base b, and an error err, if any.
+// If z's precision is 0, it is changed to 64 before rounding takes effect.
+// The number must be of the form:
+//
+//     number   = [ sign ] [ prefix ] mantissa [ exponent ] | infinity .
+//     sign     = "+" | "-" .
+//      prefix   = "0" ( "x" | "X" | "b" | "B" ) .
+//     mantissa = digits | digits "." [ digits ] | "." digits .
+//     exponent = ( "E" | "e" | "p" ) [ sign ] digits .
+//     digits   = digit { digit } .
+//     digit    = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
+//      infinity = [ sign ] ( "inf" | "Inf" ) .
+//
+// The base argument must be 0, 2, 10, or 16. Providing an invalid base
+// argument will lead to a run-time panic.
+//
+// For base 0, the number prefix determines the actual base: A prefix of
+// "0x" or "0X" selects base 16, and a "0b" or "0B" prefix selects
+// base 2; otherwise, the actual base is 10 and no prefix is accepted.
+// The octal prefix "0" is not supported (a leading "0" is simply
+// considered a "0").
+//
+// A "p" exponent indicates a binary (rather then decimal) exponent;
+// for instance "0x1.fffffffffffffp1023" (using base 0) represents the
+// maximum float64 value. For hexadecimal mantissae, the exponent must
+// be binary, if present (an "e" or "E" exponent indicator cannot be
+// distinguished from a mantissa digit).
+//
+// The returned *Float f is nil and the value of z is valid but not
+// defined if an error is reported.
+//
 func (z *Float) Parse(s string, base int) (f *Float, b int, err error) {
-       r := strings.NewReader(s)
+       // scan doesn't handle ±Inf
+       if len(s) == 3 && (s == "Inf" || s == "inf") {
+               f = z.SetInf(false)
+               return
+       }
+       if len(s) == 4 && (s[0] == '+' || s[0] == '-') && (s[1:] == "Inf" || s[1:] == "inf") {
+               f = z.SetInf(s[0] == '-')
+               return
+       }
 
-       if f, b, err = z.Scan(r, base); err != nil {
+       r := strings.NewReader(s)
+       if f, b, err = z.scan(r, base); err != nil {
                return
        }
 
@@ -232,12 +232,6 @@ func (z *Float) Parse(s string, base int) (f *Float, b int, err error) {
        return
 }
 
-// ScanFloat is like f.Scan(r, base) with f set to the given precision
-// and rounding mode.
-func ScanFloat(r io.ByteScanner, base int, prec uint, mode RoundingMode) (f *Float, b int, err error) {
-       return new(Float).SetPrec(prec).SetMode(mode).Scan(r, base)
-}
-
 // ParseFloat is like f.Parse(s, base) with f set to the given precision
 // and rounding mode.
 func ParseFloat(s string, base int, prec uint, mode RoundingMode) (f *Float, b int, err error) {
index fffcd70ce6bc40387e84e4d4c4b8a5b9b4c2c7e5..656d28c97540ad78a047c97aa7db26c1cbded3c3 100644 (file)
@@ -11,9 +11,12 @@ import (
 )
 
 func TestFloatSetFloat64String(t *testing.T) {
+       inf := math.Inf(0)
+       nan := math.NaN()
+
        for _, test := range []struct {
                s string
-               x float64
+               x float64 // NaNs represent invalid inputs
        }{
                // basics
                {"0", 0},
@@ -45,6 +48,25 @@ func TestFloatSetFloat64String(t *testing.T) {
                {"1.E+10", 1e10},
                {"+1E-10", 1e-10},
 
+               // infinities
+               {"Inf", inf},
+               {"+Inf", inf},
+               {"-Inf", -inf},
+               {"inf", inf},
+               {"+inf", inf},
+               {"-inf", -inf},
+
+               // invalid numbers
+               {"", nan},
+               {"-", nan},
+               {"0x", nan},
+               {"0e", nan},
+               {"1.2ef", nan},
+               {"2..3", nan},
+               {"123..", nan},
+               {"infinity", nan},
+               {"foobar", nan},
+
                // misc decimal values
                {"3.14159265", 3.14159265},
                {"-687436.79457e-245", -687436.79457e-245},
@@ -96,8 +118,16 @@ func TestFloatSetFloat64String(t *testing.T) {
                var x Float
                x.SetPrec(53)
                _, ok := x.SetString(test.s)
+               if math.IsNaN(test.x) {
+                       // test.s is invalid
+                       if ok {
+                               t.Errorf("%s: want parse error", test.s)
+                       }
+                       continue
+               }
+               // test.s is valid
                if !ok {
-                       t.Errorf("%s: parse error", test.s)
+                       t.Errorf("%s: got parse error", test.s)
                        continue
                }
                f, _ := x.Float64()