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