From d9859ad40438cc27415ec294e9a06bb58fa24e9a Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 13 Feb 2015 17:57:26 -0800 Subject: [PATCH] math/big: fix several issues with string->Float conversion Change-Id: I7bf7154e2d8d779fdf7f1d2bb561a06ad174f3b0 Reviewed-on: https://go-review.googlesource.com/4883 Reviewed-by: Alan Donovan --- src/math/big/float.go | 12 ++--- src/math/big/floatconv.go | 99 ++++++++++++++++++++++++---------- src/math/big/floatconv_test.go | 62 +++++++++++++++++---- 3 files changed, 128 insertions(+), 45 deletions(-) diff --git a/src/math/big/float.go b/src/math/big/float.go index 47755f2719..a89ef1021a 100644 --- a/src/math/big/float.go +++ b/src/math/big/float.go @@ -537,7 +537,7 @@ func (z *Float) SetFloat64(x float64) *Float { // fnorm normalizes mantissa m by shifting it to the left // such that the msb of the most-significant word (msw) is 1. // It returns the shift amount. It assumes that len(m) != 0. -func fnorm(m nat) uint { +func fnorm(m nat) int64 { if debugFloat && (len(m) == 0 || m[len(m)-1] == 0) { panic("msw of mantissa is 0") } @@ -548,7 +548,7 @@ func fnorm(m nat) uint { panic("nlz or shlVU incorrect") } } - return s + return int64(s) } // SetInt sets z to the (possibly rounded) value of x and returns z. @@ -884,7 +884,7 @@ func (z *Float) uadd(x, y *Float) { } // len(z.mant) > 0 - z.setExp(ex + int64(len(z.mant))*_W - int64(fnorm(z.mant))) + z.setExp(ex + int64(len(z.mant))*_W - fnorm(z.mant)) z.round(0) } @@ -926,7 +926,7 @@ func (z *Float) usub(x, y *Float) { } // len(z.mant) > 0 - z.setExp(ex + int64(len(z.mant))*_W - int64(fnorm(z.mant))) + z.setExp(ex + int64(len(z.mant))*_W - fnorm(z.mant)) z.round(0) } @@ -947,7 +947,7 @@ func (z *Float) umul(x, y *Float) { z.mant = z.mant.mul(x.mant, y.mant) // normalize mantissa - z.setExp(e - int64(fnorm(z.mant))) + z.setExp(e - fnorm(z.mant)) z.round(0) } @@ -986,7 +986,7 @@ func (z *Float) uquo(x, y *Float) { e := int64(x.exp) - int64(y.exp) - int64(d-len(z.mant))*_W // normalize mantissa - z.setExp(e - int64(fnorm(z.mant))) + z.setExp(e - fnorm(z.mant)) // The result is long enough to include (at least) the rounding bit. // If there's a non-zero remainder, the corresponding fractional part diff --git a/src/math/big/floatconv.go b/src/math/big/floatconv.go index 207e34a46c..96ccd601da 100644 --- a/src/math/big/floatconv.go +++ b/src/math/big/floatconv.go @@ -25,7 +25,7 @@ func (z *Float) SetString(s string) (*Float, bool) { } // there should be no unread characters left - if _, _, err = r.ReadRune(); err != io.EOF { + if _, err = r.ReadByte(); err != io.EOF { return nil, false } @@ -35,8 +35,10 @@ func (z *Float) SetString(s string) (*Float, bool) { // 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: +// 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 = "+" | "-" . @@ -50,16 +52,23 @@ func (z *Float) SetString(s string) (*Float, bool) { // 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 +// "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. +// The octal prefix "0" is not supported (a leading "0" is simply +// considered a "0"). // -// 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. +// 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). // // BUG(gri) This signature conflicts with Scan(s fmt.ScanState, ch rune) error. -// TODO(gri) What should the default precision be? func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) { + if z.prec == 0 { + z.prec = 64 + } + // sign z.neg, err = scanSign(r) if err != nil { @@ -67,8 +76,8 @@ func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) { } // mantissa - var ecorr int // decimal exponent correction; valid if <= 0 - z.mant, b, ecorr, err = z.mant.scan(r, base, true) + var fcount int // fractional digit count; valid if <= 0 + z.mant, b, fcount, err = z.mant.scan(r, base, true) if err != nil { return } @@ -80,48 +89,82 @@ func (z *Float) Scan(r io.ByteScanner, base int) (f *Float, b int, err error) { if err != nil { return } + + // set result + f = z + // special-case 0 if len(z.mant) == 0 { + z.acc = Exact 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))) + // The mantissa may have a decimal point (fcount <= 0) and there + // may be a nonzero exponent exp. The decimal point amounts to a + // division by b**(-fcount). An exponent means multiplication by + // ebase**exp. Finally, mantissa normalization (shift left) requires + // a correcting multiplication by 2**(-shiftcount). Multiplications + // are commutative, so we can apply them in any order as long as there + // is no loss of precision. We only have powers of 2 and 10; keep + // track via separate exponents exp2 and exp10. + + // normalize mantissa and get initial binary exponent + var exp2 = int64(len(z.mant))*_W - fnorm(z.mant) + + // determine binary or decimal exponent contribution of decimal point + var exp10 int64 + if fcount < 0 { + // The mantissa has a "decimal" point ddd.dddd; and + // -fcount is the number of digits to the right of '.'. + // Adjust relevant exponent accodingly. + switch b { + case 16: + fcount *= 4 // hexadecimal digits are 4 bits each + fallthrough + case 2: + exp2 += int64(fcount) + default: // b == 10 + exp10 = int64(fcount) + } + // we don't need fcount anymore + } + + // take actual exponent into account if ebase == 2 { exp2 += exp - exp = 0 - } - if ecorr < 0 { - exp += int64(ecorr) + } else { // ebase == 10 + exp10 += exp } + // we don't need exp anymore + // apply 2**exp2 z.setExp(exp2) - if exp == 0 { - // no decimal exponent + + if exp10 == 0 { + // no decimal exponent to consider z.round(0) - f = z return } - // exp != 0 + // exp10 != 0 // compute decimal exponent power - expabs := exp + expabs := exp10 if expabs < 0 { expabs = -expabs } - powTen := new(Float).SetInt(new(Int).SetBits(nat(nil).expNN(natTen, nat(nil).setWord(Word(expabs)), nil))) + powTen := nat(nil).expNN(natTen, nat(nil).setUint64(uint64(expabs)), nil) + fpowTen := new(Float).SetInt(new(Int).SetBits(powTen)) - // correct result - if exp < 0 { - z.uquo(z, powTen) + // apply 10**exp10 + // (uquo and umul do the rounding) + if exp10 < 0 { + z.uquo(z, fpowTen) } else { - z.umul(z, powTen) + z.umul(z, fpowTen) } - f = z return } diff --git a/src/math/big/floatconv_test.go b/src/math/big/floatconv_test.go index 4a91a63e64..e7920d0c07 100644 --- a/src/math/big/floatconv_test.go +++ b/src/math/big/floatconv_test.go @@ -15,6 +15,7 @@ func TestFloatSetFloat64String(t *testing.T) { s string x float64 }{ + // basics {"0", 0}, {"-0", -0}, {"+0", 0}, @@ -28,34 +29,69 @@ func TestFloatSetFloat64String(t *testing.T) { {"1.", 1}, {"+1.", 1}, + // various zeros {"0e100", 0}, {"-0e+100", 0}, {"+0e-100", 0}, {"0E100", 0}, {"-0E+100", 0}, {"+0E-100", 0}, - {"0p100", 0}, - {"-0p+100", 0}, - {"+0p-100", 0}, + // various decimal exponent formats {"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)}, + // misc decimal values + {"3.14159265", 3.14159265}, {"-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}, + + // decimal mantissa, binary exponent + {"0p0", 0}, + {"-0p0", -0}, + {"1p10", 1 << 10}, + {"1p+10", 1 << 10}, + {"+1p-10", 1.0 / (1 << 10)}, + {"1024p-12", 0.25}, + {"-1p10", -1024}, + {"1.5p1", 3}, + + // binary mantissa, decimal exponent + {"0b0", 0}, + {"-0b0", -0}, + {"0b0e+10", 0}, + {"-0b0e-10", -0}, + {"0b1010", 10}, + {"0B1010E2", 1000}, + {"0b.1", 0.5}, + {"0b.001", 0.125}, + {"0b.001e3", 125}, + + // binary mantissa, binary exponent + {"0b0p+10", 0}, + {"-0b0p-10", -0}, + {"0b.1010p4", 10}, + {"0b1p-1", 0.5}, + {"0b001p-3", 0.125}, + {"0b.001p3", 1}, + {"0b0.01p2", 1}, + + // hexadecimal mantissa and exponent + {"0x0", 0}, + {"-0x0", -0}, + {"0x0p+10", 0}, + {"-0x0p-10", -0}, + {"0xff", 255}, + {"0X.8p1", 1}, + {"-0X0.00008p16", -0.5}, + {"0x0.0000000000001p-1022", math.SmallestNonzeroFloat64}, + {"0x1.fffffffffffffp1023", math.MaxFloat64}, } { var x Float x.SetPrec(53) @@ -341,7 +377,11 @@ func TestFloatFormat(t *testing.T) { // and its output for 0.0 prints a biased exponent value // as in 0p-1074 which makes no sense to emulate here) if test.prec == 53 && test.format != 'p' && f.Sign() != 0 { - f64, _ := f.Float64() + f64, acc := f.Float64() + if acc != Exact { + t.Errorf("%v: expected exact conversion to float64", test) + continue + } got := strconv.FormatFloat(f64, test.format, test.digits, 64) if got != test.want { t.Errorf("%v: got %s; want %s", test, got, test.want) -- 2.48.1