]> Cypherpunks repositories - gostls13.git/commitdiff
math/big: fix several issues with string->Float conversion
authorRobert Griesemer <gri@golang.org>
Sat, 14 Feb 2015 01:57:26 +0000 (17:57 -0800)
committerRobert Griesemer <gri@golang.org>
Tue, 24 Feb 2015 17:35:25 +0000 (17:35 +0000)
Change-Id: I7bf7154e2d8d779fdf7f1d2bb561a06ad174f3b0
Reviewed-on: https://go-review.googlesource.com/4883
Reviewed-by: Alan Donovan <adonovan@google.com>
src/math/big/float.go
src/math/big/floatconv.go
src/math/big/floatconv_test.go

index 47755f2719296c909981a071ab3eb105323e1ba6..a89ef1021a9ba4d2a66e7f06439001bc479c7caa 100644 (file)
@@ -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
index 207e34a46c9917c05e298d91ec8f3a72923f389b..96ccd601da6e2b0eef87c211a02b365df9d1300e 100644 (file)
@@ -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
 }
 
index 4a91a63e64276888699175f258ab25d6eed352e3..e7920d0c079882a196c5eb80349e80f1ffb4ce3f 100644 (file)
@@ -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)