From a63b1806aa7484d744e79cd2f6d8e3bf73c4092c Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 28 May 2015 17:38:05 -0700 Subject: [PATCH] math/big: remove (*Float).Scan, ScanFloat; more robust (*Float).Parse - (*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 --- src/math/big/float.go | 7 +- src/math/big/float_test.go | 21 ++---- src/math/big/floatconv.go | 118 ++++++++++++++++----------------- src/math/big/floatconv_test.go | 34 +++++++++- 4 files changed, 94 insertions(+), 86 deletions(-) diff --git a/src/math/big/float.go b/src/math/big/float.go index dff40545d5..b13fea6a6a 100644 --- a/src/math/big/float.go +++ b/src/math/big/float.go @@ -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 diff --git a/src/math/big/float_test.go b/src/math/big/float_test.go index 23abe18baa..d3b214b631 100644 --- a/src/math/big/float_test.go +++ b/src/math/big/float_test.go @@ -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, _, err := ParseFloat(s, 0, 1000, ToNearestEven) + if err != nil { + panic(err) } - - x.SetPrec(1000) - if _, ok := x.SetString(s); !ok { - panic(fmt.Sprintf("%q is not a valid float", s)) - } - return &x + return x } func TestFloatSetPrec(t *testing.T) { diff --git a/src/math/big/floatconv.go b/src/math/big/floatconv.go index dc62b450db..4a070ca64d 100644 --- a/src/math/big/floatconv.go +++ b/src/math/big/floatconv.go @@ -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 + if f, _, err := z.Parse(s, 0); err == nil { + return f, true } - - // there should be no unread characters left - if _, err = r.ReadByte(); err != io.EOF { - return nil, false - } - - 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) { diff --git a/src/math/big/floatconv_test.go b/src/math/big/floatconv_test.go index fffcd70ce6..656d28c975 100644 --- a/src/math/big/floatconv_test.go +++ b/src/math/big/floatconv_test.go @@ -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()