Skip to content

Commit e4ba400

Browse files
committed
math/big: accept non-decimal floats with Rat.SetString
This fixes an old oversight. Rat.SetString already permitted fractions a/b where both a and b could independently specify a base prefix. With this CL, it now also accepts non-decimal floating-point numbers. Fixes #29799. Change-Id: I9cc65666a5cebb00f0202da2e4fc5654a02e3234 Reviewed-on: https://go-review.googlesource.com/c/go/+/168237 Reviewed-by: Emmanuel Odeke <[email protected]>
1 parent a591fd0 commit e4ba400

File tree

4 files changed

+133
-38
lines changed

4 files changed

+133
-38
lines changed

src/math/big/floatconv.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
7070
}
7171
// len(z.mant) > 0
7272

73-
// The mantissa may have a decimal point (fcount <= 0) and there
74-
// may be a nonzero exponent exp. The decimal point amounts to a
73+
// The mantissa may have a radix point (fcount <= 0) and there
74+
// may be a nonzero exponent exp. The radix point amounts to a
7575
// division by b**(-fcount). An exponent means multiplication by
7676
// ebase**exp. Finally, mantissa normalization (shift left) requires
7777
// a correcting multiplication by 2**(-shiftcount). Multiplications
@@ -85,11 +85,11 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
8585
exp2 := int64(len(z.mant))*_W - fnorm(z.mant)
8686
exp5 := int64(0)
8787

88-
// determine binary or decimal exponent contribution of decimal point
88+
// determine binary or decimal exponent contribution of radix point
8989
if fcount < 0 {
90-
// The mantissa has a "decimal" point ddd.dddd; and
91-
// -fcount is the number of digits to the right of '.'.
92-
// Adjust relevant exponent accordingly.
90+
// The mantissa has a radix point ddd.dddd; and
91+
// -fcount is the number of digits to the right
92+
// of '.'. Adjust relevant exponent accordingly.
9393
d := int64(fcount)
9494
switch b {
9595
case 10:
@@ -111,7 +111,7 @@ func (z *Float) scan(r io.ByteScanner, base int) (f *Float, b int, err error) {
111111
switch ebase {
112112
case 10:
113113
exp5 += exp
114-
fallthrough
114+
fallthrough // see fallthrough above
115115
case 2:
116116
exp2 += exp
117117
default:

src/math/big/nat.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ import (
3535
type nat []Word
3636

3737
var (
38-
natOne = nat{1}
39-
natTwo = nat{2}
40-
natTen = nat{10}
38+
natOne = nat{1}
39+
natTwo = nat{2}
40+
natFive = nat{5}
41+
natTen = nat{10}
4142
)
4243

4344
func (z nat) clear() {

src/math/big/ratconv.go

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,22 @@ func (z *Rat) Scan(s fmt.ScanState, ch rune) error {
3838
}
3939

4040
// SetString sets z to the value of s and returns z and a boolean indicating
41-
// success. s can be given as a fraction "a/b" or as a decimal floating-point
42-
// number optionally followed by an exponent. The entire string (not just a prefix)
43-
// must be valid for success. If the operation failed, the value of z is
44-
// undefined but the returned value is nil.
41+
// success. s can be given as a (possibly signed) fraction "a/b", or as a
42+
// floating-point number optionally followed by an exponent.
43+
// If a fraction is provided, both the dividend and the divisor may be a
44+
// decimal integer or independently use a prefix of ``0b'', ``0'' or ``0o'',
45+
// or ``0x'' (or their upper-case variants) to denote a binary, octal, or
46+
// hexadecimal integer, respectively. The divisor may not be signed.
47+
// If a floating-point number is provided, it may be in decimal form or
48+
// use any of the same prefixes as above but for ``0'' to denote a non-decimal
49+
// mantissa. A leading ``0'' is considered a decimal leading 0; it does not
50+
// indicate octal representation in this case.
51+
// An optional base-10 ``e'' or base-2 ``p'' (or their upper-case variants)
52+
// exponent may be provided as well, except for hexadecimal floats which
53+
// only accept an (optional) ``p'' exponent (because an ``e'' or ``E'' cannot
54+
// be distinguished from a mantissa digit).
55+
// The entire string, not just a prefix, must be valid for success. If the
56+
// operation failed, the value of z is undefined but the returned value is nil.
4557
func (z *Rat) SetString(s string) (*Rat, bool) {
4658
if len(s) == 0 {
4759
return nil, false
@@ -78,16 +90,17 @@ func (z *Rat) SetString(s string) (*Rat, bool) {
7890
}
7991

8092
// mantissa
81-
// TODO(gri) allow other bases besides 10 for mantissa and exponent? (issue #29799)
82-
var ecorr int
83-
z.a.abs, _, ecorr, err = z.a.abs.scan(r, 10, true)
93+
var base int
94+
var fcount int // fractional digit count; valid if <= 0
95+
z.a.abs, base, fcount, err = z.a.abs.scan(r, 0, true)
8496
if err != nil {
8597
return nil, false
8698
}
8799

88100
// exponent
89101
var exp int64
90-
exp, _, err = scanExponent(r, false, false)
102+
var ebase int
103+
exp, ebase, err = scanExponent(r, true, true)
91104
if err != nil {
92105
return nil, false
93106
}
@@ -103,30 +116,91 @@ func (z *Rat) SetString(s string) (*Rat, bool) {
103116
}
104117
// len(z.a.abs) > 0
105118

106-
// correct exponent
107-
if ecorr < 0 {
108-
exp += int64(ecorr)
119+
// The mantissa may have a radix point (fcount <= 0) and there
120+
// may be a nonzero exponent exp. The radix point amounts to a
121+
// division by base**(-fcount), which equals a multiplication by
122+
// base**fcount. An exponent means multiplication by ebase**exp.
123+
// Multiplications are commutative, so we can apply them in any
124+
// order. We only have powers of 2 and 10, and we split powers
125+
// of 10 into the product of the same powers of 2 and 5. This
126+
// may reduce the the size of shift/multiplication factors or
127+
// divisors required to create the final fraction, depending
128+
// on the actual floating-point value.
129+
130+
// determine binary or decimal exponent contribution of radix point
131+
var exp2, exp5 int64
132+
if fcount < 0 {
133+
// The mantissa has a radix point ddd.dddd; and
134+
// -fcount is the number of digits to the right
135+
// of '.'. Adjust relevant exponent accordingly.
136+
d := int64(fcount)
137+
switch base {
138+
case 10:
139+
exp5 = d
140+
fallthrough // 10**e == 5**e * 2**e
141+
case 2:
142+
exp2 = d
143+
case 8:
144+
exp2 = d * 3 // octal digits are 3 bits each
145+
case 16:
146+
exp2 = d * 4 // hexadecimal digits are 4 bits each
147+
default:
148+
panic("unexpected mantissa base")
149+
}
150+
// fcount consumed - not needed anymore
109151
}
110152

111-
// compute exponent power
112-
expabs := exp
113-
if expabs < 0 {
114-
expabs = -expabs
153+
// take actual exponent into account
154+
switch ebase {
155+
case 10:
156+
exp5 += exp
157+
fallthrough // see fallthrough above
158+
case 2:
159+
exp2 += exp
160+
default:
161+
panic("unexpected exponent base")
162+
}
163+
// exp consumed - not needed anymore
164+
165+
// compute pow5 if needed
166+
pow5 := z.b.abs
167+
if exp5 != 0 {
168+
n := exp5
169+
if n < 0 {
170+
n = -n
171+
}
172+
pow5 = pow5.expNN(natFive, nat(nil).setWord(Word(n)), nil)
115173
}
116-
powTen := nat(nil).expNN(natTen, nat(nil).setWord(Word(expabs)), nil)
117174

118-
// complete fraction
119-
if exp < 0 {
120-
z.b.abs = powTen
121-
z.norm()
122-
} else {
123-
z.a.abs = z.a.abs.mul(z.a.abs, powTen)
124-
z.b.abs = z.b.abs[:0]
175+
// apply dividend contributions of exponents
176+
// (start with exp5 so the numbers to multiply are smaller)
177+
if exp5 > 0 {
178+
z.a.abs = z.a.abs.mul(z.a.abs, pow5)
179+
exp5 = 0
180+
}
181+
if exp2 > 0 {
182+
if int64(uint(exp2)) != exp2 {
183+
panic("exponent too large")
184+
}
185+
z.a.abs = z.a.abs.shl(z.a.abs, uint(exp2))
186+
exp2 = 0
187+
}
188+
189+
// apply divisor contributions of exponents
190+
z.b.abs = z.b.abs.setWord(1)
191+
if exp5 < 0 {
192+
z.b.abs = pow5
193+
}
194+
if exp2 < 0 {
195+
if int64(uint(-exp2)) != -exp2 {
196+
panic("exponent too large")
197+
}
198+
z.b.abs = z.b.abs.shl(z.b.abs, uint(-exp2))
125199
}
126200

127201
z.a.neg = neg && len(z.a.abs) > 0 // 0 has no sign
128202

129-
return z, true
203+
return z.norm(), true
130204
}
131205

132206
// scanExponent scans the longest possible prefix of r representing a base 10
@@ -250,7 +324,7 @@ func (x *Rat) RatString() string {
250324
}
251325

252326
// FloatString returns a string representation of x in decimal form with prec
253-
// digits of precision after the decimal point. The last digit is rounded to
327+
// digits of precision after the radix point. The last digit is rounded to
254328
// nearest, with halves rounded away from zero.
255329
func (x *Rat) FloatString(prec int) string {
256330
var buf []byte

src/math/big/ratconv_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,49 @@ var setStringTests = []StringTest{
135135
var setStringTests2 = []StringTest{
136136
// invalid
137137
{in: "4/3x"},
138+
{in: "0/-1"},
139+
{in: "-1/-1"},
138140

139141
// invalid with separators
140142
// (smoke tests only - a comprehensive set of tests is in natconv_test.go)
141143
{in: "10_/1"},
142144
{in: "_10/1"},
143145
{in: "1/1__0"},
144-
{in: "1_000.0"}, // floats are base 10 which doesn't permit separators; see also issue #29799
145146

146147
// valid
147148
{"0b1000/3", "8/3", true},
148149
{"0B1000/0x8", "1", true},
149-
{"-010/1", "-8", true},
150-
{"-010.", "-10", true},
150+
{"-010/1", "-8", true}, // 0-prefix indicates octal in this case
151+
{"-010.0", "-10", true},
151152
{"-0o10/1", "-8", true},
152153
{"0x10/1", "16", true},
153154
{"0x10/0x20", "1/2", true},
154155

156+
{"0010", "10", true}, // 0-prefix is ignored in this case (not a fraction)
157+
{"0x10.0", "16", true},
158+
{"0x1.8", "3/2", true},
159+
{"0X1.8p4", "24", true},
160+
{"0x1.1E2", "2289/2048", true}, // E is part of hex mantissa, not exponent
161+
{"0b1.1E2", "150", true},
162+
{"0B1.1P3", "12", true},
163+
{"0o10e-2", "2/25", true},
164+
{"0O10p-3", "1", true},
165+
155166
// valid with separators
156167
// (smoke tests only - a comprehensive set of tests is in natconv_test.go)
157168
{"0b_1000/3", "8/3", true},
158169
{"0B_10_00/0x8", "1", true},
159170
{"0xdead/0B1101_1110_1010_1101", "1", true},
160171
{"0B1101_1110_1010_1101/0XD_E_A_D", "1", true},
172+
{"1_000.0", "1000", true},
173+
174+
{"0x_10.0", "16", true},
175+
{"0x1_0.0", "16", true},
176+
{"0x1.8_0", "3/2", true},
177+
{"0X1.8p0_4", "24", true},
178+
{"0b1.1_0E2", "150", true},
179+
{"0o1_0e-2", "2/25", true},
180+
{"0O_10p-3", "1", true},
161181
}
162182

163183
func TestRatSetString(t *testing.T) {

0 commit comments

Comments
 (0)