Skip to content

Commit

Permalink
Complete and optimized Decimal128 support.
Browse files Browse the repository at this point in the history
Parsing in ~170ns, string in ~700ns.
  • Loading branch information
niemeyer committed Apr 6, 2016
1 parent 72aab81 commit 14a4475
Show file tree
Hide file tree
Showing 5 changed files with 621 additions and 311 deletions.
102 changes: 0 additions & 102 deletions bson/bson.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,108 +389,6 @@ type undefined struct{}
// Undefined represents the undefined BSON value.
var Undefined undefined

// Decimal128 holds decimal128 BSON values.
type Decimal128 struct {
l, h uint64
}

func (d Decimal128) String() string {
var pos int // positive sign
var e int // exponent
var h, l uint64 // significand high/low

if d.h>>63&1 == 0 {
pos = 1
}

switch d.h >> 58 & (1<<5 - 1) {
case 0x1F:
return "NaN"
case 0x1E:
return "-Inf"[pos:]
}

l = d.l
if d.h>>61&3 == 3 {
// Bits: 1*sign 2*ignored 14*exponent 111*significand.
// Implicit 0b100 prefix in significand.
e = int(d.h>>47&(1<<14-1)) - 6176
//h = 4<<47 | d.h&(1<<47-1)
// Spec says all of these values are out of range.
h, l = 0, 0
} else {
// Bits: 1*sign 14*exponent 113*significand
e = int(d.h>>49&(1<<14-1)) - 6176
h = d.h & (1<<49 - 1)
}

// Would be handled by the logic below, but that's trivial and common.
if h == 0 && l == 0 && e == 0 {
return "-0"[pos:]
}

var repr [48]byte // Loop 5 times over 9 digits plus dot, negative sign, and leading zero.
var last = len(repr)
var i = len(repr)
var dot = len(repr) + e
var rem uint32
Loop:
for d9 := 0; d9 < 5; d9++ {
h, l, rem = divmod(h, l, 1e9)
for d1 := 0; d1 < 9; d1++ {
c := '0' + byte(rem%10)
rem /= 10
i--
repr[i] = c
// Handle "0E+3", "1E+3", etc.
if l == 0 && h == 0 && rem == 0 && i == len(repr)-1 && (dot < i-6 || e > 0) {
last = i
break Loop
}
if c != '0' {
last = i
}
// Handle "-0.0", "0.00123400", "-1.00E-6", "1.050E+3", etc.
if dot == i || l == 0 && h == 0 && rem > 0 && rem < 10 && (dot < i-6 || e > 0) {
e += len(repr) - i
i--
repr[i] = '.'
repr[i-1] = '0'
last = i - 1
dot = len(repr) // Unmark.
}
// Break early. Works without it, but why.
if dot > i && l == 0 && h == 0 && rem == 0 {
break Loop
}
}
}
repr[last-1] = '-'
last--

if e != 0 {
return fmt.Sprintf("%sE%+d", repr[last+pos:], e)
}
return string(repr[last+pos:])
}

func divmod(h, l uint64, div uint32) (qh, ql uint64, rem uint32) {
div64 := uint64(div)
a := h >> 32
aq := a / div64
ar := a % div64
b := ar<<32 + h&(1<<32-1)
bq := b / div64
br := b % div64
c := br<<32 + l>>32
cq := c / div64
cr := c % div64
d := cr<<32 + l&(1<<32-1)
dq := d / div64
dr := d % div64
return (aq<<32 | bq), (cq<<32 | dq), uint32(dr)
}

// Binary is a representation for non-standard binary values. Any kind should
// work, but the following are known as of this writing:
//
Expand Down
42 changes: 0 additions & 42 deletions bson/bson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1639,48 +1639,6 @@ func (s *S) TestSpecTests(c *C) {
}
}

// --------------------------------------------------------------------------
// Decimal tests

type decimalTests struct {
Valid []struct {
Description string `json:"description"`
Subject string `json:"subject"`
String string `json:"string"`
ExtJSON struct {
D struct {
NumberDecimal string `json:"$numberDecimal"`
} `json:"d"`
} `json:"extjson"`
} `json:"valid"`
}

func (s *S) TestDecimalTests(c *C) {
var tests decimalTests
err := json.Unmarshal([]byte(decimalTestsJSON), &tests)
c.Assert(err, IsNil)

// These also conform to the spec and are used by Go elsewhere.
goStr := map[string]string{
"Infinity": "Inf",
"-Infinity": "-Inf",
}

for _, test := range tests.Valid {
c.Logf("Running decimal128 test: %s", test.Description)
subject, err := hex.DecodeString(test.Subject)
var value struct{ D interface{} }
err = bson.Unmarshal(subject, &value)
c.Assert(err, IsNil)
d, isDecimal := value.D.(bson.Decimal128)
c.Assert(isDecimal, Equals, true)
if s, ok := goStr[test.String]; ok {
test.String = s
}
c.Assert(d.String(), Equals, test.String)
}
}

// --------------------------------------------------------------------------
// ObjectId Text encoding.TextUnmarshaler.

Expand Down
Loading

0 comments on commit 14a4475

Please sign in to comment.