Skip to content

Commit b1ab40e

Browse files
committed
GODRIVER-1519 Fix parsing decimal128 zero values with large exponents. (#1277)
1 parent eadc7dd commit b1ab40e

File tree

2 files changed

+35
-7
lines changed

2 files changed

+35
-7
lines changed

bson/primitive/decimal.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ func ParseDecimal128(s string) (Decimal128, error) {
328328
return dErr(s)
329329
}
330330

331+
// Parse the significand (i.e. the non-exponent part) as a big.Int.
331332
bi, ok := new(big.Int).SetString(intPart+decPart, 10)
332333
if !ok {
333334
return dErr(s)
@@ -360,6 +361,19 @@ func ParseDecimal128FromBigInt(bi *big.Int, exp int) (Decimal128, bool) {
360361
q := new(big.Int)
361362
r := new(big.Int)
362363

364+
// If the significand is zero, the logical value will always be zero, independent of the
365+
// exponent. However, the loops for handling out-of-range exponent values below may be extremely
366+
// slow for zero values because the significand never changes. Limit the exponent value to the
367+
// supported range here to prevent entering the loops below.
368+
if bi.Cmp(zero) == 0 {
369+
if exp > MaxDecimal128Exp {
370+
exp = MaxDecimal128Exp
371+
}
372+
if exp < MinDecimal128Exp {
373+
exp = MinDecimal128Exp
374+
}
375+
}
376+
363377
for bigIntCmpAbs(bi, maxS) == 1 {
364378
bi, _ = q.QuoRem(bi, ten, r)
365379
if r.Cmp(zero) != 0 {

bson/primitive/decimal_test.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,20 +141,34 @@ func TestParseDecimal128(t *testing.T) {
141141
bigIntTestCase{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"},
142142
bigIntTestCase{s: ".125e1", h: 0x303c000000000000, l: 125},
143143
bigIntTestCase{s: ".125", h: 0x303a000000000000, l: 125},
144+
// Test that parsing negative zero returns negative zero with a zero exponent.
145+
bigIntTestCase{s: "-0", h: 0xb040000000000000, l: 0},
146+
// Test that parsing negative zero with an in-range exponent returns negative zero and
147+
// preserves the specified exponent value.
148+
bigIntTestCase{s: "-0E999", h: 0xb80e000000000000, l: 0},
149+
// Test that parsing zero with an out-of-range positive exponent returns zero with the
150+
// maximum positive exponent (i.e. 0e+6111).
151+
bigIntTestCase{s: "0E2000000000000", h: 0x5ffe000000000000, l: 0},
152+
// Test that parsing zero with an out-of-range negative exponent returns zero with the
153+
// minimum negative exponent (i.e. 0e-6176).
154+
bigIntTestCase{s: "-0E2000000000000", h: 0xdffe000000000000, l: 0},
144155
bigIntTestCase{s: "", remark: "parse fail"})
145156

146157
for _, c := range cases {
147158
t.Run(c.s, func(t *testing.T) {
148159
switch c.remark {
149160
case "overflow", "parse fail":
150161
_, err := ParseDecimal128(c.s)
151-
require.Error(t, err)
152-
case "", "rounding", "subnormal", "clamped", "NaN", "Infinity", "-Infinity":
153-
d128, err := ParseDecimal128(c.s)
154-
require.NoError(t, err)
155-
156-
require.Equal(t, c.h, d128.h, "case %s", c.s, d128.l)
157-
require.Equal(t, c.l, d128.l, "case %s", c.s, d128.h)
162+
assert.Error(t, err, "ParseDecimal128(%q) should return an error", c.s)
163+
default:
164+
got, err := ParseDecimal128(c.s)
165+
require.NoError(t, err, "ParseDecimal128(%q) error", c.s)
166+
167+
want := Decimal128{h: c.h, l: c.l}
168+
// Decimal128 doesn't implement an equality function, so compare the expected
169+
// low/high uint64 values directly. Also print the string representation of each
170+
// number to make debugging failures easier.
171+
assert.Equal(t, want, got, "ParseDecimal128(%q) = %s, want %s", c.s, got, want)
158172
}
159173
})
160174
}

0 commit comments

Comments
 (0)