From f17cd880895863e2c13396296819d0dce81fa43f Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 30 Jan 2015 14:53:53 -0800 Subject: [PATCH] math/big: split float conversion routines and tests into separate files No other functional changes. Change-Id: I7e0bb7452c6a265535297ec7ce6a629f1aff695c Reviewed-on: https://go-review.googlesource.com/3674 Reviewed-by: Alan Donovan --- src/math/big/float.go | 245 ------------------------------- src/math/big/float_test.go | 101 ------------- src/math/big/floatconv.go | 256 +++++++++++++++++++++++++++++++++ src/math/big/floatconv_test.go | 111 ++++++++++++++ 4 files changed, 367 insertions(+), 346 deletions(-) create mode 100644 src/math/big/floatconv.go create mode 100644 src/math/big/floatconv_test.go diff --git a/src/math/big/float.go b/src/math/big/float.go index 80b560f9b2..7047a6d996 100644 --- a/src/math/big/float.go +++ b/src/math/big/float.go @@ -14,11 +14,8 @@ package big import ( - "bytes" "fmt" - "io" "math" - "strings" ) // TODO(gri): Determine if there's a more natural way to set the precision. @@ -958,245 +955,3 @@ func (x *Float) Sign() int { } return 1 } - -// 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. -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.ReadRune(); err != io.EOF { - return nil, false - } - - return f, true -} - -// 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 returns the corresponding Float f, the actual base b, and an -// error err, if any. 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 or a value between 2 through MaxBase. -// -// 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 permitted. -// The octal prefix ``0'' is not supported. -// -// A "p" exponent indicates power of 2 for the exponent; for instance "1.2p3" -// with base 0 or 10 corresponds to the value 1.2 * 2**3. -// -// BUG(gri) This signature conflicts with Scan(s fmt.ScanState, ch rune) error. -func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) { - // sign - z.neg, err = scanSign(r) - if err != nil { - return - } - - // mantissa - var ecorr int // decimal exponent correction; valid if <= 0 - z.mant, b, ecorr, err = z.mant.scan(r, base, true) - if err != nil { - return - } - - // exponent - var exp int64 - var ebase int - exp, ebase, err = scanExponent(r) - if err != nil { - return - } - // special-case 0 - if len(z.mant) == 0 { - z.exp = 0 - f = z - return - } - // len(z.mant) > 0 - - // determine binary (exp2) and decimal (exp) exponent - exp2 := int64(len(z.mant)*_W - int(fnorm(z.mant))) - if ebase == 2 { - exp2 += exp - exp = 0 - } - if ecorr < 0 { - exp += int64(ecorr) - } - - z.setExp(exp2) - if exp == 0 { - // no decimal exponent - z.round(0) - f = z - return - } - // exp != 0 - - // compute decimal exponent power - expabs := exp - if expabs < 0 { - expabs = -expabs - } - powTen := new(Float).SetInt(new(Int).SetBits(nat(nil).expNN(natTen, nat(nil).setWord(Word(expabs)), nil))) - - // correct result - if exp < 0 { - z.uquo(z, powTen) - } else { - z.umul(z, powTen) - } - - f = z - return -} - -// Parse is like z.Scan(r, base), but instead of reading from an -// io.ByteScanner, it parses the string s. An error is returned if the -// string contains invalid or trailing characters not belonging to the -// number. -// -// TODO(gri) define possible errors more precisely -func (z *Float) Parse(s string, base int) (f *Float, b int, err error) { - r := strings.NewReader(s) - - if f, b, err = z.Scan(r, base); err != nil { - return - } - - // entire string must have been consumed - var ch byte - if ch, err = r.ReadByte(); err != io.EOF { - if err == nil { - err = fmt.Errorf("expected end of string, found %q", ch) - } - } - - 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 NewFloat(0, prec, 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) { - return NewFloat(0, prec, mode).Parse(s, base) -} - -// Format converts the floating-point number x to a string according -// to the given format and precision prec. The format is one of: -// -// 'e' -d.dddde±dd, decimal exponent -// 'E' -d.ddddE±dd, decimal exponent -// 'f' -ddddd.dddd, no exponent -// 'g' like 'e' for large exponents, like 'f' otherwise -// 'G' like 'E' for large exponents, like 'f' otherwise -// 'b' -ddddddp±dd, binary exponent -// 'p' -0x.dddp±dd, binary exponent, hexadecimal mantissa -// -// For the binary exponent formats, the mantissa is printed in normalized form: -// -// 'b' decimal integer mantissa using x.Precision() bits, or -0 -// 'p' hexadecimal fraction with 0.5 <= 0.mantissa < 1.0, or -0 -// -// The precision prec controls the number of digits (excluding the exponent) -// printed by the 'e', 'E', 'f', 'g', and 'G' formats. For 'e', 'E', and 'f' -// it is the number of digits after the decimal point. For 'g' and 'G' it is -// the total number of digits. A negative precision selects the smallest -// number of digits necessary such that ParseFloat will return f exactly. -// The prec value is ignored for the 'b' or 'p' format. -// -// BUG(gri) Currently, Format only accepts the 'b' and 'p' format. -func (x *Float) Format(format byte, prec int) string { - switch format { - case 'b': - return x.bstring() - case 'p': - return x.pstring() - } - return fmt.Sprintf(`%%!c(%s)`, format, x.pstring()) -} - -// BUG(gri): Currently, String uses the 'p' (rather than 'g') format. -func (x *Float) String() string { - return x.Format('p', 0) -} - -// TODO(gri) The 'b' and 'p' formats have different meanings here than -// in strconv: in strconv, the printed exponent is the biased (hardware) -// exponent; here it is the unbiased exponent. Decide what to do. -// (a strconv 'p' formatted float value can only be interpreted correctly -// if the bias is known; i.e., we must know if it's a 32bit or 64bit number). - -// bstring returns x as a string in the format ["-"] mantissa "p" exponent -// with a decimal mantissa and a binary exponent, or ["-"] "0" if x is zero. -// The mantissa is normalized such that is uses x.Precision() bits in binary -// representation. -func (x *Float) bstring() string { - // TODO(gri) handle Inf - if len(x.mant) == 0 { - if x.neg { - return "-0" - } - return "0" - } - // x != 0 - // normalize mantissa - m := x.mant - t := uint(len(x.mant)*_W) - x.prec // 0 <= t < _W - if t > 0 { - m = nat(nil).shr(m, t) - } - var buf bytes.Buffer - if x.neg { - buf.WriteByte('-') - } - buf.WriteString(m.decimalString()) - fmt.Fprintf(&buf, "p%d", x.exp) - return buf.String() -} - -// pstring returns x as a string in the format ["-"] "0x." mantissa "p" exponent -// with a hexadecimal mantissa and a binary exponent, or ["-"] "0" if x is zero. -// The mantissa is normalized such that 0.5 <= 0.mantissa < 1.0. -func (x *Float) pstring() string { - // TODO(gri) handle Inf - if len(x.mant) == 0 { - if x.neg { - return "-0" - } - return "0" - } - // x != 0 - // mantissa is stored in normalized form - var buf bytes.Buffer - if x.neg { - buf.WriteByte('-') - } - buf.WriteString("0x.") - buf.WriteString(strings.TrimRight(x.mant.hexString(), "0")) - fmt.Fprintf(&buf, "p%d", x.exp) - return buf.String() -} diff --git a/src/math/big/float_test.go b/src/math/big/float_test.go index 940cb6d353..3281f2745a 100644 --- a/src/math/big/float_test.go +++ b/src/math/big/float_test.go @@ -682,104 +682,3 @@ func TestFromBits(t *testing.T) { } } } - -var floatSetFloat64StringTests = []struct { - s string - x float64 -}{ - {"0", 0}, - {"-0", -0}, - {"+0", 0}, - {"1", 1}, - {"-1", -1}, - {"+1", 1}, - {"1.234", 1.234}, - {"-1.234", -1.234}, - {"+1.234", 1.234}, - {".1", 0.1}, - {"1.", 1}, - {"+1.", 1}, - - {"0e100", 0}, - {"-0e+100", 0}, - {"+0e-100", 0}, - {"0E100", 0}, - {"-0E+100", 0}, - {"+0E-100", 0}, - {"0p100", 0}, - {"-0p+100", 0}, - {"+0p-100", 0}, - - {"1.e10", 1e10}, - {"1e+10", 1e10}, - {"+1e-10", 1e-10}, - {"1E10", 1e10}, - {"1.E+10", 1e10}, - {"+1E-10", 1e-10}, - {"1p10", 1 << 10}, - {"1p+10", 1 << 10}, - {"+1.p-10", 1.0 / (1 << 10)}, - - {"-687436.79457e-245", -687436.79457e-245}, - {"-687436.79457E245", -687436.79457e245}, - {"1024.p-12", 0.25}, - {"-1.p10", -1024}, - {"0.25p2", 1}, - - {".0000000000000000000000000000000000000001", 1e-40}, - {"+10000000000000000000000000000000000000000e-0", 1e40}, -} - -func TestFloatSetFloat64String(t *testing.T) { - for _, test := range floatSetFloat64StringTests { - var x Float - x.prec = 53 // TODO(gri) find better solution - _, ok := x.SetString(test.s) - if !ok { - t.Errorf("%s: parse error", test.s) - continue - } - f, _ := x.Float64() - want := new(Float).SetFloat64(test.x) - if x.Cmp(want) != 0 { - t.Errorf("%s: got %s (%v); want %v", test.s, &x, f, test.x) - } - } -} - -func TestFloatFormat(t *testing.T) { - for _, test := range []struct { - x string - format byte - prec int - want string - }{ - {"0", 'b', 0, "0"}, - {"-0", 'b', 0, "-0"}, - {"1.0", 'b', 0, "4503599627370496p1"}, - {"-1.0", 'b', 0, "-4503599627370496p1"}, - - {"0", 'p', 0, "0"}, - {"-0", 'p', 0, "-0"}, - {"1024.0", 'p', 0, "0x.8p11"}, - {"-1024.0", 'p', 0, "-0x.8p11"}, - } { - f64, err := strconv.ParseFloat(test.x, 64) - if err != nil { - t.Error(err) - continue - } - f := new(Float).SetFloat64(f64) - got := f.Format(test.format, test.prec) - if got != test.want { - t.Errorf("%v: got %s", test, got) - } - if test.format == 'b' || test.format == 'p' { - continue // 'b', 'p' format not supported or different in strconv.Format - } - want := strconv.FormatFloat(f64, test.format, test.prec, 64) - if got != want { - t.Errorf("%v: got %s; want %s", test, got, want) - } - } -} diff --git a/src/math/big/floatconv.go b/src/math/big/floatconv.go new file mode 100644 index 0000000000..06bbdbcb02 --- /dev/null +++ b/src/math/big/floatconv.go @@ -0,0 +1,256 @@ +// Copyright 2015 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. + +// This file implements float-to-string conversion functions. + +package big + +import ( + "bytes" + "fmt" + "io" + "strings" +) + +// 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. +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.ReadRune(); err != io.EOF { + return nil, false + } + + return f, true +} + +// 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 returns the corresponding Float f, the actual base b, and an +// error err, if any. 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 or a value between 2 through MaxBase. +// +// 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 permitted. +// The octal prefix ``0'' is not supported. +// +// A "p" exponent indicates power of 2 for the exponent; for instance "1.2p3" +// with base 0 or 10 corresponds to the value 1.2 * 2**3. +// +// BUG(gri) This signature conflicts with Scan(s fmt.ScanState, ch rune) error. +func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) { + // sign + z.neg, err = scanSign(r) + if err != nil { + return + } + + // mantissa + var ecorr int // decimal exponent correction; valid if <= 0 + z.mant, b, ecorr, err = z.mant.scan(r, base, true) + if err != nil { + return + } + + // exponent + var exp int64 + var ebase int + exp, ebase, err = scanExponent(r) + if err != nil { + return + } + // special-case 0 + if len(z.mant) == 0 { + z.exp = 0 + f = z + return + } + // len(z.mant) > 0 + + // determine binary (exp2) and decimal (exp) exponent + exp2 := int64(len(z.mant)*_W - int(fnorm(z.mant))) + if ebase == 2 { + exp2 += exp + exp = 0 + } + if ecorr < 0 { + exp += int64(ecorr) + } + + z.setExp(exp2) + if exp == 0 { + // no decimal exponent + z.round(0) + f = z + return + } + // exp != 0 + + // compute decimal exponent power + expabs := exp + if expabs < 0 { + expabs = -expabs + } + powTen := new(Float).SetInt(new(Int).SetBits(nat(nil).expNN(natTen, nat(nil).setWord(Word(expabs)), nil))) + + // correct result + if exp < 0 { + z.uquo(z, powTen) + } else { + z.umul(z, powTen) + } + + f = z + return +} + +// Parse is like z.Scan(r, base), but instead of reading from an +// io.ByteScanner, it parses the string s. An error is returned if the +// string contains invalid or trailing characters not belonging to the +// number. +// +// TODO(gri) define possible errors more precisely +func (z *Float) Parse(s string, base int) (f *Float, b int, err error) { + r := strings.NewReader(s) + + if f, b, err = z.Scan(r, base); err != nil { + return + } + + // entire string must have been consumed + var ch byte + if ch, err = r.ReadByte(); err != io.EOF { + if err == nil { + err = fmt.Errorf("expected end of string, found %q", ch) + } + } + + 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 NewFloat(0, prec, 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) { + return NewFloat(0, prec, mode).Parse(s, base) +} + +// Format converts the floating-point number x to a string according +// to the given format and precision prec. The format is one of: +// +// 'e' -d.dddde±dd, decimal exponent +// 'E' -d.ddddE±dd, decimal exponent +// 'f' -ddddd.dddd, no exponent +// 'g' like 'e' for large exponents, like 'f' otherwise +// 'G' like 'E' for large exponents, like 'f' otherwise +// 'b' -ddddddp±dd, binary exponent +// 'p' -0x.dddp±dd, binary exponent, hexadecimal mantissa +// +// For the binary exponent formats, the mantissa is printed in normalized form: +// +// 'b' decimal integer mantissa using x.Precision() bits, or -0 +// 'p' hexadecimal fraction with 0.5 <= 0.mantissa < 1.0, or -0 +// +// The precision prec controls the number of digits (excluding the exponent) +// printed by the 'e', 'E', 'f', 'g', and 'G' formats. For 'e', 'E', and 'f' +// it is the number of digits after the decimal point. For 'g' and 'G' it is +// the total number of digits. A negative precision selects the smallest +// number of digits necessary such that ParseFloat will return f exactly. +// The prec value is ignored for the 'b' or 'p' format. +// +// BUG(gri) Currently, Format only accepts the 'b' and 'p' format. +func (x *Float) Format(format byte, prec int) string { + switch format { + case 'b': + return x.bstring() + case 'p': + return x.pstring() + } + return fmt.Sprintf(`%%!c(%s)`, format, x.pstring()) +} + +// BUG(gri): Currently, String uses the 'p' (rather than 'g') format. +func (x *Float) String() string { + return x.Format('p', 0) +} + +// TODO(gri) The 'b' and 'p' formats have different meanings here than +// in strconv: in strconv, the printed exponent is the biased (hardware) +// exponent; here it is the unbiased exponent. Decide what to do. +// (a strconv 'p' formatted float value can only be interpreted correctly +// if the bias is known; i.e., we must know if it's a 32bit or 64bit number). + +// bstring returns x as a string in the format ["-"] mantissa "p" exponent +// with a decimal mantissa and a binary exponent, or ["-"] "0" if x is zero. +// The mantissa is normalized such that is uses x.Precision() bits in binary +// representation. +func (x *Float) bstring() string { + // TODO(gri) handle Inf + if len(x.mant) == 0 { + if x.neg { + return "-0" + } + return "0" + } + // x != 0 + // normalize mantissa + m := x.mant + t := uint(len(x.mant)*_W) - x.prec // 0 <= t < _W + if t > 0 { + m = nat(nil).shr(m, t) + } + var buf bytes.Buffer + if x.neg { + buf.WriteByte('-') + } + buf.WriteString(m.decimalString()) + fmt.Fprintf(&buf, "p%d", x.exp) + return buf.String() +} + +// pstring returns x as a string in the format ["-"] "0x." mantissa "p" exponent +// with a hexadecimal mantissa and a binary exponent, or ["-"] "0" if x is zero. +// The mantissa is normalized such that 0.5 <= 0.mantissa < 1.0. +func (x *Float) pstring() string { + // TODO(gri) handle Inf + if len(x.mant) == 0 { + if x.neg { + return "-0" + } + return "0" + } + // x != 0 + // mantissa is stored in normalized form + var buf bytes.Buffer + if x.neg { + buf.WriteByte('-') + } + buf.WriteString("0x.") + buf.WriteString(strings.TrimRight(x.mant.hexString(), "0")) + fmt.Fprintf(&buf, "p%d", x.exp) + return buf.String() +} diff --git a/src/math/big/floatconv_test.go b/src/math/big/floatconv_test.go new file mode 100644 index 0000000000..83ea1d6057 --- /dev/null +++ b/src/math/big/floatconv_test.go @@ -0,0 +1,111 @@ +// Copyright 2015 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 big + +import ( + "strconv" + "testing" +) + +var floatSetFloat64StringTests = []struct { + s string + x float64 +}{ + {"0", 0}, + {"-0", -0}, + {"+0", 0}, + {"1", 1}, + {"-1", -1}, + {"+1", 1}, + {"1.234", 1.234}, + {"-1.234", -1.234}, + {"+1.234", 1.234}, + {".1", 0.1}, + {"1.", 1}, + {"+1.", 1}, + + {"0e100", 0}, + {"-0e+100", 0}, + {"+0e-100", 0}, + {"0E100", 0}, + {"-0E+100", 0}, + {"+0E-100", 0}, + {"0p100", 0}, + {"-0p+100", 0}, + {"+0p-100", 0}, + + {"1.e10", 1e10}, + {"1e+10", 1e10}, + {"+1e-10", 1e-10}, + {"1E10", 1e10}, + {"1.E+10", 1e10}, + {"+1E-10", 1e-10}, + {"1p10", 1 << 10}, + {"1p+10", 1 << 10}, + {"+1.p-10", 1.0 / (1 << 10)}, + + {"-687436.79457e-245", -687436.79457e-245}, + {"-687436.79457E245", -687436.79457e245}, + {"1024.p-12", 0.25}, + {"-1.p10", -1024}, + {"0.25p2", 1}, + + {".0000000000000000000000000000000000000001", 1e-40}, + {"+10000000000000000000000000000000000000000e-0", 1e40}, +} + +func TestFloatSetFloat64String(t *testing.T) { + for _, test := range floatSetFloat64StringTests { + var x Float + x.prec = 53 // TODO(gri) find better solution + _, ok := x.SetString(test.s) + if !ok { + t.Errorf("%s: parse error", test.s) + continue + } + f, _ := x.Float64() + want := new(Float).SetFloat64(test.x) + if x.Cmp(want) != 0 { + t.Errorf("%s: got %s (%v); want %v", test.s, &x, f, test.x) + } + } +} + +func TestFloatFormat(t *testing.T) { + for _, test := range []struct { + x string + format byte + prec int + want string + }{ + {"0", 'b', 0, "0"}, + {"-0", 'b', 0, "-0"}, + {"1.0", 'b', 0, "4503599627370496p1"}, + {"-1.0", 'b', 0, "-4503599627370496p1"}, + + {"0", 'p', 0, "0"}, + {"-0", 'p', 0, "-0"}, + {"1024.0", 'p', 0, "0x.8p11"}, + {"-1024.0", 'p', 0, "-0x.8p11"}, + } { + f64, err := strconv.ParseFloat(test.x, 64) + if err != nil { + t.Error(err) + continue + } + f := new(Float).SetFloat64(f64) + got := f.Format(test.format, test.prec) + if got != test.want { + t.Errorf("%v: got %s", test, got) + } + if test.format == 'b' || test.format == 'p' { + continue // 'b', 'p' format not supported or different in strconv.Format + } + want := strconv.FormatFloat(f64, test.format, test.prec, 64) + if got != want { + t.Errorf("%v: got %s; want %s", test, got, want) + } + } +} -- 2.48.1