@@ -21,7 +21,9 @@ package decimal
2121
2222import (
2323 "fmt"
24+ "math"
2425 "reflect"
26+ "strconv"
2527
2628 "github.com/shopspring/decimal"
2729 "github.com/vmihailenco/msgpack/v5"
@@ -96,7 +98,7 @@ func (d Decimal) MarshalMsgpack() ([]byte, error) {
9698 // +--------+-------------------+------------+===============+
9799 // | MP_EXT | length (optional) | MP_DECIMAL | PackedDecimal |
98100 // +--------+-------------------+------------+===============+
99- strBuf := d .String ()
101+ strBuf := d .Decimal . String ()
100102 bcdBuf , err := encodeStringToBCD (strBuf )
101103 if err != nil {
102104 return nil , fmt .Errorf ("msgpack: can't encode string (%s) to a BCD buffer: %w" , strBuf , err )
@@ -144,6 +146,242 @@ func decimalDecoder(d *msgpack.Decoder, v reflect.Value, extLen int) error {
144146 return ptr .UnmarshalMsgpack (b )
145147}
146148
149+ // String converts the decimal type to a string representation.
150+ // For numbers within int64 range, it uses an optimized conversion path.
151+ // For larger numbers (Tarantool decimal has 38 digits, which can exceed int64),
152+ // it falls back to shopspring/decimal.String().
153+ func (d Decimal ) String () string {
154+ coefficient := d .Decimal .Coefficient () // Note: In shopspring/decimal
155+ // the number is stored as coefficient *10^exponent, where exponent can be negative.
156+ exponent := d .Decimal .Exponent ()
157+
158+ // If exponent is positive, then we use the standard method.
159+ if exponent > 0 {
160+ return d .Decimal .String ()
161+ }
162+
163+ scale := - exponent
164+
165+ if ! coefficient .IsInt64 () {
166+ return d .Decimal .String ()
167+ }
168+
169+ int64Value := coefficient .Int64 ()
170+
171+ return d .stringFromInt64 (int64Value , int (scale ))
172+ }
173+
174+ // StringFromInt64 is an internal method for converting int64
175+ // and scale to a string (for numbers up to 19 digits).
176+ func (d Decimal ) stringFromInt64 (value int64 , scale int ) string {
177+ var buf [64 ]byte
178+ pos := 0
179+
180+ negative := value < 0
181+ if negative {
182+ if value == math .MinInt64 {
183+ return d .handleMinInt64 (scale )
184+ }
185+ buf [pos ] = '-'
186+ pos ++
187+ value = - value
188+ }
189+
190+ str := strconv .FormatInt (value , 10 )
191+ length := len (str )
192+
193+ // Special case: zero value.
194+ if value == 0 {
195+ return "0" // Always return "0" regardless of scale.
196+ }
197+
198+ if scale == 0 {
199+ // No fractional part.
200+ if pos + length > len (buf ) {
201+ return d .Decimal .String ()
202+ }
203+ copy (buf [pos :], str )
204+ pos += length
205+ return string (buf [:pos ])
206+ }
207+
208+ if scale >= length {
209+ // Numbers like 0.00123.
210+ // Count trailing zeros in the fractional part.
211+ trailingZeros := 0
212+ // In this case, the fractional part consists
213+ // of (scale-length) zeros followed by the number.
214+ // We need to count trailing zeros in the actual number part.
215+ for i := length - 1 ; i >= 0 && str [i ] == '0' ; i -- {
216+ trailingZeros ++
217+ }
218+
219+ effectiveDigits := length - trailingZeros
220+
221+ // If all digits are zeros after leading zeros, we need to adjust.
222+ if effectiveDigits == 0 {
223+ return "0"
224+ }
225+
226+ required := 2 + (scale - length ) + effectiveDigits
227+ if pos + required > len (buf ) {
228+ return d .Decimal .String ()
229+ }
230+
231+ buf [pos ] = '0'
232+ buf [pos + 1 ] = '.'
233+ pos += 2
234+
235+ // Add leading zeros.
236+ zeros := scale - length
237+ for i := 0 ; i < zeros ; i ++ {
238+ buf [pos ] = '0'
239+ pos ++
240+ }
241+
242+ // Copy only significant digits (without trailing zeros).
243+ copy (buf [pos :], str [:effectiveDigits ])
244+ pos += effectiveDigits
245+ } else {
246+ // Numbers like 123.45.
247+ integerLen := length - scale
248+
249+ // Count trailing zeros in fractional part.
250+ trailingZeros := 0
251+ for i := length - 1 ; i >= integerLen && str [i ] == '0' ; i -- {
252+ trailingZeros ++
253+ }
254+
255+ effectiveScale := scale - trailingZeros
256+
257+ // If all fractional digits are zeros, return just integer part.
258+ if effectiveScale == 0 {
259+ if pos + integerLen > len (buf ) {
260+ return d .Decimal .String ()
261+ }
262+ copy (buf [pos :], str [:integerLen ])
263+ pos += integerLen
264+ return string (buf [:pos ])
265+ }
266+
267+ required := integerLen + 1 + effectiveScale
268+ if pos + required > len (buf ) {
269+ return d .Decimal .String ()
270+ }
271+
272+ // Integer part.
273+ copy (buf [pos :], str [:integerLen ])
274+ pos += integerLen
275+
276+ // Decimal point.
277+ buf [pos ] = '.'
278+ pos ++
279+
280+ // Fractional part without trailing zeros.
281+ fractionalEnd := integerLen + effectiveScale
282+ copy (buf [pos :], str [integerLen :fractionalEnd ])
283+ pos += effectiveScale
284+ }
285+
286+ return string (buf [:pos ])
287+ }
288+ func (d Decimal ) handleMinInt64 (scale int ) string {
289+ const minInt64Str = "9223372036854775808"
290+
291+ var buf [64 ]byte
292+ pos := 0
293+
294+ buf [pos ] = '-'
295+ pos ++
296+
297+ length := len (minInt64Str )
298+
299+ if scale == 0 {
300+ if pos + length > len (buf ) {
301+ return "-" + minInt64Str
302+ }
303+ copy (buf [pos :], minInt64Str )
304+ pos += length
305+ return string (buf [:pos ])
306+ }
307+
308+ if scale >= length {
309+ // Count trailing zeros in the actual number part.
310+ trailingZeros := 0
311+ for i := length - 1 ; i >= 0 && minInt64Str [i ] == '0' ; i -- {
312+ trailingZeros ++
313+ }
314+
315+ effectiveDigits := length - trailingZeros
316+
317+ if effectiveDigits == 0 {
318+ return "0"
319+ }
320+
321+ required := 2 + (scale - length ) + effectiveDigits
322+ if pos + required > len (buf ) {
323+ return d .Decimal .String ()
324+ }
325+
326+ buf [pos ] = '0'
327+ buf [pos + 1 ] = '.'
328+ pos += 2
329+
330+ zeros := scale - length
331+ for i := 0 ; i < zeros ; i ++ {
332+ buf [pos ] = '0'
333+ pos ++
334+ }
335+
336+ copy (buf [pos :], minInt64Str [:effectiveDigits ])
337+ pos += effectiveDigits
338+ } else {
339+ integerLen := length - scale
340+
341+ // Count trailing zeros for minInt64Str fractional part.
342+ trailingZeros := 0
343+ for i := length - 1 ; i >= integerLen && minInt64Str [i ] == '0' ; i -- {
344+ trailingZeros ++
345+ }
346+
347+ effectiveScale := scale - trailingZeros
348+
349+ if effectiveScale == 0 {
350+ if pos + integerLen > len (buf ) {
351+ return d .Decimal .String ()
352+ }
353+ copy (buf [pos :], minInt64Str [:integerLen ])
354+ pos += integerLen
355+ return string (buf [:pos ])
356+ }
357+
358+ required := integerLen + 1 + effectiveScale
359+ if pos + required > len (buf ) {
360+ return d .Decimal .String ()
361+ }
362+
363+ copy (buf [pos :], minInt64Str [:integerLen ])
364+ pos += integerLen
365+
366+ buf [pos ] = '.'
367+ pos ++
368+
369+ fractionalEnd := integerLen + effectiveScale
370+ copy (buf [pos :], minInt64Str [integerLen :fractionalEnd ])
371+ pos += effectiveScale
372+ }
373+
374+ return string (buf [:pos ])
375+ }
376+
377+ func MustMakeDecimal (src string ) Decimal {
378+ dec , err := MakeDecimalFromString (src )
379+ if err != nil {
380+ panic (fmt .Sprintf ("MustMakeDecimalFromString: %v" , err ))
381+ }
382+ return dec
383+ }
384+
147385func init () {
148386 msgpack .RegisterExtDecoder (decimalExtID , Decimal {}, decimalDecoder )
149387 msgpack .RegisterExtEncoder (decimalExtID , Decimal {}, decimalEncoder )
0 commit comments