Skip to content

Commit b866691

Browse files
msgpack: support decimals with negative scale
Current decimal external type parser do not expect negative scale in decimal payload. Negative scale is a positive exponent. It seems that the only way to obtain positive exponent is to use E-notation since Tarantool library do not truncate trailing zeroes before decimal point: ``` tarantool> msgpack.encode(decimal.new('1e33')):hex() --- - c70301d0df1c ... tarantool> msgpack.encode(decimal.new('1000000000000000000000000000000000')):hex() --- - c713010001000000000000000000000000000000000c ... ``` There are two different bugs in current implementation: - we support only `positive fixint` scale and do not expect `int 8` [2], - we do not expect negative scale so positive exponent will be ignored. This patch fixes both of them. See also [3]. 1. https://github.com/tarantool/tarantool/blob/ba749e820bf0638aa3f79f266848590f9713c1cf/src/lib/core/decimal.c#L432-L450 2. https://github.com/msgpack/msgpack/blob/master/spec.md 3. tarantool/go-tarantool#314
1 parent 9c8945f commit b866691

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Allow to require specific server protocol version and features (#267).
1111

12+
### Fixed
13+
- Parsing of E-notation Tarantool decimals with positive exponent (PR #298).
14+
1215
## 1.0.0 - 2023-04-17
1316

1417
### Changed

Diff for: tarantool/msgpack_ext/decimal.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656

5757
from decimal import Decimal
5858

59+
import msgpack
60+
5961
from tarantool.error import MsgpackError, MsgpackWarning, warn
6062

6163
EXT_ID = 1
@@ -353,7 +355,12 @@ def decode(data, _):
353355
:raise: :exc:`~tarantool.error.MsgpackError`
354356
"""
355357

356-
scale = data[0]
358+
# A decimal starts with mp_int or mp_uint followed by raw bytes.
359+
unpacker = msgpack.Unpacker()
360+
unpacker.feed(data)
361+
362+
scale = unpacker.unpack()
363+
scale_size = unpacker.tell()
357364

358365
sign = get_str_sign(data[-1] & 0x0f)
359366

@@ -362,7 +369,7 @@ def decode(data, _):
362369

363370
add_str_digit(data[-1] >> 4, digits_reverted, scale)
364371

365-
for i in range(len(data) - 2, 0, -1):
372+
for i in range(len(data) - 2, scale_size - 1, -1):
366373
add_str_digit(data[i] & 0x0f, digits_reverted, scale)
367374
add_str_digit(data[i] >> 4, digits_reverted, scale)
368375

@@ -372,6 +379,12 @@ def decode(data, _):
372379

373380
digits_reverted.append(sign)
374381

375-
str_repr = ''.join(digits_reverted[::-1])
382+
digits = digits_reverted[::-1]
383+
384+
# Add trailing zeroes in case of a negative scale
385+
for i in range(0, -1 * scale):
386+
add_str_digit(0, digits, scale)
387+
388+
str_repr = ''.join(digits)
376389

377390
return Decimal(str_repr)

Diff for: test/suites/test_decimal.py

+33
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,39 @@ def setUp(self):
226226
b'\x09\x87\x65\x43\x21\x98\x76\x54\x32\x1d'),
227227
'tarantool': "decimal.new('-1234567891234567890.0987654321987654321')",
228228
},
229+
'decimal_exponent_1': {
230+
'python': decimal.Decimal('1e33'),
231+
'msgpack': (b'\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00'
232+
b'\x00\x00\x00\x00\x00\x00\x00\x0c'),
233+
'tarantool': "decimal.new('1e33')",
234+
},
235+
'decimal_exponent_2': {
236+
'python': decimal.Decimal('1.2345e33'),
237+
'msgpack': (b'\x00\x01\x23\x45\x00\x00\x00\x00\x00\x00\x00'
238+
b'\x00\x00\x00\x00\x00\x00\x00\x0c'),
239+
'tarantool': "decimal.new('1.2345e33')",
240+
},
241+
'decimal_exponent_3': {
242+
'python': decimal.Decimal('1.2345e2'),
243+
'msgpack': (b'\x02\x12\x34\x5c'),
244+
'tarantool': "decimal.new('1.2345e2')",
245+
},
246+
'decimal_exponent_4': {
247+
'python': decimal.Decimal('1.2345e4'),
248+
'msgpack': (b'\x00\x12\x34\x5c'),
249+
'tarantool': "decimal.new('1.2345e4')",
250+
},
251+
'decimal_exponent_5': {
252+
'python': decimal.Decimal('-1e33'),
253+
'msgpack': (b'\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00'
254+
b'\x00\x00\x00\x00\x00\x00\x00\x0d'),
255+
'tarantool': "decimal.new('-1e33')",
256+
},
257+
'decimal_exponent_6': {
258+
'python': decimal.Decimal('1e-33'),
259+
'msgpack': (b'\x21\x1c'),
260+
'tarantool': "decimal.new('1e-33')",
261+
},
229262
}
230263

231264
def test_msgpack_decode(self):

0 commit comments

Comments
 (0)