Skip to content

Commit f4265e1

Browse files
committed
msgpack: add string() for decimal, datetime. interval
Added a benchmark, which shows that the code for decimal is optimized two or more times for string conversion than the code from the library. Added a datetime, Interval type conversion function to a string, added tests for this function. Added #322
1 parent 6688ae7 commit f4265e1

File tree

2 files changed

+100
-28
lines changed

2 files changed

+100
-28
lines changed

decimal/decimal.go

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"math"
2525
"reflect"
2626
"strconv"
27-
"strings"
2827

2928
"github.com/shopspring/decimal"
3029
"github.com/vmihailenco/msgpack/v5"
@@ -180,8 +179,7 @@ func (d Decimal) String() string {
180179
// StringFromInt64 is an internal method for converting int64
181180
// and scale to a string (for numbers up to 19 digits).
182181
func (d Decimal) stringFromInt64(value int64, scale int) string {
183-
184-
var buf [48]byte
182+
var buf [64]byte
185183
pos := 0
186184

187185
negative := value < 0
@@ -197,7 +195,13 @@ func (d Decimal) stringFromInt64(value int64, scale int) string {
197195
str := strconv.FormatInt(value, 10)
198196
length := len(str)
199197

198+
// Special case: zero value.
199+
if value == 0 {
200+
return "0" // Always return "0" regardless of scale.
201+
}
202+
200203
if scale == 0 {
204+
// No fractional part.
201205
if pos+length > len(buf) {
202206
return d.Decimal.String()
203207
}
@@ -207,8 +211,23 @@ func (d Decimal) stringFromInt64(value int64, scale int) string {
207211
}
208212

209213
if scale >= length {
214+
// Numbers like 0.00123.
215+
// Count trailing zeros in the fractional part.
216+
trailingZeros := 0
217+
// In this case, the fractional part consists of (scale-length) zeros followed by the number.
218+
// We need to count trailing zeros in the actual number part.
219+
for i := length - 1; i >= 0 && str[i] == '0'; i-- {
220+
trailingZeros++
221+
}
210222

211-
required := 2 + (scale - length) + length
223+
effectiveDigits := length - trailingZeros
224+
225+
// If all digits are zeros after leading zeros, we need to adjust.
226+
if effectiveDigits == 0 {
227+
return "0"
228+
}
229+
230+
required := 2 + (scale - length) + effectiveDigits
212231
if pos+required > len(buf) {
213232
return d.Decimal.String()
214233
}
@@ -217,39 +236,63 @@ func (d Decimal) stringFromInt64(value int64, scale int) string {
217236
buf[pos+1] = '.'
218237
pos += 2
219238

239+
// Add leading zeros.
220240
zeros := scale - length
221241
for i := 0; i < zeros; i++ {
222242
buf[pos] = '0'
223243
pos++
224244
}
225245

226-
copy(buf[pos:], str)
227-
pos += length
246+
// Copy only significant digits (without trailing zeros).
247+
copy(buf[pos:], str[:effectiveDigits])
248+
pos += effectiveDigits
228249
} else {
229-
250+
// Numbers like 123.45.
230251
integerLen := length - scale
231252

232-
required := integerLen + 1 + scale
253+
// Count trailing zeros in fractional part.
254+
trailingZeros := 0
255+
for i := length - 1; i >= integerLen && str[i] == '0'; i-- {
256+
trailingZeros++
257+
}
258+
259+
effectiveScale := scale - trailingZeros
260+
261+
// If all fractional digits are zeros, return just integer part.
262+
if effectiveScale == 0 {
263+
if pos+integerLen > len(buf) {
264+
return d.Decimal.String()
265+
}
266+
copy(buf[pos:], str[:integerLen])
267+
pos += integerLen
268+
return string(buf[:pos])
269+
}
270+
271+
required := integerLen + 1 + effectiveScale
233272
if pos+required > len(buf) {
234273
return d.Decimal.String()
235274
}
236275

276+
// Integer part.
237277
copy(buf[pos:], str[:integerLen])
238278
pos += integerLen
239279

280+
// Decimal point.
240281
buf[pos] = '.'
241282
pos++
242283

243-
copy(buf[pos:], str[integerLen:])
244-
pos += scale
284+
// Fractional part without trailing zeros.
285+
fractionalEnd := integerLen + effectiveScale
286+
copy(buf[pos:], str[integerLen:fractionalEnd])
287+
pos += effectiveScale
245288
}
246289

247290
return string(buf[:pos])
248291
}
249292
func (d Decimal) handleMinInt64(scale int) string {
250293
const minInt64Str = "9223372036854775808"
251294

252-
var buf [48]byte
295+
var buf [64]byte
253296
pos := 0
254297

255298
buf[pos] = '-'
@@ -259,19 +302,29 @@ func (d Decimal) handleMinInt64(scale int) string {
259302

260303
if scale == 0 {
261304
if pos+length > len(buf) {
262-
return "-" + minInt64Str // Fallback.
305+
return "-" + minInt64Str
263306
}
264307
copy(buf[pos:], minInt64Str)
265308
pos += length
266309
return string(buf[:pos])
267310
}
268311

269312
if scale >= length {
270-
required := 2 + (scale - length) + length
313+
// Count trailing zeros in the actual number part.
314+
trailingZeros := 0
315+
for i := length - 1; i >= 0 && minInt64Str[i] == '0'; i-- {
316+
trailingZeros++
317+
}
318+
319+
effectiveDigits := length - trailingZeros
320+
321+
if effectiveDigits == 0 {
322+
return "0"
323+
}
324+
325+
required := 2 + (scale - length) + effectiveDigits
271326
if pos+required > len(buf) {
272-
// Fallback.
273-
result := "0." + strings.Repeat("0", scale-length) + minInt64Str
274-
return "-" + result
327+
return d.Decimal.String()
275328
}
276329

277330
buf[pos] = '0'
@@ -284,13 +337,31 @@ func (d Decimal) handleMinInt64(scale int) string {
284337
pos++
285338
}
286339

287-
copy(buf[pos:], minInt64Str)
288-
pos += length
340+
copy(buf[pos:], minInt64Str[:effectiveDigits])
341+
pos += effectiveDigits
289342
} else {
290343
integerLen := length - scale
291-
required := integerLen + 1 + scale
344+
345+
// Count trailing zeros for minInt64Str fractional part.
346+
trailingZeros := 0
347+
for i := length - 1; i >= integerLen && minInt64Str[i] == '0'; i-- {
348+
trailingZeros++
349+
}
350+
351+
effectiveScale := scale - trailingZeros
352+
353+
if effectiveScale == 0 {
354+
if pos+integerLen > len(buf) {
355+
return d.Decimal.String()
356+
}
357+
copy(buf[pos:], minInt64Str[:integerLen])
358+
pos += integerLen
359+
return string(buf[:pos])
360+
}
361+
362+
required := integerLen + 1 + effectiveScale
292363
if pos+required > len(buf) {
293-
return d.Decimal.String() // Fallback
364+
return d.Decimal.String()
294365
}
295366

296367
copy(buf[pos:], minInt64Str[:integerLen])
@@ -299,8 +370,9 @@ func (d Decimal) handleMinInt64(scale int) string {
299370
buf[pos] = '.'
300371
pos++
301372

302-
copy(buf[pos:], minInt64Str[integerLen:])
303-
pos += scale
373+
fractionalEnd := integerLen + effectiveScale
374+
copy(buf[pos:], minInt64Str[integerLen:fractionalEnd])
375+
pos += effectiveScale
304376
}
305377

306378
return string(buf[:pos])

decimal/decimal_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -900,12 +900,12 @@ func TestDecimalTrailingZeros(t *testing.T) {
900900
input string
901901
expected string
902902
}{
903-
{"100.00", "100.00"},
904-
{"0.00", "0.00"},
905-
{"0.000", "0.000"},
906-
{"1.000", "1.000"},
907-
{"123.4500", "123.4500"},
908-
{"0.00100", "0.00100"},
903+
{"100.00", "100"},
904+
{"0.00", "0"},
905+
{"0.000", "0"},
906+
{"1.000", "1"},
907+
{"123.4500", "123.45"},
908+
{"0.00100", "0.001"},
909909
}
910910

911911
for _, tt := range tests {

0 commit comments

Comments
 (0)