Skip to content

Commit 1e197fb

Browse files
committed
DecodeBCD: use strings.Builder and optimize decoding [TO SQUASH]
1 parent 2259dff commit 1e197fb

File tree

1 file changed

+61
-36
lines changed

1 file changed

+61
-36
lines changed

decimal/bcd.go

+61-36
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ var isNegative = map[byte]bool{
6363

6464
// Calculate a number of digits in a buffer with decimal number.
6565
//
66-
// Plus, minus, point and leading zeros do not count.
66+
// Plus, minus, point and leading zeroes do not count.
6767
// Contains a quirk for a zero - returns 1.
6868
//
6969
// Examples (see more examples in tests):
@@ -125,7 +125,7 @@ func EncodeStringToBCD(buf string) ([]byte, error) {
125125
scale := 0 // By default decimal number is integer.
126126
var byteBuf []byte
127127
for i, ch := range []byte(buf) {
128-
// Skip leading zeros.
128+
// Skip leading zeroes.
129129
if (len(byteBuf) == 0) && ch == '0' {
130130
continue
131131
}
@@ -179,52 +179,77 @@ func EncodeStringToBCD(buf string) ([]byte, error) {
179179
// converted to a string. The BCD array is assumed full of digits, and must be
180180
// ended by a 4-bit sign nibble in the least significant four bits of the final
181181
// byte. The scale is used (negated) as the exponent of the decimal number.
182-
// Note that zeros may have a sign and/or a scale.
182+
// Note that zeroes may have a sign and/or a scale.
183183
func DecodeStringFromBCD(bcdBuf []byte) (string, error) {
184-
const scaleIdx = 0 // Index of a byte with scale.
184+
// Index of a byte with scale.
185+
const scaleIdx = 0
185186
scale := int(bcdBuf[scaleIdx])
186-
// Get a BCD buffer without a byte with scale.
187+
188+
// Get a BCD buffer without scale.
187189
bcdBuf = bcdBuf[scaleIdx+1:]
188-
length := len(bcdBuf)
189-
var digits []string
190-
for i, bcdByte := range bcdBuf {
191-
highNibble := bcdByte >> 4
192-
digits = append(digits, string('0'+highNibble))
193-
lowNibble := bcdByte & 0x0f
194-
// lowNibble of last byte is ignored because it contains a
195-
// sign.
196-
if i != length-1 {
197-
digits = append(digits, string('0'+lowNibble))
198-
}
199-
}
190+
bufLen := len(bcdBuf)
200191

201-
// Add missing zeros when scale is less than current length.
202-
l := len(digits)
203-
if scale >= l {
204-
var zeros []string
205-
for i := 0; i <= scale-l; i++ {
206-
zeros = append(zeros, "0")
207-
}
208-
digits = append(zeros, digits...)
192+
// Every nibble contains a digit, and the last low nibble contains a
193+
// sign.
194+
ndigits := bufLen*2 - 1
195+
196+
// The first nibble contains 0 if the decimal number has an even number of
197+
// digits. Decrease a number of digits if so.
198+
if bcdBuf[0]&0xf0 == 0 {
199+
ndigits -= 1
209200
}
210201

211-
// Add a dot when number is fractional.
212-
if scale != 0 {
213-
idx := len(digits) - scale
214-
digits = append(digits, "X") // [1 2 3 X]
215-
copy(digits[idx:], digits[idx-1:]) // [1 2 2 3]
216-
digits[idx] = "." // [1 . 2 3]
202+
// Reserve bytes for dot and sign.
203+
numLen := ndigits + 2
204+
// Reserve bytes for zeroes.
205+
if scale >= ndigits {
206+
numLen += scale - ndigits
217207
}
218208

209+
var bld strings.Builder
210+
bld.Grow(numLen)
211+
219212
// Add a sign, it is encoded in a low nibble of a last byte.
220-
lastByte := bcdBuf[length-1]
213+
lastByte := bcdBuf[bufLen-1]
221214
sign := lastByte & 0x0f
222215
if isNegative[sign] {
223-
digits = append([]string{"-"}, digits...)
216+
bld.WriteByte('-')
224217
}
225218

226-
// Merge slice to a single string.
227-
str := strings.Join(digits, "")
219+
// Add missing zeroes to the left side when scale is bigger than a
220+
// number of digits and a single missed zero to the right side when
221+
// equal.
222+
if scale > ndigits {
223+
bld.WriteByte('0')
224+
bld.WriteByte('.')
225+
for diff := scale - ndigits; diff > 0; diff-- {
226+
bld.WriteByte('0')
227+
}
228+
} else if scale == ndigits {
229+
bld.WriteByte('0')
230+
}
231+
232+
const MaxDigit = 0x09
233+
// Builds a buffer with symbols of decimal number (digits, dot and sign).
234+
processNibble := func(nibble byte) {
235+
if nibble <= MaxDigit {
236+
if ndigits == scale {
237+
bld.WriteByte('.')
238+
}
239+
bld.WriteByte(nibble + '0')
240+
ndigits--
241+
}
242+
}
243+
244+
for i, bcdByte := range bcdBuf {
245+
highNibble := bcdByte >> 4
246+
lowNibble := bcdByte & 0x0f
247+
// Skip a first high nibble as no digit there.
248+
if i != 0 || highNibble != 0 {
249+
processNibble(highNibble)
250+
}
251+
processNibble(lowNibble)
252+
}
228253

229-
return str, nil
254+
return bld.String(), nil
230255
}

0 commit comments

Comments
 (0)