Tarantool development patches archive
 help / color / mirror / Atom feed
* [PATCH v3 0/6] Decimal indices
@ 2019-08-20 17:09 Serge Petrenko
  2019-08-20 17:09 ` [PATCH v3 1/6] lua: fix decimal comparison with nil Serge Petrenko
                   ` (5 more replies)
  0 siblings, 6 replies; 14+ messages in thread
From: Serge Petrenko @ 2019-08-20 17:09 UTC (permalink / raw)
  To: vdavydov.dev, kostja; +Cc: tarantool-patches, Serge Petrenko

https://github.com/tarantool/tarantool/issues/4333
https://github.com/tarantool/tarantool/tree/sp/gh-4333-decimal-index

This patchset adds the ability to store decimals in spaces and to build indices
over them. As a prerequisite, the ability to encode and decode decimals as
msgpack is also added to both C and Lua modules.

First three patches contain various bugfixes found during the work on the issue.

The sixth patch adds the methods to encode and decode decimals as msgpack, and
expands lua msgpack and msgpackffi modules with the same abilities.
This allows to store decimal values in tuples, and in space unindexed fields.

The seventh patch adds methods to cast decimals to 64 bit integers. This
functionality is needed to calculate comparison hints from decimals in scope of
patch 8.

Patch 8 finally introduces decimal fields, adds comparators for decimal values
and defines field types appropriate for storing decimals.

Changes in v3:
  - minor fixes as per review by Kostja
    and Vladimir.
  - Remove patches introducing the new
    enum mp_field_type (patches 4 & 5 previously)
  - Basically return to v1 with some additional
    review fixes and better checks for MP_EXT
    subtypes in patch 6

Changes in v2:
  - Various minor fixes regarding a review by
    Vladimir Davydov
  - All the bugfixes are extracted into separate
    patches
  - More tests are added to cover space.format and
    index.alter with decimal fields
  - A single enum is introduced to cover both existent
    msgpack types and the ones with which we might
    come up later
  - The patches covering the ability to encode/decode
    decimals as msgpack in C and Lua are squashed.

Serge Petrenko (6):
  lua: fix decimal comparison with nil
  decimal: fix encoding numbers with positive exponent.
  lua/pickle: fix a typo
  decimal: allow to encode/decode decimals as MsgPack
  decimal: add conversions to (u)int64_t
  decimal: allow to index decimals

 extra/exports                        |   4 +
 src/box/field_def.c                  |  45 +-
 src/box/field_def.h                  |  23 +-
 src/box/key_def.h                    |   3 +-
 src/box/tuple_compare.cc             | 198 +++++++-
 src/box/tuple_format.c               |   3 +-
 src/lib/core/CMakeLists.txt          |   1 +
 src/lib/core/decimal.c               |  99 +++-
 src/lib/core/decimal.h               |  27 +
 src/lib/core/mp_decimal.c            |  72 +++
 src/lib/core/mp_decimal.h            |  70 +++
 src/lib/core/mp_extension_types.h    |  47 ++
 src/lib/core/mpstream.c              |  11 +
 src/lib/core/mpstream.h              |   4 +
 src/lua/decimal.c                    |  23 +-
 src/lua/decimal.h                    |   7 +
 src/lua/msgpack.c                    |  53 +-
 src/lua/msgpackffi.lua               |  38 ++
 src/lua/pickle.c                     |   4 +-
 src/lua/utils.c                      |  16 +-
 src/lua/utils.h                      |   8 +-
 test/app-tap/lua/serializer_test.lua |  14 +
 test/app-tap/msgpackffi.test.lua     |   3 +-
 test/app/decimal.result              | 172 ++++---
 test/app/decimal.test.lua            |   8 +
 test/app/msgpack.result              |  41 ++
 test/app/msgpack.test.lua            |  15 +
 test/app/pack.result                 |   8 +
 test/app/pack.test.lua               |   2 +
 test/box/tuple.result                |  85 ++++
 test/box/tuple.test.lua              |  23 +
 test/engine/ddl.result               |  85 +++-
 test/engine/ddl.test.lua             |  40 +-
 test/engine/decimal.result           | 356 +++++++++++++
 test/engine/decimal.test.lua         | 104 ++++
 test/engine/iterator.result          | 730 +++++++++++++++++++++++++++
 test/engine/iterator.test.lua        |  22 +
 test/unit/decimal.c                  | 139 ++++-
 test/unit/decimal.result             | 283 ++++++++++-
 third_party/decNumber                |   2 +-
 third_party/lua-cjson/lua_cjson.c    |  12 +-
 third_party/lua-yaml/lyaml.cc        |  10 +-
 42 files changed, 2764 insertions(+), 146 deletions(-)
 create mode 100644 src/lib/core/mp_decimal.c
 create mode 100644 src/lib/core/mp_decimal.h
 create mode 100644 src/lib/core/mp_extension_types.h
 create mode 100644 test/engine/decimal.result
 create mode 100644 test/engine/decimal.test.lua

-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 1/6] lua: fix decimal comparison with nil
  2019-08-20 17:09 [PATCH v3 0/6] Decimal indices Serge Petrenko
@ 2019-08-20 17:09 ` Serge Petrenko
  2019-08-21 14:13   ` Vladimir Davydov
  2019-08-20 17:10 ` [PATCH v3 2/6] decimal: fix encoding numbers with positive exponent Serge Petrenko
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Serge Petrenko @ 2019-08-20 17:09 UTC (permalink / raw)
  To: vdavydov.dev, kostja; +Cc: tarantool-patches, Serge Petrenko

Previously decimal comparison with nil failed with following error:
`expected decimal, number or string as 2 argument`.
Fix this. Throw a more verbose error in case of '>', '<', '>=', '<='
and fix equality check.

Follow-up #692
---
 src/lua/decimal.c         | 19 ++++++++++++++++++-
 test/app/decimal.result   | 26 ++++++++++++++++++++++++++
 test/app/decimal.test.lua |  8 ++++++++
 3 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/src/lua/decimal.c b/src/lua/decimal.c
index de6586c8b..7f9358787 100644
--- a/src/lua/decimal.c
+++ b/src/lua/decimal.c
@@ -69,6 +69,10 @@ ldecimal_##name(struct lua_State *L) {						\
 static int									\
 ldecimal_##name(struct lua_State *L) {						\
 	assert(lua_gettop(L) == 2);						\
+	if (lua_isnil(L, 1) || lua_isnil(L, 2)) {				\
+		luaL_error(L, "attempt to compare decimal with nil");		\
+		return 1;							\
+	}									\
 	decimal_t *lhs = lua_todecimal(L, 1);					\
 	decimal_t *rhs = lua_todecimal(L, 2);					\
 	lua_pushboolean(L, decimal_compare(lhs, rhs) cmp 0);			\
@@ -226,10 +230,23 @@ LDECIMAL_FUNC(exp, exp)
 LDECIMAL_FUNC(sqrt, sqrt)
 LDECIMAL_FUNC(abs, abs)
 
-LDECIMAL_CMPOP(eq, ==)
 LDECIMAL_CMPOP(lt, <)
 LDECIMAL_CMPOP(le, <=)
 
+static int
+ldecimal_eq(struct lua_State *L)
+{
+	assert(lua_gettop(L) == 2);
+	if (lua_isnil(L, 1) || lua_isnil(L, 2)) {
+		lua_pushboolean(L, false);
+		return 1;
+	}
+	decimal_t *lhs = lua_todecimal(L, 1);
+	decimal_t *rhs = lua_todecimal(L, 2);
+	lua_pushboolean(L, decimal_compare(lhs, rhs) == 0);
+	return 1;
+}
+
 static int
 ldecimal_minus(struct lua_State *L)
 {
diff --git a/test/app/decimal.result b/test/app/decimal.result
index c632f57a7..2e44928bb 100644
--- a/test/app/decimal.result
+++ b/test/app/decimal.result
@@ -223,6 +223,32 @@ b
  | - '0.1'
  | ...
 
+-- check comparsion with nil
+a == nil
+ | ---
+ | - false
+ | ...
+a ~= nil
+ | ---
+ | - true
+ | ...
+a > nil
+ | ---
+ | - error: '[string "return a > nil "]:1: attempt to compare decimal with nil'
+ | ...
+a < nil
+ | ---
+ | - error: '[string "return a < nil "]:1: attempt to compare decimal with nil'
+ | ...
+a >= nil
+ | ---
+ | - error: '[string "return a >= nil "]:1: attempt to compare decimal with nil'
+ | ...
+a <= nil
+ | ---
+ | - error: '[string "return a <= nil "]:1: attempt to compare decimal with nil'
+ | ...
+
 decimal.sqrt(a)
  | ---
  | - '3.1622776601683793319988935444327185337'
diff --git a/test/app/decimal.test.lua b/test/app/decimal.test.lua
index 40f1f29de..d83522b45 100644
--- a/test/app/decimal.test.lua
+++ b/test/app/decimal.test.lua
@@ -62,6 +62,14 @@ a ~= b
 a
 b
 
+-- check comparsion with nil
+a == nil
+a ~= nil
+a > nil
+a < nil
+a >= nil
+a <= nil
+
 decimal.sqrt(a)
 decimal.ln(a)
 decimal.log10(a)
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 2/6] decimal: fix encoding numbers with positive exponent.
  2019-08-20 17:09 [PATCH v3 0/6] Decimal indices Serge Petrenko
  2019-08-20 17:09 ` [PATCH v3 1/6] lua: fix decimal comparison with nil Serge Petrenko
@ 2019-08-20 17:10 ` Serge Petrenko
  2019-08-21 14:13   ` Vladimir Davydov
  2019-08-20 17:10 ` [PATCH v3 3/6] lua/pickle: fix a typo Serge Petrenko
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Serge Petrenko @ 2019-08-20 17:10 UTC (permalink / raw)
  To: vdavydov.dev, kostja; +Cc: tarantool-patches, Serge Petrenko

When a number having a positive exponent is encoded, the internal
decPackedFromNumber function returns a negative scale, which differs
from the scale, returned by decimal_scale(). This leads to errors in
decoding. Account for negative scale in decimal_pack() and
decimal_unpack().

Follow-up #692
---
 src/lib/core/decimal.c   | 42 ++++++++++++++++++++++++++++++++--------
 test/unit/decimal.c      | 15 ++++++++++++--
 test/unit/decimal.result | 11 ++++++++---
 3 files changed, 55 insertions(+), 13 deletions(-)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index e141a91f8..356acf6f2 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -33,6 +33,7 @@
 #include "third_party/decNumber/decContext.h"
 #include "third_party/decNumber/decPacked.h"
 #include "lib/core/tt_static.h"
+#include "lib/msgpuck/msgpuck.h"
 #include <stddef.h>
 #include <stdlib.h>
 #include <float.h> /* DBL_DIG */
@@ -339,20 +340,27 @@ decimal_sqrt(decimal_t *res, const decimal_t *lhs)
 uint32_t
 decimal_len(const decimal_t *dec)
 {
-	/* 1  + ceil((digits + 1) / 2) */
-	return 2 + dec->digits / 2;
+	uint32_t sizeof_scale = dec->exponent > 0 ? mp_sizeof_int(-dec->exponent) :
+						    mp_sizeof_uint(-dec->exponent);
+	/* sizeof_scale  + ceil((digits + 1) / 2) */
+	return sizeof_scale + 1 + dec->digits / 2;
 }
 
 char *
 decimal_pack(char *data, const decimal_t *dec)
 {
 	uint32_t len = decimal_len(dec);
-	*data++ = decimal_scale(dec);
-	len--;
+	char *svp = data;
+	/* encode scale */
+	if (dec->exponent > 0) {
+		data = mp_encode_int(data, -dec->exponent);
+	} else {
+		data = mp_encode_uint(data, -dec->exponent);
+	}
+	len -= data - svp;
 	int32_t scale;
 	char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec);
 	assert(tmp == data);
-	assert(scale == (int32_t)decimal_scale(dec));
 	(void)tmp;
 	data += len;
 	return data;
@@ -361,12 +369,30 @@ decimal_pack(char *data, const decimal_t *dec)
 decimal_t *
 decimal_unpack(const char **data, uint32_t len, decimal_t *dec)
 {
-	int32_t scale = *((*data)++);
-	len--;
+	int32_t scale;
+	const char *svp = *data;
+	if (mp_typeof(**data) == MP_UINT) {
+		scale = mp_decode_uint(data);
+	} else if (mp_typeof(**data) == MP_INT) {
+		scale = mp_decode_int(data);
+	} else {
+		return NULL;
+	}
+	/*
+	 * scale = -exponent. The exponent should be in range
+	 * [-DECIMAL_MAX_DIGITS; DECIMAL_MAX_DIGITS)
+	 */
+	if (scale > DECIMAL_MAX_DIGITS ||
+	    scale <= -DECIMAL_MAX_DIGITS) {
+		*data = svp;
+		return NULL;
+	}
+
+	len -= *data - svp;
 	decimal_t *res = decPackedToNumber((uint8_t *)*data, len, &scale, dec);
 	if (res)
 		*data += len;
 	else
-		(*data)--;
+		*data = svp;
 	return res;
 }
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index b587e1f14..b55333ed7 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -91,7 +91,7 @@ char buf[32];
 static int
 test_pack_unpack(void)
 {
-	plan(146);
+	plan(151);
 
 	test_decpack("0");
 	test_decpack("-0");
@@ -112,13 +112,24 @@ test_pack_unpack(void)
 	test_decpack("99999999999999999999999999999999999999");
 	test_decpack("-99999999999999999999999999999999999999");
 
+	/* Check correct encoding of positive exponent numbers. */
+	decimal_t dec, d1;
+	decimal_from_string(&dec, "1e10");
+	uint32_t l1 = decimal_len(&dec);
+	ok(l1 == 2, "decimal_len() is small for positive exponent decimal");
+	char *b1 = decimal_pack(buf, &dec);
+	is(b1, buf + l1, "positive exponent decimal length");
+	const char *b2 = buf;
+	is(decimal_unpack(&b2, l1, &d1), &d1, "decimal_unpack() of a positive exponent decimal");
+	is(b1, b2, "decimal_unpack uses every byte packed by decimal_pack");
+	is(decimal_compare(&dec, &d1), 0, "positive exponent number is packed/unpacked correctly");
+
 	/* Pack an invalid decimal. */
 	char *b = buf;
 	*b++ = 1;
 	*b++ = '\xab';
 	*b++ = '\xcd';
 	const char *bb = buf;
-	decimal_t dec;
 	is(decimal_unpack(&bb, 3, &dec), NULL, "unpack malformed decimal fails");
 	is(bb, buf, "decode malformed decimal preserves buffer position");
 
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index 1c72cdfab..2dd91af49 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -277,7 +277,7 @@ ok 275 - decimal_from_string(-1)
 ok 276 - decimal_log10(-1) - error on wrong operands.
 ok 277 - decimal_from_string(-10)
 ok 278 - decimal_sqrt(-10) - error on wrong operands.
-    1..146
+    1..151
     ok 1 - decimal_len(0)
     ok 2 - decimal_len(0) == len(decimal_pack(0)
     ok 3 - decimal_unpack(decimal_pack(0))
@@ -422,6 +422,11 @@ ok 278 - decimal_sqrt(-10) - error on wrong operands.
     ok 142 - decimal_unpack(decimal_pack(-99999999999999999999999999999999999999)) scale
     ok 143 - decimal_unpack(decimal_pack(-99999999999999999999999999999999999999)) precision
     ok 144 - str(decimal_unpack(decimal_pack(-99999999999999999999999999999999999999)) == -99999999999999999999999999999999999999
-    ok 145 - unpack malformed decimal fails
-    ok 146 - decode malformed decimal preserves buffer position
+    ok 145 - decimal_len() is small for positive exponent decimal
+    ok 146 - positive exponent decimal length
+    ok 147 - decimal_unpack() of a positive exponent decimal
+    ok 148 - decimal_unpack uses every byte packed by decimal_pack
+    ok 149 - positive exponent number is packed/unpacked correctly
+    ok 150 - unpack malformed decimal fails
+    ok 151 - decode malformed decimal preserves buffer position
 ok 279 - subtests
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 3/6] lua/pickle: fix a typo
  2019-08-20 17:09 [PATCH v3 0/6] Decimal indices Serge Petrenko
  2019-08-20 17:09 ` [PATCH v3 1/6] lua: fix decimal comparison with nil Serge Petrenko
  2019-08-20 17:10 ` [PATCH v3 2/6] decimal: fix encoding numbers with positive exponent Serge Petrenko
@ 2019-08-20 17:10 ` Serge Petrenko
  2019-08-21 14:13   ` Vladimir Davydov
  2019-08-20 17:10 ` [PATCH v3 4/6] decimal: allow to encode/decode decimals as MsgPack Serge Petrenko
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Serge Petrenko @ 2019-08-20 17:10 UTC (permalink / raw)
  To: vdavydov.dev, kostja; +Cc: tarantool-patches, Serge Petrenko

Prior to this patch format checking was broken for 'i' (integer) and 'N'
(big-endian integer). pickle.pack() rejected negative integers with
these formats. Fix this
---
 src/lua/pickle.c       | 4 ++--
 test/app/pack.result   | 8 ++++++++
 test/app/pack.test.lua | 2 ++
 3 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/lua/pickle.c b/src/lua/pickle.c
index e47ac11b4..65208b5b3 100644
--- a/src/lua/pickle.c
+++ b/src/lua/pickle.c
@@ -109,14 +109,14 @@ lbox_pack(struct lua_State *L)
 		case 'I':
 		case 'i':
 			/* signed and unsigned 32-bit integers */
-			if (field.type != MP_UINT && field.ival != MP_INT)
+			if (field.type != MP_UINT && field.type != MP_INT)
 				luaL_error(L, "pickle.pack: expected 32-bit int");
 
 			luaL_region_dup(L, buf, &field.ival, sizeof(uint32_t));
 			break;
 		case 'N':
 			/* signed and unsigned 32-bit big endian integers */
-			if (field.type != MP_UINT && field.ival != MP_INT)
+			if (field.type != MP_UINT && field.type != MP_INT)
 				luaL_error(L, "pickle.pack: expected 32-bit int");
 
 			field.ival = htonl(field.ival);
diff --git a/test/app/pack.result b/test/app/pack.result
index 8c1333abb..52f4be3cd 100644
--- a/test/app/pack.result
+++ b/test/app/pack.result
@@ -26,6 +26,14 @@ pickle.pack('s', 0x4d)
 ---
 - "M\0"
 ...
+pickle.pack('i', -1)
+---
+- !!binary /////w==
+...
+pickle.pack('N', -1)
+---
+- !!binary /////w==
+...
 pickle.pack('ssss', 25940, 29811, 28448, 11883)
 ---
 - Test ok.
diff --git a/test/app/pack.test.lua b/test/app/pack.test.lua
index 8f7a6a0f4..6ddd81cd4 100644
--- a/test/app/pack.test.lua
+++ b/test/app/pack.test.lua
@@ -7,6 +7,8 @@ pickle.pack('abc')
 pickle.pack('a', ' - hello')
 pickle.pack('Aa', ' - hello', ' world')
 pickle.pack('s', 0x4d)
+pickle.pack('i', -1)
+pickle.pack('N', -1)
 pickle.pack('ssss', 25940, 29811, 28448, 11883)
 pickle.pack('SSSS', 25940, 29811, 28448, 11883)
 pickle.pack('SSSSSSSS', 28493, 29550, 27680, 27497, 29541, 20512, 29285, 8556)
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 4/6] decimal: allow to encode/decode decimals as MsgPack
  2019-08-20 17:09 [PATCH v3 0/6] Decimal indices Serge Petrenko
                   ` (2 preceding siblings ...)
  2019-08-20 17:10 ` [PATCH v3 3/6] lua/pickle: fix a typo Serge Petrenko
@ 2019-08-20 17:10 ` Serge Petrenko
  2019-08-21 15:02   ` Vladimir Davydov
  2019-08-20 17:10 ` [PATCH v3 5/6] decimal: add conversions to (u)int64_t Serge Petrenko
  2019-08-20 17:10 ` [PATCH v3 6/6] decimal: allow to index decimals Serge Petrenko
  5 siblings, 1 reply; 14+ messages in thread
From: Serge Petrenko @ 2019-08-20 17:10 UTC (permalink / raw)
  To: vdavydov.dev, kostja; +Cc: tarantool-patches, Serge Petrenko

This patch adds the methods necessary to encode and decode decimals to
MsgPack. MsgPack EXT type (MP_EXT) together with a new extension type
MP_DECIMAL is used as a record header.

The decimal MsgPack representation looks like this:
+--------+-------------------+------------+===============+
| MP_EXT | length (optional) | MP_DECIMAL | PackedDecimal |
+--------+-------------------+------------+===============+
The whole record may be encoded and decoded with
mp_encode_decimal() and mp_decode_decimal(). This is equivalent to
performing mp_encode_extl()/mp_decode_extl() on the first 3 fields and
decimal_pack/unpack() on the PackedDecimal field.

It is also possible to decode and encode decimals to msgpack from lua,
which means you can insert decimals into spaces, but only into unindexed
fields for now.

Follow up #692
Part of #4333
---
 extra/exports                        |   4 +
 src/lib/core/CMakeLists.txt          |   1 +
 src/lib/core/mp_decimal.c            |  72 ++++++++++
 src/lib/core/mp_decimal.h            |  62 ++++++++
 src/lib/core/mp_extension_types.h    |  47 +++++++
 src/lib/core/mpstream.c              |  11 ++
 src/lib/core/mpstream.h              |   4 +
 src/lua/decimal.c                    |   4 +-
 src/lua/decimal.h                    |   7 +
 src/lua/msgpack.c                    |  53 +++++--
 src/lua/msgpackffi.lua               |  38 +++++
 src/lua/utils.c                      |  16 ++-
 src/lua/utils.h                      |   8 +-
 test/app-tap/lua/serializer_test.lua |  14 ++
 test/app-tap/msgpackffi.test.lua     |   3 +-
 test/app/decimal.result              | 146 +++++++++----------
 test/app/msgpack.result              |  41 ++++++
 test/app/msgpack.test.lua            |  15 ++
 test/box/tuple.result                |  85 +++++++++++
 test/box/tuple.test.lua              |  23 +++
 test/engine/decimal.result           |  81 +++++++++++
 test/engine/decimal.test.lua         |  21 +++
 test/unit/decimal.c                  |  60 +++++++-
 test/unit/decimal.result             | 202 ++++++++++++++++++++++++++-
 third_party/lua-cjson/lua_cjson.c    |  12 +-
 third_party/lua-yaml/lyaml.cc        |  10 +-
 26 files changed, 942 insertions(+), 98 deletions(-)
 create mode 100644 src/lib/core/mp_decimal.c
 create mode 100644 src/lib/core/mp_decimal.h
 create mode 100644 src/lib/core/mp_extension_types.h
 create mode 100644 test/engine/decimal.result
 create mode 100644 test/engine/decimal.test.lua

diff --git a/extra/exports b/extra/exports
index b8c42c0df..7b84a1452 100644
--- a/extra/exports
+++ b/extra/exports
@@ -62,8 +62,12 @@ PMurHash32_Result
 crc32_calc
 mp_encode_double
 mp_encode_float
+mp_encode_decimal
 mp_decode_double
 mp_decode_float
+mp_decode_extl
+mp_sizeof_decimal
+decimal_unpack
 
 log_type
 say_set_log_level
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index 4b96d1105..e60b5199e 100644
--- a/src/lib/core/CMakeLists.txt
+++ b/src/lib/core/CMakeLists.txt
@@ -27,6 +27,7 @@ set(core_sources
     mpstream.c
     port.c
     decimal.c
+    mp_decimal.c
 )
 
 if (TARGET_OS_NETBSD)
diff --git a/src/lib/core/mp_decimal.c b/src/lib/core/mp_decimal.c
new file mode 100644
index 000000000..985e75291
--- /dev/null
+++ b/src/lib/core/mp_decimal.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "mp_decimal.h"
+#include "mp_extension_types.h"
+#include "msgpuck.h"
+#include "decimal.h"
+
+uint32_t
+mp_sizeof_decimal(const decimal_t *dec)
+{
+	return mp_sizeof_ext(decimal_len(dec));
+}
+
+decimal_t *
+mp_decode_decimal(const char **data, decimal_t *dec)
+{
+	if (mp_typeof(**data) != MP_EXT)
+		return NULL;
+
+	int8_t type;
+	uint32_t len;
+	const char *const svp = *data;
+
+	len = mp_decode_extl(data, &type);
+
+	if (type != MP_DECIMAL || len == 0) {
+		*data = svp;
+		return NULL;
+	}
+	decimal_t *res = decimal_unpack(data,  len, dec);
+	if (!res)
+		*data = svp;
+	return res;
+}
+
+char *
+mp_encode_decimal(char *data, const decimal_t *dec)
+{
+	uint32_t len = decimal_len(dec);
+	data = mp_encode_extl(data, MP_DECIMAL, len);
+	data = decimal_pack(data, dec);
+	return data;
+}
diff --git a/src/lib/core/mp_decimal.h b/src/lib/core/mp_decimal.h
new file mode 100644
index 000000000..a991a5f16
--- /dev/null
+++ b/src/lib/core/mp_decimal.h
@@ -0,0 +1,62 @@
+#ifndef TARANTOOL_LIB_CORE_MP_DECIMAL_INCLUDED
+#define TARANTOOL_LIB_CORE_MP_DECIMAL_INCLUDED
+/*
+ * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "decimal.h"
+#include <stdint.h>
+
+/**
+ * \brief Calculate exact buffer size needed to store a decimal
+ * pointed to by \a dec.
+ */
+uint32_t
+mp_sizeof_decimal(const decimal_t *dec);
+
+/**
+ * \brief Decode a decimal from MsgPack \a data
+ * \param data - buffer pointer
+ * \return the decoded decimal
+ * \post *data = *data + mp_sizeof_decimal(retval)
+ */
+decimal_t *
+mp_decode_decimal(const char **data, decimal_t *dec);
+
+/**
+ * \brief Encode a decimal pointed to by \a dec.
+ * \parad dec - decimal pointer
+ * \param data - a buffer
+ * \return \a data + mp_sizeof_decimal(\a dec)
+ */
+char *
+mp_encode_decimal(char *data, const decimal_t *dec);
+
+#endif
diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
new file mode 100644
index 000000000..bc9873f68
--- /dev/null
+++ b/src/lib/core/mp_extension_types.h
@@ -0,0 +1,47 @@
+#ifndef TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED
+#define TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED
+/*
+ * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "msgpuck.h"
+
+/**
+ * MessagePack extension type. Used as a subtype after MP_EXT
+ * format specifier.
+ * Values in range [-128, -1] are reserved.
+ * You may assign values in range [0, 127]
+ */
+enum mp_extension_type {
+    MP_UNKNOWN_EXTENSION = 0,
+    MP_DECIMAL = 1,
+};
+
+#endif
diff --git a/src/lib/core/mpstream.c b/src/lib/core/mpstream.c
index 8b7276ab1..2be1797d0 100644
--- a/src/lib/core/mpstream.c
+++ b/src/lib/core/mpstream.c
@@ -33,6 +33,7 @@
 #include <assert.h>
 #include <stdint.h>
 #include "msgpuck.h"
+#include "mp_decimal.h"
 
 void
 mpstream_reserve_slow(struct mpstream *stream, size_t size)
@@ -186,6 +187,16 @@ mpstream_encode_binl(struct mpstream *stream, uint32_t len)
 	mpstream_advance(stream, pos - data);
 }
 
+void
+mpstream_encode_decimal(struct mpstream *stream, const decimal_t *val)
+{
+	char *data = mpstream_reserve(stream, mp_sizeof_decimal(val));
+	if (data == NULL)
+		return;
+	char *pos = mp_encode_decimal(data, val);
+	mpstream_advance(stream, pos - data);
+}
+
 void
 mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n)
 {
diff --git a/src/lib/core/mpstream.h b/src/lib/core/mpstream.h
index 69fa76f7f..3a022daa0 100644
--- a/src/lib/core/mpstream.h
+++ b/src/lib/core/mpstream.h
@@ -32,6 +32,7 @@
  */
 
 #include "diag.h"
+#include "decimal.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -136,6 +137,9 @@ mpstream_encode_bool(struct mpstream *stream, bool val);
 void
 mpstream_encode_binl(struct mpstream *stream, uint32_t len);
 
+void
+mpstream_encode_decimal(struct mpstream *stream, const decimal_t *val);
+
 /** Copies n bytes from memory area src to stream. */
 void
 mpstream_memcpy(struct mpstream *stream, const void *src, uint32_t n);
diff --git a/src/lua/decimal.c b/src/lua/decimal.c
index 7f9358787..23e50ba68 100644
--- a/src/lua/decimal.c
+++ b/src/lua/decimal.c
@@ -79,10 +79,8 @@ ldecimal_##name(struct lua_State *L) {						\
 	return 1;								\
 }
 
-static uint32_t CTID_DECIMAL;
-
 /** Push a new decimal on the stack and return a pointer to it. */
-static decimal_t *
+decimal_t *
 lua_pushdecimal(struct lua_State *L)
 {
 	decimal_t *res = luaL_pushcdata(L, CTID_DECIMAL);
diff --git a/src/lua/decimal.h b/src/lua/decimal.h
index 0485d11ef..b13e59247 100644
--- a/src/lua/decimal.h
+++ b/src/lua/decimal.h
@@ -31,12 +31,19 @@
 #ifndef TARANTOOL_LUA_DECIMAL_H_INCLUDED
 #define TARANTOOL_LUA_DECIMAL_H_INCLUDED
 
+#include "lib/core/decimal.h"
+
 #if defined(__cplusplus)
 extern "C" {
 #endif /* defined(__cplusplus) */
 
+extern uint32_t CTID_DECIMAL;
+
 struct lua_State;
 
+decimal_t *
+lua_pushdecimal(struct lua_State *L);
+
 void
 tarantool_lua_decimal_init(struct lua_State *L);
 
diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
index 2126988eb..c2be0b3e8 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -41,6 +41,10 @@
 #include <small/region.h>
 #include <small/ibuf.h>
 
+#include "lua/decimal.h" /* lua_pushdecimal() */
+#include "lib/core/decimal.h" /* decimal_unpack() */
+#include "lib/core/mp_extension_types.h"
+
 #include <fiber.h>
 
 void
@@ -175,16 +179,22 @@ restart: /* used by MP_EXT */
 		assert(lua_gettop(L) == top);
 		return MP_ARRAY;
 	case MP_EXT:
-		/* Run trigger if type can't be encoded */
-		type = luamp_encode_extension(L, top, stream);
-		if (type != MP_EXT)
-			return type; /* Value has been packed by the trigger */
-		/* Try to convert value to serializable type */
-		luaL_convertfield(L, cfg, top, field);
-		/* handled by luaL_convertfield */
-		assert(field->type != MP_EXT);
-		assert(lua_gettop(L) == top);
-		goto restart;
+		switch (field->ext_type) {
+		case MP_DECIMAL:
+			mpstream_encode_decimal(stream, field->decval);
+			return MP_EXT;
+		default:
+			/* Run trigger if type can't be encoded */
+			type = luamp_encode_extension(L, top, stream);
+			if (type != MP_EXT)
+				return type; /* Value has been packed by the trigger */
+			/* Try to convert value to serializable type */
+			luaL_convertfield(L, cfg, top, field);
+			/* handled by luaL_convertfield */
+			assert(field->type != MP_EXT);
+			assert(lua_gettop(L) == top);
+			goto restart;
+		}
 	}
 	return MP_EXT;
 }
@@ -283,9 +293,30 @@ luamp_decode(struct lua_State *L, struct luaL_serializer *cfg,
 		return;
 	}
 	case MP_EXT:
-		luamp_decode_extension(L, data);
+	{
+		int8_t ext_type;
+		const char *svp = *data;
+		uint32_t len = mp_decode_extl(data, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+		{
+			decimal_t *dec = lua_pushdecimal(L);
+			dec = decimal_unpack(data, len, dec);
+			if (dec == NULL) {
+				lua_pop(L, -1);
+				luaL_error(L, "msgpack.decode: invalid MsgPack");
+			}
+			return;
+		}
+		default:
+			/* reset data to the extension header */
+			*data = svp;
+			luamp_decode_extension(L, data);
+			break;
+		}
 		break;
 	}
+	}
 }
 
 
diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua
index bfeedbc4b..f7ee44291 100644
--- a/src/lua/msgpackffi.lua
+++ b/src/lua/msgpackffi.lua
@@ -17,10 +17,18 @@ char *
 mp_encode_float(char *data, float num);
 char *
 mp_encode_double(char *data, double num);
+char *
+mp_encode_decimal(char *data, decimal_t *dec);
+uint32_t
+mp_sizeof_decimal(const decimal_t *dec);
 float
 mp_decode_float(const char **data);
 double
 mp_decode_double(const char **data);
+uint32_t
+mp_decode_extl(const char **data, int8_t *type);
+decimal_t *
+decimal_unpack(const char **data, uint32_t len, decimal_t *dec);
 ]])
 
 local strict_alignment = (jit.arch == 'arm')
@@ -117,6 +125,11 @@ local function encode_double(buf, num)
     builtin.mp_encode_double(p, num)
 end
 
+local function encode_decimal(buf, num)
+    local p = buf:alloc(builtin.mp_sizeof_decimal(num))
+    builtin.mp_encode_decimal(p, num)
+end
+
 local function encode_int(buf, num)
     if num >= 0 then
         if num <= 0x7f then
@@ -294,6 +307,7 @@ on_encode(ffi.typeof('const unsigned char'), encode_int)
 on_encode(ffi.typeof('bool'), encode_bool_cdata)
 on_encode(ffi.typeof('float'), encode_float)
 on_encode(ffi.typeof('double'), encode_double)
+on_encode(ffi.typeof('decimal_t'), encode_decimal)
 
 --------------------------------------------------------------------------------
 -- Decoder
@@ -473,6 +487,28 @@ local function decode_map(data, size)
     return setmetatable(map, msgpack.map_mt)
 end
 
+local ext_decoder = {
+    -- MP_UNKNOWN_EXTENSION
+    [0] = function(data, len) error("unsupported extension type") end,
+    -- MP_DECIMAL
+    [1] = function(data, len) local num = ffi.new("decimal_t") builtin.decimal_unpack(data, len, num) return num end,
+}
+
+local function decode_ext(data)
+    local t = ffi.new("int8_t[1]")
+    -- mp_decode_extl and mp_decode_decimal
+    -- need type code
+    data[0] = data[0] - 1
+    local old_data = data[0]
+    local len = builtin.mp_decode_extl(data, t)
+    local fun = ext_decoder[t[0]]
+    if type(fun) == 'function' then
+        return fun(data, len)
+    else
+        error("Unsupported extension type")
+    end
+end
+
 local decoder_hint = {
     --[[{{{ MP_BIN]]
     [0xc4] = function(data) return decode_str(data, decode_u8(data)) end;
@@ -528,6 +564,8 @@ decode_r = function(data)
         return false
     elseif c == 0xc3 then
         return true
+    elseif c >= 0xd4 and c <= 0xd8 or c >= 0xc7 and c <= 0xc9 then
+        return decode_ext(data)
     else
         local fun = decoder_hint[c];
         assert (type(fun) == "function")
diff --git a/src/lua/utils.c b/src/lua/utils.c
index 0a4bcf517..75efe0ed2 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -45,6 +45,8 @@ static uint32_t CTID_STRUCT_IBUF;
 static uint32_t CTID_STRUCT_IBUF_PTR;
 uint32_t CTID_CHAR_PTR;
 uint32_t CTID_CONST_CHAR_PTR;
+uint32_t CTID_DECIMAL;
+
 
 void *
 luaL_pushcdata(struct lua_State *L, uint32_t ctypeid)
@@ -723,6 +725,12 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index,
 			/* Fall through */
 		default:
 			field->type = MP_EXT;
+			if (cd->ctypeid == CTID_DECIMAL) {
+				field->ext_type = MP_DECIMAL;
+				field->decval = (decimal_t *) cdata;
+			} else {
+				field->ext_type = MP_UNKNOWN_EXTENSION;
+			}
 		}
 		return 0;
 	}
@@ -754,6 +762,7 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index,
 		/* Fall through */
 	default:
 		field->type = MP_EXT;
+		field->ext_type = MP_UNKNOWN_EXTENSION;
 	}
 #undef CHECK_NUMBER
 	return 0;
@@ -765,7 +774,7 @@ luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
 {
 	if (idx < 0)
 		idx = lua_gettop(L) + idx + 1;
-	assert(field->type == MP_EXT); /* must be called after tofield() */
+	assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */
 
 	if (cfg->encode_load_metatables) {
 		int type = lua_type(L, idx);
@@ -782,10 +791,11 @@ luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
 		}
 	}
 
-	if (field->type == MP_EXT && cfg->encode_use_tostring)
+	if (field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION &&
+	    cfg->encode_use_tostring)
 		lua_field_tostring(L, cfg, idx, field);
 
-	if (field->type != MP_EXT)
+	if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION)
 		return;
 
 	if (cfg->encode_invalid_as_nil) {
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 7e7cdc0c6..d42cc3992 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -55,6 +55,9 @@ extern "C" {
 
 #include "lua/error.h"
 
+#include "lib/core/mp_extension_types.h"
+#include "lib/core/decimal.h" /* decimal_t */
+
 struct lua_State;
 struct ibuf;
 
@@ -286,8 +289,11 @@ struct luaL_field {
 		bool bval;
 		/* Array or map. */
 		uint32_t size;
+		decimal_t *decval;
 	};
 	enum mp_type type;
+	/* subtypes of MP_EXT */
+	enum mp_extension_type ext_type;
 	bool compact;                /* a flag used by YAML serializer */
 };
 
@@ -373,7 +379,7 @@ luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
 {
 	if (luaL_tofield(L, cfg, idx, field) < 0)
 		luaT_error(L);
-	if (field->type != MP_EXT)
+	if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION)
 		return;
 	luaL_convertfield(L, cfg, idx, field);
 }
diff --git a/test/app-tap/lua/serializer_test.lua b/test/app-tap/lua/serializer_test.lua
index a48e557a3..e7048da4d 100644
--- a/test/app-tap/lua/serializer_test.lua
+++ b/test/app-tap/lua/serializer_test.lua
@@ -189,6 +189,17 @@ local function test_double(test, s)
     ss = nil
 end
 
+local function test_decimal(test, s)
+    local decimal = require('decimal')
+    test:plan(10)
+
+    rt(test, s, decimal.new(1), 'cdata')
+    rt(test, s, decimal.new('1e37'), 'cdata')
+    rt(test, s, decimal.new('1e-38'), 'cdata')
+    rt(test, s, decimal.new('1234567891234567890.0987654321987654321'), 'cdata')
+    rt(test, s, decimal.new('-1234567891234567890.0987654321987654321'), 'cdata')
+end
+
 local function test_boolean(test, s)
     test:plan(4)
 
@@ -223,6 +234,8 @@ local function test_nil(test, s)
     test:is(t[4], s.NULL, "sparse array with NULL")
 end
 
+
+
 local function test_table(test, s, is_array, is_map)
     test:plan(s.cfg and 31 or 13)
 
@@ -398,4 +411,5 @@ return {
     test_nil = test_nil;
     test_table = test_table;
     test_ucdata = test_ucdata;
+    test_decimal = test_decimal;
 }
diff --git a/test/app-tap/msgpackffi.test.lua b/test/app-tap/msgpackffi.test.lua
index f9646335b..f2a8f254b 100755
--- a/test/app-tap/msgpackffi.test.lua
+++ b/test/app-tap/msgpackffi.test.lua
@@ -72,10 +72,11 @@ end
 
 tap.test("msgpackffi", function(test)
     local serializer = require('msgpackffi')
-    test:plan(9)
+    test:plan(10)
     test:test("unsigned", common.test_unsigned, serializer)
     test:test("signed", common.test_signed, serializer)
     test:test("double", common.test_double, serializer)
+    test:test("decimal", common.test_decimal, serializer)
     test:test("boolean", common.test_boolean, serializer)
     test:test("string", common.test_string, serializer)
     test:test("nil", common.test_nil, serializer)
diff --git a/test/app/decimal.result b/test/app/decimal.result
index 2e44928bb..8251e13d8 100644
--- a/test/app/decimal.result
+++ b/test/app/decimal.result
@@ -12,77 +12,77 @@ ffi = require('ffi')
 -- check various constructors
 decimal.new('1234.5678')
  | ---
- | - '1234.5678'
+ | - 1234.5678
  | ...
 decimal.new('1e6')
  | ---
- | - '1000000'
+ | - 1000000
  | ...
 decimal.new('-6.234612e2')
  | ---
- | - '-623.4612'
+ | - -623.4612
  | ...
 -- check (u)int16/32/64_t
 decimal.new(2ULL ^ 63)
  | ---
- | - '9223372036854775808'
+ | - 9223372036854775808
  | ...
 decimal.new(123456789123456789ULL)
  | ---
- | - '123456789123456789'
+ | - 123456789123456789
  | ...
 decimal.new(-123456789123456789LL)
  | ---
- | - '-123456789123456789'
+ | - -123456789123456789
  | ...
 decimal.new(ffi.new('uint8_t', 231))
  | ---
- | - '231'
+ | - 231
  | ...
 decimal.new(ffi.new('int8_t', -113))
  | ---
- | - '-113'
+ | - -113
  | ...
 decimal.new(ffi.new('uint16_t', 65535))
  | ---
- | - '65535'
+ | - 65535
  | ...
 decimal.new(ffi.new('int16_t', -31263))
  | ---
- | - '-31263'
+ | - -31263
  | ...
 decimal.new(ffi.new('uint32_t', 4123123123))
  | ---
- | - '4123123123'
+ | - 4123123123
  | ...
 decimal.new(ffi.new('int32_t', -2123123123))
  | ---
- | - '-2123123123'
+ | - -2123123123
  | ...
 decimal.new(ffi.new('float', 128.5))
  | ---
- | - '128.5'
+ | - 128.5
  | ...
 decimal.new(ffi.new('double', 128.5))
  | ---
- | - '128.5'
+ | - 128.5
  | ...
 
 decimal.new(1)
  | ---
- | - '1'
+ | - 1
  | ...
 decimal.new(-1)
  | ---
- | - '-1'
+ | - -1
  | ...
 decimal.new(2^64)
  | ---
- | - '18446744073709600000'
+ | - 18446744073709600000
  | ...
 decimal.new(2^(-20))
  | ---
- | - '0.00000095367431640625'
+ | - 0.00000095367431640625
  | ...
 
 -- incorrect constructions
@@ -128,38 +128,38 @@ a = decimal.new('10')
  | ...
 a
  | ---
- | - '10'
+ | - 10
  | ...
 b = decimal.new('0.1')
  | ---
  | ...
 b
  | ---
- | - '0.1'
+ | - 0.1
  | ...
 a + b
  | ---
- | - '10.1'
+ | - 10.1
  | ...
 a - b
  | ---
- | - '9.9'
+ | - 9.9
  | ...
 a * b
  | ---
- | - '1.0'
+ | - 1.0
  | ...
 a / b
  | ---
- | - '100'
+ | - 100
  | ...
 a ^ b
  | ---
- | - '1.2589254117941672104239541063958006061'
+ | - 1.2589254117941672104239541063958006061
  | ...
 b ^ a
  | ---
- | - '0.0000000001'
+ | - 0.0000000001
  | ...
 -a + -b == -(a + b)
  | ---
@@ -167,11 +167,11 @@ b ^ a
  | ...
 a
  | ---
- | - '10'
+ | - 10
  | ...
 b
  | ---
- | - '0.1'
+ | - 0.1
  | ...
 
 a < b
@@ -216,11 +216,11 @@ a ~= b
  | ...
 a
  | ---
- | - '10'
+ | - 10
  | ...
 b
  | ---
- | - '0.1'
+ | - 0.1
  | ...
 
 -- check comparsion with nil
@@ -251,19 +251,19 @@ a <= nil
 
 decimal.sqrt(a)
  | ---
- | - '3.1622776601683793319988935444327185337'
+ | - 3.1622776601683793319988935444327185337
  | ...
 decimal.ln(a)
  | ---
- | - '2.3025850929940456840179914546843642076'
+ | - 2.3025850929940456840179914546843642076
  | ...
 decimal.log10(a)
  | ---
- | - '1'
+ | - 1
  | ...
 decimal.exp(a)
  | ---
- | - '22026.465794806716516957900645284244366'
+ | - 22026.465794806716516957900645284244366
  | ...
 a == decimal.ln(decimal.exp(a))
  | ---
@@ -287,7 +287,7 @@ a + -a == 0
  | ...
 a
  | ---
- | - '10'
+ | - 10
  | ...
 
 a = decimal.new('1.1234567891234567891234567891234567891')
@@ -295,7 +295,7 @@ a = decimal.new('1.1234567891234567891234567891234567891')
  | ...
 a
  | ---
- | - '1.1234567891234567891234567891234567891'
+ | - 1.1234567891234567891234567891234567891
  | ...
 decimal.precision(a)
  | ---
@@ -311,7 +311,7 @@ decimal.round(a, 37) == a
  | ...
 a
  | ---
- | - '1.1234567891234567891234567891234567891'
+ | - 1.1234567891234567891234567891234567891
  | ...
 a = decimal.round(a, 36)
  | ---
@@ -335,19 +335,19 @@ decimal.round(a, -5) == a
  | ...
 decimal.round(a, 7)
  | ---
- | - '1.1234568'
+ | - 1.1234568
  | ...
 decimal.round(a, 3)
  | ---
- | - '1.123'
+ | - 1.123
  | ...
 decimal.round(a, 0)
  | ---
- | - '1'
+ | - 1
  | ...
 a
  | ---
- | - '1.123456789123456789123456789123456789'
+ | - 1.123456789123456789123456789123456789
  | ...
 
 decimal.ln(0)
@@ -360,7 +360,7 @@ decimal.ln(-1)
  | ...
 decimal.ln(1)
  | ---
- | - '0'
+ | - 0
  | ...
 decimal.log10(0)
  | ---
@@ -372,7 +372,7 @@ decimal.log10(-1)
  | ...
 decimal.log10(1)
  | ---
- | - '0'
+ | - 0
  | ...
 decimal.exp(88)
  | ---
@@ -380,7 +380,7 @@ decimal.exp(88)
  | ...
 decimal.exp(87)
  | ---
- | - '60760302250568721495223289381302760753'
+ | - 60760302250568721495223289381302760753
  | ...
 decimal.sqrt(-5)
  | ---
@@ -388,7 +388,7 @@ decimal.sqrt(-5)
  | ...
 decimal.sqrt(5)
  | ---
- | - '2.2360679774997896964091736687312762354'
+ | - 2.2360679774997896964091736687312762354
  | ...
 
 -- various incorrect operands
@@ -434,11 +434,11 @@ a ^ 2
  | ...
 a ^ 1.9
  | ---
- | - '1258925411794167210423954106395800606.1'
+ | - 1258925411794167210423954106395800606.1
  | ...
 a * '1e18'
  | ---
- | - '10000000000000000000000000000000000000'
+ | - 10000000000000000000000000000000000000
  | ...
 a = decimal.new(string.rep('9', 38))
  | ---
@@ -461,7 +461,7 @@ a + '0.5'
  | ...
 a + '0.4'
  | ---
- | - '99999999999999999999999999999999999999'
+ | - 99999999999999999999999999999999999999
  | ...
 a / 0.5
  | ---
@@ -477,7 +477,7 @@ a = decimal.new('-13')
  | ...
 a ^ 2
  | ---
- | - '169'
+ | - 169
  | ...
 -- fractional powers are allowed only for positive numbers
 a ^ 2.5
@@ -490,69 +490,69 @@ a = decimal.new('1e5')
  | ...
 a
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.trim(a)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.trim(decimal.rescale(a, 10))
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.rescale(a, 10)
  | ---
- | - '100000.0000000000'
+ | - 100000.0000000000
  | ...
 decimal.rescale(a, -5)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.rescale(a, 0)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.rescale(a, 32)
  | ---
- | - '100000.00000000000000000000000000000000'
+ | - 100000.00000000000000000000000000000000
  | ...
 -- scale too big
 decimal.rescale(a, 33)
  | ---
- | - '100000'
+ | - 100000
  | ...
 decimal.trim(decimal.rescale(a, 10))
  | ---
- | - '100000'
+ | - 100000
  | ...
 a = decimal.new('123.456789000000000')
  | ---
  | ...
 a
  | ---
- | - '123.456789000000000'
+ | - 123.456789000000000
  | ...
 decimal.trim(a)
  | ---
- | - '123.456789'
+ | - 123.456789
  | ...
 
 -- check correct rounding when scale = 0
 decimal.round(decimal.new(0.9), 0)
  | ---
- | - '1'
+ | - 1
  | ...
 decimal.round(decimal.new(9.9), 0)
  | ---
- | - '10'
+ | - 10
  | ...
 decimal.round(decimal.new(99.9), 0)
  | ---
- | - '100'
+ | - 100
  | ...
 decimal.round(decimal.new(99.4), 0)
  | ---
- | - '99'
+ | - 99
  | ...
 
 -- check remainder operation
@@ -561,37 +561,37 @@ a = decimal.new(172.51)
  | ...
 a % 1
  | ---
- | - '0.51'
+ | - 0.51
  | ...
 a % 2
  | ---
- | - '0.51'
+ | - 0.51
  | ...
 a % 0.3
  | ---
- | - '0.01'
+ | - 0.01
  | ...
 a % 0.13
  | ---
- | - '0.00'
+ | - 0.00
  | ...
 a % 13.27
  | ---
- | - '0.00'
+ | - 0.00
  | ...
 a % 100
  | ---
- | - '72.51'
+ | - 72.51
  | ...
 a % 173
  | ---
- | - '172.51'
+ | - 172.51
  | ...
 a % 72
  | ---
- | - '28.51'
+ | - 28.51
  | ...
 720 % a
  | ---
- | - '29.96'
+ | - 29.96
  | ...
diff --git a/test/app/msgpack.result b/test/app/msgpack.result
index a67c05d38..4b5aec784 100644
--- a/test/app/msgpack.result
+++ b/test/app/msgpack.result
@@ -252,3 +252,44 @@ msgpack.decode(ffi.cast('char *', '\x04\x05\x06'), -1)
 ---
 - error: 'msgpack.decode: size can''t be negative'
 ...
+--
+-- gh-4333: msgpack encode/decode decimals.
+--
+decimal = require('decimal')
+---
+...
+a = decimal.new('1e37')
+---
+...
+b = decimal.new('1e-38')
+---
+...
+c = decimal.new('1')
+---
+...
+d = decimal.new('0.1234567')
+---
+...
+e = decimal.new('123.4567')
+---
+...
+msgpack.decode(msgpack.encode(a)) == a
+---
+- true
+...
+msgpack.decode(msgpack.encode(b)) == b
+---
+- true
+...
+msgpack.decode(msgpack.encode(c)) == c
+---
+- true
+...
+msgpack.decode(msgpack.encode(d)) == d
+---
+- true
+...
+msgpack.decode(msgpack.encode(e)) == e
+---
+- true
+...
diff --git a/test/app/msgpack.test.lua b/test/app/msgpack.test.lua
index e0880ac22..9224d870a 100644
--- a/test/app/msgpack.test.lua
+++ b/test/app/msgpack.test.lua
@@ -84,3 +84,18 @@ size = msgpack.encode(100, buf)
 -- is not negative.
 --
 msgpack.decode(ffi.cast('char *', '\x04\x05\x06'), -1)
+
+--
+-- gh-4333: msgpack encode/decode decimals.
+--
+decimal = require('decimal')
+a = decimal.new('1e37')
+b = decimal.new('1e-38')
+c = decimal.new('1')
+d = decimal.new('0.1234567')
+e = decimal.new('123.4567')
+msgpack.decode(msgpack.encode(a)) == a
+msgpack.decode(msgpack.encode(b)) == b
+msgpack.decode(msgpack.encode(c)) == c
+msgpack.decode(msgpack.encode(d)) == d
+msgpack.decode(msgpack.encode(e)) == e
diff --git a/test/box/tuple.result b/test/box/tuple.result
index 2561ebc35..a5010538d 100644
--- a/test/box/tuple.result
+++ b/test/box/tuple.result
@@ -1252,3 +1252,88 @@ s2:frommap({a="1", k="11"}):tomap({names_only = true})
 s2:drop()
 ---
 ...
+-- test decimals in tuple
+dec = require('decimal')
+---
+...
+a = dec.new('1')
+---
+...
+b = dec.new('1e10')
+---
+...
+c = dec.new('1e-10')
+---
+...
+d = box.tuple.new{5, a, 6, b, 7, c, "string"}
+---
+...
+d
+---
+- [5, 1, 6, 10000000000, 7, 0.0000000001, 'string']
+...
+state, val = d:next()
+---
+...
+state
+---
+- 1
+...
+val
+---
+- 5
+...
+state, val = d:next(state)
+---
+...
+state
+---
+- 2
+...
+val
+---
+- 1
+...
+state, val = d:next(state)
+---
+...
+state, val
+---
+- 3
+- 6
+...
+d:slice(1)
+---
+- 1
+- 6
+- 10000000000
+- 7
+- 0.0000000001
+- string
+...
+d:slice(-1)
+---
+- string
+...
+d:slice(-2)
+---
+- 0.0000000001
+- string
+...
+msgpack.decode(msgpackffi.encode(d))
+---
+- [5, 1, 6, 10000000000, 7, 0.0000000001, 'string']
+- 24
+...
+d:bsize()
+---
+- 23
+...
+d:update{{'!', 3, dec.new('1234.5678')}}
+---
+- [5, 1, 1234.5678, 6, 10000000000, 7, 0.0000000001, 'string']
+...
+d:update{{'=', -1, dec.new('0.12345678910111213')}}
+---
+- [5, 1, 6, 10000000000, 7, 0.0000000001, 0.12345678910111213]
+...
diff --git a/test/box/tuple.test.lua b/test/box/tuple.test.lua
index b0a4ab173..8d4431bc6 100644
--- a/test/box/tuple.test.lua
+++ b/test/box/tuple.test.lua
@@ -426,3 +426,26 @@ s2:frommap({a="1", k="11"})
 s2:frommap({a="1", k="11"}):tomap({names_only = true})
 
 s2:drop()
+
+-- test decimals in tuple
+dec = require('decimal')
+a = dec.new('1')
+b = dec.new('1e10')
+c = dec.new('1e-10')
+d = box.tuple.new{5, a, 6, b, 7, c, "string"}
+d
+state, val = d:next()
+state
+val
+state, val = d:next(state)
+state
+val
+state, val = d:next(state)
+state, val
+d:slice(1)
+d:slice(-1)
+d:slice(-2)
+msgpack.decode(msgpackffi.encode(d))
+d:bsize()
+d:update{{'!', 3, dec.new('1234.5678')}}
+d:update{{'=', -1, dec.new('0.12345678910111213')}}
diff --git a/test/engine/decimal.result b/test/engine/decimal.result
new file mode 100644
index 000000000..f8888d7c9
--- /dev/null
+++ b/test/engine/decimal.result
@@ -0,0 +1,81 @@
+-- test-run result file version 2
+env = require('test_run')
+ | ---
+ | ...
+test_run = env.new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+
+decimal = require('decimal')
+ | ---
+ | ...
+
+_ = box.schema.space.create('test', {engine=engine})
+ | ---
+ | ...
+_ = box.space.test:create_index('pk')
+ | ---
+ | ...
+box.space.test:insert{1, decimal.new(1.1)}
+ | ---
+ | - [1, 1.1]
+ | ...
+box.space.test:insert{2, decimal.new(2.2)}
+ | ---
+ | - [2, 2.2]
+ | ...
+box.space.test:insert{3, decimal.new(1.1)}
+ | ---
+ | - [3, 1.1]
+ | ...
+box.space.test:insert{4, decimal.new('1234567890123456789.9876543210987654321'), decimal.new(1.2345)}
+ | ---
+ | - [4, 1234567890123456789.9876543210987654321, 1.2345]
+ | ...
+box.space.test:select{}
+ | ---
+ | - - [1, 1.1]
+ |   - [2, 2.2]
+ |   - [3, 1.1]
+ |   - [4, 1234567890123456789.9876543210987654321, 1.2345]
+ | ...
+a = box.space.test:get{4}
+ | ---
+ | ...
+a:next()
+ | ---
+ | - 1
+ | - 4
+ | ...
+a:next(1)
+ | ---
+ | - 2
+ | - 1234567890123456789.9876543210987654321
+ | ...
+a:next(2)
+ | ---
+ | - 3
+ | - 1.2345
+ | ...
+a:slice(-2)
+ | ---
+ | - 1234567890123456789.9876543210987654321
+ | - 1.2345
+ | ...
+box.space.test:replace{3, decimal.new(3.3)}
+ | ---
+ | - [3, 3.3]
+ | ...
+box.space.test:select{}
+ | ---
+ | - - [1, 1.1]
+ |   - [2, 2.2]
+ |   - [3, 3.3]
+ |   - [4, 1234567890123456789.9876543210987654321, 1.2345]
+ | ...
+box.space.test:drop()
+ | ---
+ | ...
diff --git a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua
new file mode 100644
index 000000000..1b14871b0
--- /dev/null
+++ b/test/engine/decimal.test.lua
@@ -0,0 +1,21 @@
+env = require('test_run')
+test_run = env.new()
+engine = test_run:get_cfg('engine')
+
+decimal = require('decimal')
+
+_ = box.schema.space.create('test', {engine=engine})
+_ = box.space.test:create_index('pk')
+box.space.test:insert{1, decimal.new(1.1)}
+box.space.test:insert{2, decimal.new(2.2)}
+box.space.test:insert{3, decimal.new(1.1)}
+box.space.test:insert{4, decimal.new('1234567890123456789.9876543210987654321'), decimal.new(1.2345)}
+box.space.test:select{}
+a = box.space.test:get{4}
+a:next()
+a:next(1)
+a:next(2)
+a:slice(-2)
+box.space.test:replace{3, decimal.new(3.3)}
+box.space.test:select{}
+box.space.test:drop()
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index b55333ed7..625913efe 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -1,5 +1,8 @@
 #include "unit.h"
 #include "decimal.h"
+#include "mp_decimal.h"
+#include "mp_extension_types.h"
+#include "msgpuck.h"
 #include <limits.h>
 #include <string.h>
 #include <float.h> /* DBL_DIG */
@@ -71,6 +74,32 @@
 
 char buf[32];
 
+#define test_mpdec(str) ({\
+	decimal_t dec;\
+	decimal_from_string(&dec, str);\
+	uint32_t l1 = mp_sizeof_decimal(&dec);\
+	ok(l1 <= 25 && l1 >= 4, "mp_sizeof_decimal("str")");\
+	char *b1 = mp_encode_decimal(buf, &dec);\
+	is(b1, buf + l1, "mp_sizeof_decimal("str") == len(mp_encode_decimal("str"))");\
+	const char *b2 = buf;\
+	const char *b3 = buf;\
+	decimal_t d2;\
+	mp_next(&b3);\
+	is(b3, b1, "mp_next(mp_encode("str"))");\
+	mp_decode_decimal(&b2, &d2);\
+	is(b1, b2, "mp_decode(mp_encode("str") len");\
+	is(decimal_compare(&dec, &d2), 0, "mp_decode(mp_encode("str")) value");\
+	is(decimal_scale(&dec), decimal_scale(&d2), "mp_decode(mp_encode("str")) scale");\
+	is(strcmp(decimal_to_string(&d2), str), 0, "str(mp_decode(mp_encode("str"))) == "str);\
+	b2 = buf;\
+	int8_t type;\
+	uint32_t l2 = mp_decode_extl(&b2, &type);\
+	is(type, MP_DECIMAL, "mp_ext_type is MP_DECIMAL");\
+	is(&d2, decimal_unpack(&b2, l2, &d2), "decimal_unpack() after mp_decode_extl()");\
+	is(decimal_compare(&dec, &d2), 0, "decimal_unpack() after mp_decode_extl() value");\
+	is(b2, buf + l1, "decimal_unpack() after mp_decode_extl() len");\
+})
+
 #define test_decpack(str) ({\
 	decimal_t dec;\
 	decimal_from_string(&dec, str);\
@@ -136,10 +165,37 @@ test_pack_unpack(void)
 	return check_plan();
 }
 
+static int
+test_mp_decimal(void)
+{
+	plan(198);
+
+	test_mpdec("0");
+	test_mpdec("-0");
+	test_mpdec("1");
+	test_mpdec("-1");
+	test_mpdec("0.1");
+	test_mpdec("-0.1");
+	test_mpdec("2.718281828459045");
+	test_mpdec("-2.718281828459045");
+	test_mpdec("3.141592653589793");
+	test_mpdec("-3.141592653589793");
+	test_mpdec("1234567891234567890.0987654321987654321");
+	test_mpdec("-1234567891234567890.0987654321987654321");
+	test_mpdec("0.0000000000000000000000000000000000001");
+	test_mpdec("-0.0000000000000000000000000000000000001");
+	test_mpdec("0.00000000000000000000000000000000000001");
+	test_mpdec("-0.00000000000000000000000000000000000001");
+	test_mpdec("99999999999999999999999999999999999999");
+	test_mpdec("-99999999999999999999999999999999999999");
+
+	return check_plan();
+}
+
 int
 main(void)
 {
-	plan(279);
+	plan(280);
 
 	dectest(314, 271, uint64, uint64_t);
 	dectest(65535, 23456, uint64, uint64_t);
@@ -201,5 +257,7 @@ main(void)
 
 	test_pack_unpack();
 
+	test_mp_decimal();
+
 	return check_plan();
 }
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index 2dd91af49..e6812a8dd 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -1,4 +1,4 @@
-1..279
+1..280
 ok 1 - decimal(314)
 ok 2 - decimal(271)
 ok 3 - decimal(314) + decimal(271)
@@ -430,3 +430,203 @@ ok 278 - decimal_sqrt(-10) - error on wrong operands.
     ok 150 - unpack malformed decimal fails
     ok 151 - decode malformed decimal preserves buffer position
 ok 279 - subtests
+    1..198
+    ok 1 - mp_sizeof_decimal(0)
+    ok 2 - mp_sizeof_decimal(0) == len(mp_encode_decimal(0))
+    ok 3 - mp_next(mp_encode(0))
+    ok 4 - mp_decode(mp_encode(0) len
+    ok 5 - mp_decode(mp_encode(0)) value
+    ok 6 - mp_decode(mp_encode(0)) scale
+    ok 7 - str(mp_decode(mp_encode(0))) == 0
+    ok 8 - mp_ext_type is MP_DECIMAL
+    ok 9 - decimal_unpack() after mp_decode_extl()
+    ok 10 - decimal_unpack() after mp_decode_extl() value
+    ok 11 - decimal_unpack() after mp_decode_extl() len
+    ok 12 - mp_sizeof_decimal(-0)
+    ok 13 - mp_sizeof_decimal(-0) == len(mp_encode_decimal(-0))
+    ok 14 - mp_next(mp_encode(-0))
+    ok 15 - mp_decode(mp_encode(-0) len
+    ok 16 - mp_decode(mp_encode(-0)) value
+    ok 17 - mp_decode(mp_encode(-0)) scale
+    ok 18 - str(mp_decode(mp_encode(-0))) == -0
+    ok 19 - mp_ext_type is MP_DECIMAL
+    ok 20 - decimal_unpack() after mp_decode_extl()
+    ok 21 - decimal_unpack() after mp_decode_extl() value
+    ok 22 - decimal_unpack() after mp_decode_extl() len
+    ok 23 - mp_sizeof_decimal(1)
+    ok 24 - mp_sizeof_decimal(1) == len(mp_encode_decimal(1))
+    ok 25 - mp_next(mp_encode(1))
+    ok 26 - mp_decode(mp_encode(1) len
+    ok 27 - mp_decode(mp_encode(1)) value
+    ok 28 - mp_decode(mp_encode(1)) scale
+    ok 29 - str(mp_decode(mp_encode(1))) == 1
+    ok 30 - mp_ext_type is MP_DECIMAL
+    ok 31 - decimal_unpack() after mp_decode_extl()
+    ok 32 - decimal_unpack() after mp_decode_extl() value
+    ok 33 - decimal_unpack() after mp_decode_extl() len
+    ok 34 - mp_sizeof_decimal(-1)
+    ok 35 - mp_sizeof_decimal(-1) == len(mp_encode_decimal(-1))
+    ok 36 - mp_next(mp_encode(-1))
+    ok 37 - mp_decode(mp_encode(-1) len
+    ok 38 - mp_decode(mp_encode(-1)) value
+    ok 39 - mp_decode(mp_encode(-1)) scale
+    ok 40 - str(mp_decode(mp_encode(-1))) == -1
+    ok 41 - mp_ext_type is MP_DECIMAL
+    ok 42 - decimal_unpack() after mp_decode_extl()
+    ok 43 - decimal_unpack() after mp_decode_extl() value
+    ok 44 - decimal_unpack() after mp_decode_extl() len
+    ok 45 - mp_sizeof_decimal(0.1)
+    ok 46 - mp_sizeof_decimal(0.1) == len(mp_encode_decimal(0.1))
+    ok 47 - mp_next(mp_encode(0.1))
+    ok 48 - mp_decode(mp_encode(0.1) len
+    ok 49 - mp_decode(mp_encode(0.1)) value
+    ok 50 - mp_decode(mp_encode(0.1)) scale
+    ok 51 - str(mp_decode(mp_encode(0.1))) == 0.1
+    ok 52 - mp_ext_type is MP_DECIMAL
+    ok 53 - decimal_unpack() after mp_decode_extl()
+    ok 54 - decimal_unpack() after mp_decode_extl() value
+    ok 55 - decimal_unpack() after mp_decode_extl() len
+    ok 56 - mp_sizeof_decimal(-0.1)
+    ok 57 - mp_sizeof_decimal(-0.1) == len(mp_encode_decimal(-0.1))
+    ok 58 - mp_next(mp_encode(-0.1))
+    ok 59 - mp_decode(mp_encode(-0.1) len
+    ok 60 - mp_decode(mp_encode(-0.1)) value
+    ok 61 - mp_decode(mp_encode(-0.1)) scale
+    ok 62 - str(mp_decode(mp_encode(-0.1))) == -0.1
+    ok 63 - mp_ext_type is MP_DECIMAL
+    ok 64 - decimal_unpack() after mp_decode_extl()
+    ok 65 - decimal_unpack() after mp_decode_extl() value
+    ok 66 - decimal_unpack() after mp_decode_extl() len
+    ok 67 - mp_sizeof_decimal(2.718281828459045)
+    ok 68 - mp_sizeof_decimal(2.718281828459045) == len(mp_encode_decimal(2.718281828459045))
+    ok 69 - mp_next(mp_encode(2.718281828459045))
+    ok 70 - mp_decode(mp_encode(2.718281828459045) len
+    ok 71 - mp_decode(mp_encode(2.718281828459045)) value
+    ok 72 - mp_decode(mp_encode(2.718281828459045)) scale
+    ok 73 - str(mp_decode(mp_encode(2.718281828459045))) == 2.718281828459045
+    ok 74 - mp_ext_type is MP_DECIMAL
+    ok 75 - decimal_unpack() after mp_decode_extl()
+    ok 76 - decimal_unpack() after mp_decode_extl() value
+    ok 77 - decimal_unpack() after mp_decode_extl() len
+    ok 78 - mp_sizeof_decimal(-2.718281828459045)
+    ok 79 - mp_sizeof_decimal(-2.718281828459045) == len(mp_encode_decimal(-2.718281828459045))
+    ok 80 - mp_next(mp_encode(-2.718281828459045))
+    ok 81 - mp_decode(mp_encode(-2.718281828459045) len
+    ok 82 - mp_decode(mp_encode(-2.718281828459045)) value
+    ok 83 - mp_decode(mp_encode(-2.718281828459045)) scale
+    ok 84 - str(mp_decode(mp_encode(-2.718281828459045))) == -2.718281828459045
+    ok 85 - mp_ext_type is MP_DECIMAL
+    ok 86 - decimal_unpack() after mp_decode_extl()
+    ok 87 - decimal_unpack() after mp_decode_extl() value
+    ok 88 - decimal_unpack() after mp_decode_extl() len
+    ok 89 - mp_sizeof_decimal(3.141592653589793)
+    ok 90 - mp_sizeof_decimal(3.141592653589793) == len(mp_encode_decimal(3.141592653589793))
+    ok 91 - mp_next(mp_encode(3.141592653589793))
+    ok 92 - mp_decode(mp_encode(3.141592653589793) len
+    ok 93 - mp_decode(mp_encode(3.141592653589793)) value
+    ok 94 - mp_decode(mp_encode(3.141592653589793)) scale
+    ok 95 - str(mp_decode(mp_encode(3.141592653589793))) == 3.141592653589793
+    ok 96 - mp_ext_type is MP_DECIMAL
+    ok 97 - decimal_unpack() after mp_decode_extl()
+    ok 98 - decimal_unpack() after mp_decode_extl() value
+    ok 99 - decimal_unpack() after mp_decode_extl() len
+    ok 100 - mp_sizeof_decimal(-3.141592653589793)
+    ok 101 - mp_sizeof_decimal(-3.141592653589793) == len(mp_encode_decimal(-3.141592653589793))
+    ok 102 - mp_next(mp_encode(-3.141592653589793))
+    ok 103 - mp_decode(mp_encode(-3.141592653589793) len
+    ok 104 - mp_decode(mp_encode(-3.141592653589793)) value
+    ok 105 - mp_decode(mp_encode(-3.141592653589793)) scale
+    ok 106 - str(mp_decode(mp_encode(-3.141592653589793))) == -3.141592653589793
+    ok 107 - mp_ext_type is MP_DECIMAL
+    ok 108 - decimal_unpack() after mp_decode_extl()
+    ok 109 - decimal_unpack() after mp_decode_extl() value
+    ok 110 - decimal_unpack() after mp_decode_extl() len
+    ok 111 - mp_sizeof_decimal(1234567891234567890.0987654321987654321)
+    ok 112 - mp_sizeof_decimal(1234567891234567890.0987654321987654321) == len(mp_encode_decimal(1234567891234567890.0987654321987654321))
+    ok 113 - mp_next(mp_encode(1234567891234567890.0987654321987654321))
+    ok 114 - mp_decode(mp_encode(1234567891234567890.0987654321987654321) len
+    ok 115 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) value
+    ok 116 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) scale
+    ok 117 - str(mp_decode(mp_encode(1234567891234567890.0987654321987654321))) == 1234567891234567890.0987654321987654321
+    ok 118 - mp_ext_type is MP_DECIMAL
+    ok 119 - decimal_unpack() after mp_decode_extl()
+    ok 120 - decimal_unpack() after mp_decode_extl() value
+    ok 121 - decimal_unpack() after mp_decode_extl() len
+    ok 122 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321)
+    ok 123 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321) == len(mp_encode_decimal(-1234567891234567890.0987654321987654321))
+    ok 124 - mp_next(mp_encode(-1234567891234567890.0987654321987654321))
+    ok 125 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321) len
+    ok 126 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) value
+    ok 127 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) scale
+    ok 128 - str(mp_decode(mp_encode(-1234567891234567890.0987654321987654321))) == -1234567891234567890.0987654321987654321
+    ok 129 - mp_ext_type is MP_DECIMAL
+    ok 130 - decimal_unpack() after mp_decode_extl()
+    ok 131 - decimal_unpack() after mp_decode_extl() value
+    ok 132 - decimal_unpack() after mp_decode_extl() len
+    ok 133 - mp_sizeof_decimal(0.0000000000000000000000000000000000001)
+    ok 134 - mp_sizeof_decimal(0.0000000000000000000000000000000000001) == len(mp_encode_decimal(0.0000000000000000000000000000000000001))
+    ok 135 - mp_next(mp_encode(0.0000000000000000000000000000000000001))
+    ok 136 - mp_decode(mp_encode(0.0000000000000000000000000000000000001) len
+    ok 137 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) value
+    ok 138 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) scale
+    ok 139 - str(mp_decode(mp_encode(0.0000000000000000000000000000000000001))) == 0.0000000000000000000000000000000000001
+    ok 140 - mp_ext_type is MP_DECIMAL
+    ok 141 - decimal_unpack() after mp_decode_extl()
+    ok 142 - decimal_unpack() after mp_decode_extl() value
+    ok 143 - decimal_unpack() after mp_decode_extl() len
+    ok 144 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001)
+    ok 145 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001) == len(mp_encode_decimal(-0.0000000000000000000000000000000000001))
+    ok 146 - mp_next(mp_encode(-0.0000000000000000000000000000000000001))
+    ok 147 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001) len
+    ok 148 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) value
+    ok 149 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) scale
+    ok 150 - str(mp_decode(mp_encode(-0.0000000000000000000000000000000000001))) == -0.0000000000000000000000000000000000001
+    ok 151 - mp_ext_type is MP_DECIMAL
+    ok 152 - decimal_unpack() after mp_decode_extl()
+    ok 153 - decimal_unpack() after mp_decode_extl() value
+    ok 154 - decimal_unpack() after mp_decode_extl() len
+    ok 155 - mp_sizeof_decimal(0.00000000000000000000000000000000000001)
+    ok 156 - mp_sizeof_decimal(0.00000000000000000000000000000000000001) == len(mp_encode_decimal(0.00000000000000000000000000000000000001))
+    ok 157 - mp_next(mp_encode(0.00000000000000000000000000000000000001))
+    ok 158 - mp_decode(mp_encode(0.00000000000000000000000000000000000001) len
+    ok 159 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) value
+    ok 160 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) scale
+    ok 161 - str(mp_decode(mp_encode(0.00000000000000000000000000000000000001))) == 0.00000000000000000000000000000000000001
+    ok 162 - mp_ext_type is MP_DECIMAL
+    ok 163 - decimal_unpack() after mp_decode_extl()
+    ok 164 - decimal_unpack() after mp_decode_extl() value
+    ok 165 - decimal_unpack() after mp_decode_extl() len
+    ok 166 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001)
+    ok 167 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001) == len(mp_encode_decimal(-0.00000000000000000000000000000000000001))
+    ok 168 - mp_next(mp_encode(-0.00000000000000000000000000000000000001))
+    ok 169 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001) len
+    ok 170 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) value
+    ok 171 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) scale
+    ok 172 - str(mp_decode(mp_encode(-0.00000000000000000000000000000000000001))) == -0.00000000000000000000000000000000000001
+    ok 173 - mp_ext_type is MP_DECIMAL
+    ok 174 - decimal_unpack() after mp_decode_extl()
+    ok 175 - decimal_unpack() after mp_decode_extl() value
+    ok 176 - decimal_unpack() after mp_decode_extl() len
+    ok 177 - mp_sizeof_decimal(99999999999999999999999999999999999999)
+    ok 178 - mp_sizeof_decimal(99999999999999999999999999999999999999) == len(mp_encode_decimal(99999999999999999999999999999999999999))
+    ok 179 - mp_next(mp_encode(99999999999999999999999999999999999999))
+    ok 180 - mp_decode(mp_encode(99999999999999999999999999999999999999) len
+    ok 181 - mp_decode(mp_encode(99999999999999999999999999999999999999)) value
+    ok 182 - mp_decode(mp_encode(99999999999999999999999999999999999999)) scale
+    ok 183 - str(mp_decode(mp_encode(99999999999999999999999999999999999999))) == 99999999999999999999999999999999999999
+    ok 184 - mp_ext_type is MP_DECIMAL
+    ok 185 - decimal_unpack() after mp_decode_extl()
+    ok 186 - decimal_unpack() after mp_decode_extl() value
+    ok 187 - decimal_unpack() after mp_decode_extl() len
+    ok 188 - mp_sizeof_decimal(-99999999999999999999999999999999999999)
+    ok 189 - mp_sizeof_decimal(-99999999999999999999999999999999999999) == len(mp_encode_decimal(-99999999999999999999999999999999999999))
+    ok 190 - mp_next(mp_encode(-99999999999999999999999999999999999999))
+    ok 191 - mp_decode(mp_encode(-99999999999999999999999999999999999999) len
+    ok 192 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) value
+    ok 193 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) scale
+    ok 194 - str(mp_decode(mp_encode(-99999999999999999999999999999999999999))) == -99999999999999999999999999999999999999
+    ok 195 - mp_ext_type is MP_DECIMAL
+    ok 196 - decimal_unpack() after mp_decode_extl()
+    ok 197 - decimal_unpack() after mp_decode_extl() value
+    ok 198 - decimal_unpack() after mp_decode_extl() len
+ok 280 - subtests
diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c
index 631c53e3f..f9f43f765 100644
--- a/third_party/lua-cjson/lua_cjson.c
+++ b/third_party/lua-cjson/lua_cjson.c
@@ -411,9 +411,15 @@ static void json_append_data(lua_State *l, struct luaL_serializer *cfg,
     json_append_array(l, cfg, current_depth, json, field.size);
     return;
     case MP_EXT:
-    /* handled by luaL_convertfield */
-    assert(false);
-    return;
+	switch (field.ext_type) {
+	case MP_DECIMAL:
+	{
+	    const char *str = decimal_to_string(field.decval);
+	    return json_append_string(cfg, json, str, strlen(str));
+	}
+	default:
+	    assert(false);
+	}
     }
 }
 
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 46c98bde1..7485341fa 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -49,6 +49,7 @@ extern "C" {
 #include "b64.h"
 } /* extern "C" */
 #include "lua/utils.h"
+#include "lib/core/decimal.h"
 
 #define LUAYAML_TAG_PREFIX "tag:yaml.org,2002:"
 
@@ -693,7 +694,14 @@ static int dump_node(struct lua_yaml_dumper *dumper)
       len = 4;
       break;
    case MP_EXT:
-      assert(0); /* checked by luaL_checkfield() */
+      switch (field.ext_type) {
+      case MP_DECIMAL:
+         str = decimal_to_string(field.decval);
+	 len = strlen(str);
+	 break;
+      default:
+	 assert(0); /* checked by luaL_checkfield() */
+      }
       break;
     }
 
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 5/6] decimal: add conversions to (u)int64_t
  2019-08-20 17:09 [PATCH v3 0/6] Decimal indices Serge Petrenko
                   ` (3 preceding siblings ...)
  2019-08-20 17:10 ` [PATCH v3 4/6] decimal: allow to encode/decode decimals as MsgPack Serge Petrenko
@ 2019-08-20 17:10 ` Serge Petrenko
  2019-08-21 15:02   ` Vladimir Davydov
  2019-08-20 17:10 ` [PATCH v3 6/6] decimal: allow to index decimals Serge Petrenko
  5 siblings, 1 reply; 14+ messages in thread
From: Serge Petrenko @ 2019-08-20 17:10 UTC (permalink / raw)
  To: vdavydov.dev, kostja; +Cc: tarantool-patches, Serge Petrenko

Update decNumber library, add methods to convert decimals to uint64_t
and int64_t, add unit tests.
Also replace decimal_round() function with decimal_round_with_mode() to
allow setting rounding mode.
We need to round with mode DEC_ROUND_DOWN in to_int64 conversions in
order to be consistent with double to int conversions.
It will be needed to compute hints for decimal fields.

Prerequisite #4333
---
 src/lib/core/decimal.c   | 57 +++++++++++++++++++++++++++++--
 src/lib/core/decimal.h   | 19 +++++++++++
 test/unit/decimal.c      | 66 ++++++++++++++++++++++++++++++++++-
 test/unit/decimal.result | 74 ++++++++++++++++++++++++++++++++++++++--
 third_party/decNumber    |  2 +-
 5 files changed, 210 insertions(+), 8 deletions(-)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 356acf6f2..c44b2f6a2 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -157,6 +157,45 @@ decimal_to_string(const decimal_t *dec)
 	return buf;
 }
 
+static decimal_t *
+decimal_to_integer(decimal_t *dec)
+{
+	decimal_t z;
+	decNumberZero(&z);
+	if (decimal_scale(dec) != 0) {
+		/*
+		 * Rounding mode is important here.
+		 * We want to be consistent with double
+		 * to int conversion so that comparison
+		 * hints work correctly.
+		 */
+		decimal_floor(dec, 0);
+	}
+	/* Zero the number exponent for decNumberToInt64. */
+	decNumberRescale(dec, dec, &z, &decimal_context);
+	return decimal_check_status(dec, &decimal_context);
+}
+
+decimal_t *
+decimal_to_int64(const decimal_t *dec, int64_t *num)
+{
+	decimal_t d = *dec;
+	if (decimal_to_integer(&d) == NULL)
+		return NULL;
+	*num = decNumberToInt64(&d, &decimal_context);
+	return decimal_check_status(&d, &decimal_context);
+}
+
+decimal_t *
+decimal_to_uint64(const decimal_t *dec, uint64_t *num)
+{
+	decimal_t d = *dec;
+	if (decimal_to_integer(&d) == NULL)
+		return NULL;
+	*num = decNumberToUInt64(&d, &decimal_context);
+	return decimal_check_status(&d, &decimal_context);
+}
+
 int
 decimal_compare(const decimal_t *lhs, const decimal_t *rhs)
 {
@@ -167,8 +206,8 @@ decimal_compare(const decimal_t *lhs, const decimal_t *rhs)
 	return r;
 }
 
-decimal_t *
-decimal_round(decimal_t *dec, int scale)
+static decimal_t *
+decimal_round_with_mode(decimal_t *dec, int scale, enum rounding mode)
 {
 	if (scale < 0 || scale > DECIMAL_MAX_DIGITS)
 		return NULL;
@@ -181,7 +220,7 @@ decimal_round(decimal_t *dec, int scale)
 		ndig, /* Precision */
 		ndig, /* emax */
 		scale != 0 ? -1 : 0, /* emin */
-		DECIMAL_ROUNDING, /* rounding */
+		mode, /* rounding */
 		0, /* no traps */
 		0, /* zero status */
 		0 /* no clamping */
@@ -192,6 +231,18 @@ decimal_round(decimal_t *dec, int scale)
 	return dec;
 }
 
+inline decimal_t *
+decimal_round(decimal_t *dec, int scale)
+{
+	return decimal_round_with_mode(dec, scale, DECIMAL_ROUNDING);
+}
+
+inline decimal_t *
+decimal_floor(decimal_t *dec, int scale)
+{
+	return decimal_round_with_mode(dec, scale, DEC_ROUND_DOWN);
+}
+
 decimal_t *
 decimal_trim(decimal_t *dec)
 {
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
index ffe49b652..77b6dbfd0 100644
--- a/src/lib/core/decimal.h
+++ b/src/lib/core/decimal.h
@@ -101,6 +101,18 @@ decimal_from_uint64(decimal_t *dec, uint64_t num);
 const char *
 decimal_to_string(const decimal_t *dec);
 
+/**
+ * Convert a given decimal to int64_t
+ * \param[out] num - the result
+ * @return NULL if \a dec doesn't fit into int64_t
+ */
+decimal_t *
+decimal_to_int64(const decimal_t *dec, int64_t *num);
+
+/** \sa decimal_to_int64 */
+decimal_t *
+decimal_to_uint64(const decimal_t *dec, uint64_t *num);
+
 /**
  * Compare 2 decimal values.
  * @return -1, lhs < rhs,
@@ -122,6 +134,13 @@ decimal_compare(const decimal_t *lhs, const decimal_t *rhs);
 decimal_t *
 decimal_round(decimal_t *dec, int scale);
 
+/**
+ * Round a decimal towards zero.
+ * \sa decimal_round
+ */
+decimal_t *
+decimal_floor(decimal_t *dec, int scale);
+
 /**
  * Remove trailing zeros from the fractional part of a number.
  * @return \a dec with trimmed fractional zeros.
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index 625913efe..254114b5f 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -5,6 +5,7 @@
 #include "msgpuck.h"
 #include <limits.h>
 #include <string.h>
+#include <inttypes.h>
 #include <float.h> /* DBL_DIG */
 
 #define success(x) x
@@ -117,6 +118,15 @@ char buf[32];
 	is(strcmp(decimal_to_string(&d2), str), 0, "str(decimal_unpack(decimal_pack("str")) == "str);\
 })
 
+#define test_toint(type, num, out_fmt) ({\
+	decimal_t dec;\
+	type##_t val;\
+	decimal_from_##type(&dec, num);\
+	isnt(decimal_to_##type(&dec, &val), NULL, "Conversion of %"out_fmt\
+						  " to decimal and back to "#type" successful", (type##_t) num);\
+	is(val, num, "Conversion back to "#type" correct");\
+})
+
 static int
 test_pack_unpack(void)
 {
@@ -192,10 +202,62 @@ test_mp_decimal(void)
 	return check_plan();
 }
 
+static int
+test_to_int(void)
+{
+	plan(66);
+
+	test_toint(uint64, ULLONG_MAX, PRIu64);
+	test_toint(int64, LLONG_MAX, PRId64);
+	test_toint(int64, LLONG_MIN, PRId64);
+	test_toint(uint64, 0, PRIu64);
+	test_toint(int64, 0, PRId64);
+	test_toint(int64, -1, PRId64);
+
+	/* test some arbitrary values. */
+	test_toint(uint64, ULLONG_MAX / 157, PRIu64);
+	test_toint(int64, LLONG_MAX / 157, PRId64);
+	test_toint(int64, LLONG_MIN / 157, PRId64);
+
+	test_toint(uint64, ULLONG_MAX / 157 / 151, PRIu64);
+	test_toint(int64, LLONG_MAX / 157 / 151, PRId64);
+	test_toint(int64, LLONG_MIN / 157 / 151, PRId64);
+
+	test_toint(uint64, ULLONG_MAX / 157 / 151 / 149, PRIu64);
+	test_toint(int64, LLONG_MAX / 157 / 151 / 149, PRId64);
+	test_toint(int64, LLONG_MIN / 157 / 151 / 149, PRId64);
+
+	test_toint(uint64, ULLONG_MAX / 157 / 151 / 149 / 139, PRIu64);
+	test_toint(int64, LLONG_MAX / 157 / 151 / 149 / 139, PRId64);
+	test_toint(int64, LLONG_MIN / 157 / 151 / 149 / 139, PRId64);
+
+	test_toint(uint64, ULLONG_MAX / 157 / 151 / 149 / 139 / 137, PRIu64);
+	test_toint(int64, LLONG_MAX / 156 / 151 / 149 / 139 / 137, PRId64);
+	test_toint(int64, LLONG_MIN / 156 / 151 / 149 / 139 / 137, PRId64);
+
+	test_toint(uint64, UINT_MAX, PRIu64);
+	test_toint(int64, INT_MAX, PRId64);
+	test_toint(int64, INT_MIN, PRId64);
+
+	test_toint(uint64, UINT_MAX / 157, PRIu64); /* ~ 27356479 */
+	test_toint(int64, INT_MAX / 157, PRId64);
+	test_toint(int64, INT_MIN / 157, PRId64);
+
+	test_toint(uint64, UINT_MAX / 157 / 151, PRIu64); /* ~ 181168 */
+	test_toint(int64, INT_MAX / 157 / 151, PRId64);
+	test_toint(int64, INT_MIN / 157 / 151, PRId64);
+
+	test_toint(uint64, UINT_MAX / 157 / 151 / 149, PRIu64); /* ~ 1215 */
+	test_toint(int64, INT_MAX / 157 / 151 / 149, PRId64);
+	test_toint(int64, INT_MIN / 157 / 151 / 149, PRId64);
+
+	return check_plan();
+}
+
 int
 main(void)
 {
-	plan(280);
+	plan(281);
 
 	dectest(314, 271, uint64, uint64_t);
 	dectest(65535, 23456, uint64, uint64_t);
@@ -255,6 +317,8 @@ main(void)
 	dectest_op1_fail(log10, -1);
 	dectest_op1_fail(sqrt, -10);
 
+	test_to_int();
+
 	test_pack_unpack();
 
 	test_mp_decimal();
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index e6812a8dd..e8432fb36 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -1,4 +1,4 @@
-1..280
+1..281
 ok 1 - decimal(314)
 ok 2 - decimal(271)
 ok 3 - decimal(314) + decimal(271)
@@ -277,6 +277,74 @@ ok 275 - decimal_from_string(-1)
 ok 276 - decimal_log10(-1) - error on wrong operands.
 ok 277 - decimal_from_string(-10)
 ok 278 - decimal_sqrt(-10) - error on wrong operands.
+    1..66
+    ok 1 - Conversion of 18446744073709551615 to decimal and back to uint64 successful
+    ok 2 - Conversion back to uint64 correct
+    ok 3 - Conversion of 9223372036854775807 to decimal and back to int64 successful
+    ok 4 - Conversion back to int64 correct
+    ok 5 - Conversion of -9223372036854775808 to decimal and back to int64 successful
+    ok 6 - Conversion back to int64 correct
+    ok 7 - Conversion of 0 to decimal and back to uint64 successful
+    ok 8 - Conversion back to uint64 correct
+    ok 9 - Conversion of 0 to decimal and back to int64 successful
+    ok 10 - Conversion back to int64 correct
+    ok 11 - Conversion of -1 to decimal and back to int64 successful
+    ok 12 - Conversion back to int64 correct
+    ok 13 - Conversion of 117495185182863386 to decimal and back to uint64 successful
+    ok 14 - Conversion back to uint64 correct
+    ok 15 - Conversion of 58747592591431693 to decimal and back to int64 successful
+    ok 16 - Conversion back to int64 correct
+    ok 17 - Conversion of -58747592591431693 to decimal and back to int64 successful
+    ok 18 - Conversion back to int64 correct
+    ok 19 - Conversion of 778113809158035 to decimal and back to uint64 successful
+    ok 20 - Conversion back to uint64 correct
+    ok 21 - Conversion of 389056904579017 to decimal and back to int64 successful
+    ok 22 - Conversion back to int64 correct
+    ok 23 - Conversion of -389056904579017 to decimal and back to int64 successful
+    ok 24 - Conversion back to int64 correct
+    ok 25 - Conversion of 5222240329919 to decimal and back to uint64 successful
+    ok 26 - Conversion back to uint64 correct
+    ok 27 - Conversion of 2611120164959 to decimal and back to int64 successful
+    ok 28 - Conversion back to int64 correct
+    ok 29 - Conversion of -2611120164959 to decimal and back to int64 successful
+    ok 30 - Conversion back to int64 correct
+    ok 31 - Conversion of 37570074315 to decimal and back to uint64 successful
+    ok 32 - Conversion back to uint64 correct
+    ok 33 - Conversion of 18785037157 to decimal and back to int64 successful
+    ok 34 - Conversion back to int64 correct
+    ok 35 - Conversion of -18785037157 to decimal and back to int64 successful
+    ok 36 - Conversion back to int64 correct
+    ok 37 - Conversion of 274234119 to decimal and back to uint64 successful
+    ok 38 - Conversion back to uint64 correct
+    ok 39 - Conversion of 137996015 to decimal and back to int64 successful
+    ok 40 - Conversion back to int64 correct
+    ok 41 - Conversion of -137996015 to decimal and back to int64 successful
+    ok 42 - Conversion back to int64 correct
+    ok 43 - Conversion of 4294967295 to decimal and back to uint64 successful
+    ok 44 - Conversion back to uint64 correct
+    ok 45 - Conversion of 2147483647 to decimal and back to int64 successful
+    ok 46 - Conversion back to int64 correct
+    ok 47 - Conversion of -2147483648 to decimal and back to int64 successful
+    ok 48 - Conversion back to int64 correct
+    ok 49 - Conversion of 27356479 to decimal and back to uint64 successful
+    ok 50 - Conversion back to uint64 correct
+    ok 51 - Conversion of 13678239 to decimal and back to int64 successful
+    ok 52 - Conversion back to int64 correct
+    ok 53 - Conversion of -13678239 to decimal and back to int64 successful
+    ok 54 - Conversion back to int64 correct
+    ok 55 - Conversion of 181168 to decimal and back to uint64 successful
+    ok 56 - Conversion back to uint64 correct
+    ok 57 - Conversion of 90584 to decimal and back to int64 successful
+    ok 58 - Conversion back to int64 correct
+    ok 59 - Conversion of -90584 to decimal and back to int64 successful
+    ok 60 - Conversion back to int64 correct
+    ok 61 - Conversion of 1215 to decimal and back to uint64 successful
+    ok 62 - Conversion back to uint64 correct
+    ok 63 - Conversion of 607 to decimal and back to int64 successful
+    ok 64 - Conversion back to int64 correct
+    ok 65 - Conversion of -607 to decimal and back to int64 successful
+    ok 66 - Conversion back to int64 correct
+ok 279 - subtests
     1..151
     ok 1 - decimal_len(0)
     ok 2 - decimal_len(0) == len(decimal_pack(0)
@@ -429,7 +497,7 @@ ok 278 - decimal_sqrt(-10) - error on wrong operands.
     ok 149 - positive exponent number is packed/unpacked correctly
     ok 150 - unpack malformed decimal fails
     ok 151 - decode malformed decimal preserves buffer position
-ok 279 - subtests
+ok 280 - subtests
     1..198
     ok 1 - mp_sizeof_decimal(0)
     ok 2 - mp_sizeof_decimal(0) == len(mp_encode_decimal(0))
@@ -629,4 +697,4 @@ ok 279 - subtests
     ok 196 - decimal_unpack() after mp_decode_extl()
     ok 197 - decimal_unpack() after mp_decode_extl() value
     ok 198 - decimal_unpack() after mp_decode_extl() len
-ok 280 - subtests
+ok 281 - subtests
diff --git a/third_party/decNumber b/third_party/decNumber
index ee540fca6..878ed752f 160000
--- a/third_party/decNumber
+++ b/third_party/decNumber
@@ -1 +1 @@
-Subproject commit ee540fca6a44b3a2df7258dc7a1ec612cbf84dce
+Subproject commit 878ed752f2453cd5e73587e67f7782aec9181a22
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v3 6/6] decimal: allow to index decimals
  2019-08-20 17:09 [PATCH v3 0/6] Decimal indices Serge Petrenko
                   ` (4 preceding siblings ...)
  2019-08-20 17:10 ` [PATCH v3 5/6] decimal: add conversions to (u)int64_t Serge Petrenko
@ 2019-08-20 17:10 ` Serge Petrenko
  2019-08-21 15:02   ` Vladimir Davydov
  2019-08-22 10:33   ` [tarantool-patches] " Kirill Yukhin
  5 siblings, 2 replies; 14+ messages in thread
From: Serge Petrenko @ 2019-08-20 17:10 UTC (permalink / raw)
  To: vdavydov.dev, kostja; +Cc: tarantool-patches, Serge Petrenko

Closes #4333

@TarantoolBot document
Title: Document decimal field type.

Decimals may now be stored in spaces. A corresponding field type is
introduced: 'decimal'. Decimal values are also allowed in 'scalar',
'any' and 'number' fields.

'decimal' field type is appropriate for both memtx HASH and TREE
indices, as well as for vinyl TREE index.

```
To create an index 'pk' over a decimal field, say
```
tarantool> box.space.test:create_index('pk', {parts={1, 'decimal'}})
---
- unique: true
  parts:
  - type: decimal
    is_nullable: false
    fieldno: 1
  id: 0
  space_id: 512
  type: TREE
  name: pk
...
```
Now you can insert some decimal values:
```
tarantool> for i = 1,10 do
         > box.space.test:insert{decimal.new((i-5)/10)}
         > end
---
...
```
tarantool> box.space.test:select{}
---
- - [-0.4]
  - [-0.3]
  - [-0.2]
  - [-0.1]
  - [0]
  - [0.1]
  - [0.2]
  - [0.3]
  - [0.4]
  - [0.5]
...
```
Decimals may alse be inserted into `scalar` and `number` fields. In this
case all the number values are sorted correctly:
```
tarantool> box.schema.space.create('test')
tarantool> box.space.test:create_index('pk', {parts={1, 'number'}})
tarantool> box.space.test:insert{-1.0001, 'number'}
---
- [-1.0001, 'number']
...

tarantool> box.space.test:insert{decimal.new(-1.00001), 'decimal'}
---
- [-1.00001, 'decimal']
...

tarantool> box.space.test:insert{-1, 'number'}
---
- [-1, 'number']
...

tarantool> box.space.test:insert{decimal.new(-0.999), 'decimal'}
---
- [-0.999, 'decimal']
...

tarantool> box.space.test:insert{-0.998, 'number'}
---
- [-0.998, 'number']
...

tarantool> box.space.test:insert{-0.9, 'number'}
---
- [-0.9, 'number']
...

tarantool> box.space.test:insert{-0.95, 'number'}
---
- [-0.95, 'number']
...

tarantool> box.space.test:insert{decimal.new(-0.92), 'decimal'}
---
- [-0.92, 'decimal']
...

tarantool> box.space.test:insert{decimal.new(-0.971), 'decimal'}
---
- [-0.971, 'decimal']
...

tarantool> box.space.test:select{}
---
- - [-1.0001, 'number']
  - [-1.00001, 'decimal']
  - [-1, 'number']
  - [-0.999, 'decimal']
  - [-0.998, 'number']
  - [-0.971, 'decimal']
  - [-0.95, 'number']
  - [-0.92, 'decimal']
  - [-0.9, 'number']
...

```
Uniqueness is also preserved between decimals and other number types:
```
tarantool> box.space.test:insert{-0.92}
---
- error: Duplicate key exists in unique index 'pk' in space 'test'
...

tarantool> box.space.test:insert{decimal.new(-0.9)}
---
- error: Duplicate key exists in unique index 'pk' in space 'test'
...

```

You can also set decimal fields in space format:
```
tarantool> _ = box.schema.space.create('test')
---
...

tarantool> _ = box.space.test:create_index('pk')
---
...

tarantool> box.space.test:format{{name='id', type='unsigned'}, {name='balance', type='decimal'}}
---
...

tarantool> box.space.test:insert{1}
---
- error: Tuple field 2 required by space format is missing
...

tarantool> box.space.test:insert{1, 'string'}
---
- error: 'Tuple field 2 type does not match one required by operation: expected decimal'
...

tarantool> box.space.test:insert{1, 1.2345}
---
- error: 'Tuple field 2 type does not match one required by operation: expected decimal'
...

tarantool> box.space.test:insert{1, decimal.new('1337.420')}
---
- [1, 1337.420]
...

```
---
 src/box/field_def.c           |  45 ++-
 src/box/field_def.h           |  23 +-
 src/box/key_def.h             |   3 +-
 src/box/tuple_compare.cc      | 198 ++++++++-
 src/box/tuple_format.c        |   3 +-
 src/lib/core/decimal.h        |   8 +
 src/lib/core/mp_decimal.h     |   8 +
 test/engine/ddl.result        |  85 +++-
 test/engine/ddl.test.lua      |  40 +-
 test/engine/decimal.result    | 275 +++++++++++++
 test/engine/decimal.test.lua  |  83 ++++
 test/engine/iterator.result   | 730 ++++++++++++++++++++++++++++++++++
 test/engine/iterator.test.lua |  22 +
 13 files changed, 1496 insertions(+), 27 deletions(-)

diff --git a/src/box/field_def.c b/src/box/field_def.c
index 346042b98..da766d586 100644
--- a/src/box/field_def.c
+++ b/src/box/field_def.c
@@ -32,6 +32,7 @@
 #include "field_def.h"
 #include "trivia/util.h"
 #include "key_def.h"
+#include "mp_extension_types.h"
 
 const char *mp_type_strs[] = {
 	/* .MP_NIL    = */ "nil",
@@ -47,6 +48,11 @@ const char *mp_type_strs[] = {
 	/* .MP_EXT    = */ "extension",
 };
 
+/*
+ * messagepack types supported by given field types.
+ * MP_EXT requires to parse extension type to check
+ * compatibility with field type.
+ */
 const uint32_t field_mp_type[] = {
 	/* [FIELD_TYPE_ANY]      =  */ UINT32_MAX,
 	/* [FIELD_TYPE_UNSIGNED] =  */ 1U << MP_UINT,
@@ -59,10 +65,25 @@ const uint32_t field_mp_type[] = {
 	/* [FIELD_TYPE_SCALAR]   =  */ (1U << MP_UINT) | (1U << MP_INT) |
 		(1U << MP_FLOAT) | (1U << MP_DOUBLE) | (1U << MP_STR) |
 		(1U << MP_BIN) | (1U << MP_BOOL),
+	/* [FIELD_TYPE_DECIMAL]  =  */ 0, /* only MP_DECIMAL is supported */
 	/* [FIELD_TYPE_ARRAY]    =  */ 1U << MP_ARRAY,
 	/* [FIELD_TYPE_MAP]      =  */ (1U << MP_MAP),
 };
 
+const uint32_t field_ext_type[] = {
+	/* [FIELD_TYPE_ANY]       = */ UINT32_MAX ^ (1U << MP_UNKNOWN_EXTENSION),
+	/* [FIELD_TYPE_UNSIGNED]  = */ 0,
+	/* [FIELD_TYPE_STRING]    = */ 0,
+	/* [FIELD_TYPE_NUMBER]    = */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_INTEGER]   = */ 0,
+	/* [FIELD_TYPE_BOOLEAN]   = */ 0,
+	/* [FIELD_TYPE_VARBINARY] = */ 0,
+	/* [FIELD_TYPE_SCALAR]    = */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_DECIMAL]   = */ 1U << MP_DECIMAL,
+	/* [FIELD_TYPE_ARRAY]     = */ 0,
+	/* [FIELD_TYPE_MAP]       = */ 0,
+};
+
 const char *field_type_strs[] = {
 	/* [FIELD_TYPE_ANY]      = */ "any",
 	/* [FIELD_TYPE_UNSIGNED] = */ "unsigned",
@@ -72,6 +93,7 @@ const char *field_type_strs[] = {
 	/* [FIELD_TYPE_BOOLEAN]  = */ "boolean",
 	/* [FIELD_TYPE_VARBINARY] = */"varbinary",
 	/* [FIELD_TYPE_SCALAR]   = */ "scalar",
+	/* [FIELD_TYPE_DECIMAL]  = */ "decimal",
 	/* [FIELD_TYPE_ARRAY]    = */ "array",
 	/* [FIELD_TYPE_MAP]      = */ "map",
 };
@@ -98,17 +120,18 @@ field_type_by_name_wrapper(const char *str, uint32_t len)
  * values can be stored in the j type.
  */
 static const bool field_type_compatibility[] = {
-	   /*   ANY   UNSIGNED  STRING   NUMBER  INTEGER  BOOLEAN VARBINARY SCALAR   ARRAY     MAP */
-/*   ANY    */ true,   false,   false,   false,   false,   false,   false,  false,   false,   false,
-/* UNSIGNED */ true,   true,    false,   true,    true,    false,   false,  true,    false,   false,
-/*  STRING  */ true,   false,   true,    false,   false,   false,   false,  true,    false,   false,
-/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,  true,    false,   false,
-/*  INTEGER */ true,   false,   false,   true,    true,    false,   false,  true,    false,   false,
-/*  BOOLEAN */ true,   false,   false,   false,   false,   true,    false,  true,    false,   false,
-/* VARBINARY*/ true,   false,   false,   false,   false,   false,   true,   true,    false,   false,
-/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,  true,    false,   false,
-/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,  false,   true,    false,
-/*    MAP   */ true,   false,   false,   false,   false,   false,   false,  false,   false,   true,
+	   /*   ANY   UNSIGNED  STRING   NUMBER  INTEGER  BOOLEAN VARBINARY SCALAR  DECIMAL  ARRAY    MAP  */
+/*   ANY    */ true,   false,   false,   false,   false,   false,   false,  false,  false,  false,   false,
+/* UNSIGNED */ true,   true,    false,   true,    true,    false,   false,  true,   false,  false,   false,
+/*  STRING  */ true,   false,   true,    false,   false,   false,   false,  true,   false,  false,   false,
+/*  NUMBER  */ true,   false,   false,   true,    false,   false,   false,  true,   false,  false,   false,
+/*  INTEGER */ true,   false,   false,   true,    true,    false,   false,  true,   false,  false,   false,
+/*  BOOLEAN */ true,   false,   false,   false,   false,   true,    false,  true,   false,  false,   false,
+/* VARBINARY*/ true,   false,   false,   false,   false,   false,   true,   true,   false,  false,   false,
+/*  SCALAR  */ true,   false,   false,   false,   false,   false,   false,  true,   false,  false,   false,
+/*  DECIMAL */ true,   false,   false,   true,    false,   false,   false,  true,   true,   false,   false,
+/*   ARRAY  */ true,   false,   false,   false,   false,   false,   false,  false,  false,  true,    false,
+/*    MAP   */ true,   false,   false,   false,   false,   false,   false,  false,  false,  false,   true,
 };
 
 bool
diff --git a/src/box/field_def.h b/src/box/field_def.h
index c1a7ec0a9..fdedc9622 100644
--- a/src/box/field_def.h
+++ b/src/box/field_def.h
@@ -58,6 +58,7 @@ enum field_type {
 	FIELD_TYPE_BOOLEAN,
 	FIELD_TYPE_VARBINARY,
 	FIELD_TYPE_SCALAR,
+	FIELD_TYPE_DECIMAL,
 	FIELD_TYPE_ARRAY,
 	FIELD_TYPE_MAP,
 	field_type_MAX
@@ -109,8 +110,9 @@ field_type_by_name(const char *name, size_t len);
 /* MsgPack type names */
 extern const char *mp_type_strs[];
 
-/** A helper table for field_mp_type_is_compatible */
+/** Two helper tables for field_mp_type_is_compatible */
 extern const uint32_t field_mp_type[];
+extern const uint32_t field_ext_type[];
 
 extern const struct opt_def field_def_reg[];
 extern const struct field_def field_def_default;
@@ -144,13 +146,26 @@ struct field_def {
 
 /** Checks if mp_type (MsgPack) is compatible with field type. */
 static inline bool
-field_mp_type_is_compatible(enum field_type type, enum mp_type mp_type,
+field_mp_type_is_compatible(enum field_type type, const char *data,
 			    bool is_nullable)
 {
 	assert(type < field_type_MAX);
+	enum mp_type mp_type = mp_typeof(*data);
 	assert((size_t)mp_type < CHAR_BIT * sizeof(*field_mp_type));
-	uint32_t mask = field_mp_type[type] | (is_nullable * (1U << MP_NIL));
-	return (mask & (1U << mp_type)) != 0;
+	uint32_t mask;
+	if (mp_type != MP_EXT) {
+		mask = field_mp_type[type] | (is_nullable * (1U << MP_NIL));
+		return (mask & (1U << mp_type)) != 0;
+	} else {
+		int8_t ext_type;
+		mp_decode_extl(&data, &ext_type);
+		if (ext_type >= 0) {
+			mask = field_ext_type[type];
+			return (mask & (1U << ext_type)) != 0;
+		} else {
+			return false;
+		}
+	}
 }
 
 static inline bool
diff --git a/src/box/key_def.h b/src/box/key_def.h
index c6e33e4ad..f4d9e76f2 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -528,8 +528,7 @@ static inline int
 key_part_validate(enum field_type key_type, const char *key,
 		  uint32_t field_no, bool is_nullable)
 {
-	if (unlikely(!field_mp_type_is_compatible(key_type, mp_typeof(*key),
-						  is_nullable))) {
+	if (unlikely(!field_mp_type_is_compatible(key_type, key, is_nullable))) {
 		diag_set(ClientError, ER_KEY_PART_TYPE, field_no,
 			 field_type_strs[key_type]);
 		return -1;
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index b7b54e21a..e621f3c3c 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -33,6 +33,9 @@
 #include "coll/coll.h"
 #include "trivia/util.h" /* NOINLINE */
 #include <math.h>
+#include "lib/core/decimal.h"
+#include "lib/core/mp_decimal.h"
+#include "lib/core/mp_extension_types.h"
 
 /* {{{ tuple_compare */
 
@@ -87,7 +90,12 @@ static enum mp_class mp_classes[] = {
 	/* .MP_BOOL    = */ MP_CLASS_BOOL,
 	/* .MP_FLOAT   = */ MP_CLASS_NUMBER,
 	/* .MP_DOUBLE  = */ MP_CLASS_NUMBER,
-	/* .MP_BIN     = */ MP_CLASS_BIN
+	/* .MP_EXT     = */ mp_class_max,
+};
+
+static enum mp_class mp_ext_classes[] = {
+	/* .MP_UNKNOWN_EXTENSION = */ mp_class_max, /* unsupported */
+	/* .MP_DECIMAL		 = */ MP_CLASS_NUMBER,
 };
 
 #define COMPARE_RESULT(a, b) (a < b ? -1 : a > b)
@@ -98,6 +106,15 @@ mp_classof(enum mp_type type)
 	return mp_classes[type];
 }
 
+static enum mp_class
+mp_extension_class(const char *data)
+{
+	assert(mp_typeof(*data) == MP_EXT);
+	int8_t type;
+	mp_decode_extl(&data, &type);
+	return mp_ext_classes[type];
+}
+
 static int
 mp_compare_bool(const char *field_a, const char *field_b)
 {
@@ -264,13 +281,107 @@ mp_compare_double_any_number(double lhs, const char *rhs,
 	return k * COMPARE_RESULT(lqbit, rqbit);
 }
 
+static int
+mp_compare_decimal(const char *lhs, const char *rhs)
+{
+	decimal_t lhs_dec, rhs_dec;
+	decimal_t *ret;
+	ret = mp_decode_decimal(&lhs, &lhs_dec);
+	assert(ret != NULL);
+	ret = mp_decode_decimal(&rhs, &rhs_dec);
+	assert(ret != NULL);
+	(void)ret;
+	return decimal_compare(&lhs_dec, &rhs_dec);
+
+}
+
+static int
+mp_compare_decimal_any_number(decimal_t *lhs, const char *rhs,
+			      enum mp_type rhs_type, int k)
+{
+	decimal_t rhs_dec;
+	switch (rhs_type) {
+	case MP_FLOAT:
+	{
+		double d = mp_decode_float(&rhs);
+		decimal_from_double(&rhs_dec, d);
+		break;
+	}
+	case MP_DOUBLE:
+	{
+		double d = mp_decode_double(&rhs);
+		decimal_from_double(&rhs_dec, d);
+		break;
+	}
+	case MP_INT:
+	{
+		int64_t num = mp_decode_int(&rhs);
+		decimal_from_int64(&rhs_dec, num);
+		break;
+	}
+	case MP_UINT:
+	{
+		uint64_t num = mp_decode_uint(&rhs);
+		decimal_from_uint64(&rhs_dec, num);
+		break;
+	}
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&rhs, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+			decimal_unpack(&rhs, len, &rhs_dec);
+			break;
+		default:
+			unreachable();
+		}
+		break;
+	}
+	default:
+		unreachable();
+	}
+	return k * decimal_compare(lhs, &rhs_dec);
+}
+
 static int
 mp_compare_number_with_type(const char *lhs, enum mp_type lhs_type,
 			    const char *rhs, enum mp_type rhs_type)
 {
-	assert(mp_classof(lhs_type) == MP_CLASS_NUMBER);
-	assert(mp_classof(rhs_type) == MP_CLASS_NUMBER);
+	assert(mp_classof(lhs_type) == MP_CLASS_NUMBER ||
+	       mp_extension_class(lhs) == MP_CLASS_NUMBER);
+	assert(mp_classof(rhs_type) == MP_CLASS_NUMBER ||
+	       mp_extension_class(rhs) == MP_CLASS_NUMBER);
 
+	/*
+	 * Test decimals first, so that we don't have to
+	 * account for them in other comparators.
+	 */
+	decimal_t dec;
+	if (rhs_type == MP_EXT) {
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&rhs, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+			return mp_compare_decimal_any_number(
+				decimal_unpack(&rhs, len, &dec), lhs, lhs_type, -1
+			);
+		default:
+			unreachable();
+		}
+	}
+	if (lhs_type == MP_EXT) {
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&lhs, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+			return mp_compare_decimal_any_number(
+				decimal_unpack(&lhs, len, &dec), rhs, rhs_type, 1
+			);
+		default:
+			unreachable();
+		}
+	}
 	if (rhs_type == MP_FLOAT) {
 		return mp_compare_double_any_number(
 			mp_decode_float(&rhs), lhs, lhs_type, -1
@@ -348,8 +459,12 @@ static int
 mp_compare_scalar_with_type(const char *field_a, enum mp_type a_type,
 			    const char *field_b, enum mp_type b_type)
 {
-	enum mp_class a_class = mp_classof(a_type);
-	enum mp_class b_class = mp_classof(b_type);
+	enum mp_class a_class = mp_classof(a_type) < mp_class_max ?
+						      mp_classof(a_type) :
+						      mp_extension_class(field_a);
+	enum mp_class b_class = mp_classof(b_type) < mp_class_max ?
+						      mp_classof(b_type) :
+						      mp_extension_class(field_b);
 	if (a_class != b_class)
 		return COMPARE_RESULT(a_class, b_class);
 	mp_compare_f cmp = mp_class_comparators[a_class];
@@ -410,6 +525,8 @@ tuple_compare_field(const char *field_a, const char *field_b,
 		return coll != NULL ?
 		       mp_compare_scalar_coll(field_a, field_b, coll) :
 		       mp_compare_scalar(field_a, field_b);
+	case FIELD_TYPE_DECIMAL:
+		return mp_compare_decimal(field_a, field_b);
 	default:
 		unreachable();
 		return 0;
@@ -443,6 +560,9 @@ tuple_compare_field_with_type(const char *field_a, enum mp_type a_type,
 		       mp_compare_scalar_coll(field_a, field_b, coll) :
 		       mp_compare_scalar_with_type(field_a, a_type,
 						   field_b, b_type);
+	case FIELD_TYPE_DECIMAL:
+		return mp_compare_number_with_type(field_a, a_type,
+						   field_b, b_type);
 	default:
 		unreachable();
 		return 0;
@@ -1502,6 +1622,24 @@ hint_double(double d)
 	return hint_create(MP_CLASS_NUMBER, val);
 }
 
+static inline hint_t
+hint_decimal(decimal_t *dec)
+{
+	uint64_t val = 0;
+	int64_t num;
+	if (decimal_to_int64(dec, &num) &&
+	    num >= HINT_VALUE_INT_MIN && num <= HINT_VALUE_INT_MAX) {
+		val = num - HINT_VALUE_INT_MIN;
+	} else if (!(dec->bits & DECNEG)) {
+		val = HINT_VALUE_MAX;
+	}
+	/*
+	 * In case the number is negative and out of bounds, val
+	 * remains zero.
+	 */
+	return hint_create(MP_CLASS_NUMBER, val);
+}
+
 static inline uint64_t
 hint_str_raw(const char *s, uint32_t len)
 {
@@ -1578,12 +1716,43 @@ field_hint_number(const char *field)
 		return hint_double(mp_decode_float(&field));
 	case MP_DOUBLE:
 		return hint_double(mp_decode_double(&field));
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&field, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+		{
+			decimal_t dec;
+			return hint_decimal(decimal_unpack(&field, len, &dec));
+		}
+		default:
+			unreachable();
+		}
+	}
 	default:
 		unreachable();
 	}
 	return HINT_NONE;
 }
 
+static inline hint_t
+field_hint_decimal(const char *field)
+{
+	assert(mp_typeof(*field) == MP_EXT);
+	int8_t ext_type;
+	uint32_t len = mp_decode_extl(&field, &ext_type);
+	switch (ext_type) {
+	case MP_DECIMAL:
+	{
+		decimal_t dec;
+		return hint_decimal(decimal_unpack(&field, len, &dec));
+	}
+	default:
+		unreachable();
+	}
+}
+
 static inline hint_t
 field_hint_string(const char *field, struct coll *coll)
 {
@@ -1623,6 +1792,20 @@ field_hint_scalar(const char *field, struct coll *coll)
 	case MP_BIN:
 		len = mp_decode_binl(&field);
 		return hint_bin(field, len);
+	case MP_EXT:
+	{
+		int8_t ext_type;
+		uint32_t len = mp_decode_extl(&field, &ext_type);
+		switch (ext_type) {
+		case MP_DECIMAL:
+		{
+			decimal_t dec;
+			return hint_decimal(decimal_unpack(&field, len, &dec));
+		}
+		default:
+			unreachable();
+		}
+	}
 	default:
 		unreachable();
 	}
@@ -1650,6 +1833,8 @@ field_hint(const char *field, struct coll *coll)
 		return field_hint_varbinary(field);
 	case FIELD_TYPE_SCALAR:
 		return field_hint_scalar(field, coll);
+	case FIELD_TYPE_DECIMAL:
+		return field_hint_decimal(field);
 	default:
 		unreachable();
 	}
@@ -1755,6 +1940,9 @@ key_def_set_hint_func(struct key_def *def)
 	case FIELD_TYPE_SCALAR:
 		key_def_set_hint_func<FIELD_TYPE_SCALAR>(def);
 		break;
+	case FIELD_TYPE_DECIMAL:
+		key_def_set_hint_func<FIELD_TYPE_DECIMAL>(def);
+		break;
 	default:
 		/* Invalid key definition. */
 		def->key_hint = NULL;
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index 514d5d9c0..154535ab1 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -1169,8 +1169,7 @@ tuple_format_iterator_next(struct tuple_format_iterator *it,
 	 * defined in format.
 	 */
 	bool is_nullable = tuple_field_is_nullable(field);
-	if (!field_mp_type_is_compatible(field->type, mp_typeof(*entry->data),
-					 is_nullable) != 0) {
+	if (!field_mp_type_is_compatible(field->type, entry->data, is_nullable) != 0) {
 		diag_set(ClientError, ER_FIELD_TYPE,
 			 tuple_field_path(field),
 			 field_type_strs[field->type]);
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
index 77b6dbfd0..9a162c73c 100644
--- a/src/lib/core/decimal.h
+++ b/src/lib/core/decimal.h
@@ -37,6 +37,10 @@
 #include "third_party/decNumber/decNumber.h"
 #include <stdint.h>
 
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
 typedef decNumber decimal_t;
 
 /**
@@ -239,4 +243,8 @@ decimal_pack(char *data, const decimal_t *dec);
 decimal_t *
 decimal_unpack(const char **data, uint32_t len, decimal_t *dec);
 
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
 #endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
diff --git a/src/lib/core/mp_decimal.h b/src/lib/core/mp_decimal.h
index a991a5f16..778529068 100644
--- a/src/lib/core/mp_decimal.h
+++ b/src/lib/core/mp_decimal.h
@@ -34,6 +34,10 @@
 #include "decimal.h"
 #include <stdint.h>
 
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
 /**
  * \brief Calculate exact buffer size needed to store a decimal
  * pointed to by \a dec.
@@ -59,4 +63,8 @@ mp_decode_decimal(const char **data, decimal_t *dec);
 char *
 mp_encode_decimal(char *data, const decimal_t *dec);
 
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
 #endif
diff --git a/test/engine/ddl.result b/test/engine/ddl.result
index fa3f6051f..67b22ed9e 100644
--- a/test/engine/ddl.result
+++ b/test/engine/ddl.result
@@ -1034,6 +1034,9 @@ s:drop()
 --
 -- gh-2800: space formats checking is broken.
 --
+decimal = require('decimal')
+---
+...
 -- Ensure that vinyl correctly process field count change.
 s = box.schema.space.create('test', {engine = engine, field_count = 2})
 ---
@@ -1092,13 +1095,16 @@ format[9] = {name = 'field9', type = 'array'}
 format[10] = {name = 'field10', type = 'map'}
 ---
 ...
+format[11] = {name = 'field11', type = 'decimal'}
+---
+...
 s = box.schema.space.create('test', {engine = engine, format = format})
 ---
 ...
 pk = s:create_index('pk')
 ---
 ...
-t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}}
+t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}, decimal.new(-11.11)}
 ---
 ...
 inspector:cmd("setopt delimiter ';'")
@@ -1160,6 +1166,11 @@ fail_format_change(3, 'map')
 ---
 - 'Tuple field 3 type does not match one required by operation: expected map'
 ...
+-- unsigned --X--> decimal
+fail_format_change(3, 'decimal')
+---
+- 'Tuple field 3 type does not match one required by operation: expected decimal'
+...
 -- string -----> any
 ok_format_change(4, 'any')
 ---
@@ -1173,6 +1184,11 @@ fail_format_change(4, 'boolean')
 ---
 - 'Tuple field 4 type does not match one required by operation: expected boolean'
 ...
+-- string --X--> decimal
+fail_format_change(4, 'decimal')
+---
+- 'Tuple field 4 type does not match one required by operation: expected decimal'
+...
 -- number -----> any
 ok_format_change(5, 'any')
 ---
@@ -1186,6 +1202,11 @@ fail_format_change(5, 'integer')
 ---
 - 'Tuple field 5 type does not match one required by operation: expected integer'
 ...
+-- number --X--> decimal
+fail_format_change(5, 'decimal')
+---
+- 'Tuple field 5 type does not match one required by operation: expected decimal'
+...
 -- integer -----> any
 ok_format_change(6, 'any')
 ---
@@ -1203,6 +1224,11 @@ fail_format_change(6, 'unsigned')
 ---
 - 'Tuple field 6 type does not match one required by operation: expected unsigned'
 ...
+-- integer --X--> decimal
+fail_format_change(6, 'decimal')
+---
+- 'Tuple field 6 type does not match one required by operation: expected decimal'
+...
 -- boolean -----> any
 ok_format_change(7, 'any')
 ---
@@ -1216,6 +1242,11 @@ fail_format_change(7, 'string')
 ---
 - 'Tuple field 7 type does not match one required by operation: expected string'
 ...
+-- boolead --X--> decimal
+fail_format_change(7, 'decimal')
+---
+- 'Tuple field 7 type does not match one required by operation: expected decimal'
+...
 -- scalar -----> any
 ok_format_change(8, 'any')
 ---
@@ -1225,6 +1256,11 @@ fail_format_change(8, 'unsigned')
 ---
 - 'Tuple field 8 type does not match one required by operation: expected unsigned'
 ...
+-- scalar --X--> decimal
+fail_format_change(8, 'decimal')
+---
+- 'Tuple field 8 type does not match one required by operation: expected decimal'
+...
 -- array -----> any
 ok_format_change(9, 'any')
 ---
@@ -1234,6 +1270,11 @@ fail_format_change(9, 'scalar')
 ---
 - 'Tuple field 9 type does not match one required by operation: expected scalar'
 ...
+-- arary --X--> decimal
+fail_format_change(9, 'decimal')
+---
+- 'Tuple field 9 type does not match one required by operation: expected decimal'
+...
 -- map -----> any
 ok_format_change(10, 'any')
 ---
@@ -1243,6 +1284,48 @@ fail_format_change(10, 'scalar')
 ---
 - 'Tuple field 10 type does not match one required by operation: expected scalar'
 ...
+-- map --X--> decimal
+fail_format_change(10, 'decimal')
+---
+- 'Tuple field 10 type does not match one required by operation: expected decimal'
+...
+-- decimal ----> any
+ok_format_change(11, 'any')
+---
+...
+-- decimal ----> number
+ok_format_change(11, 'number')
+---
+...
+-- decimal ----> scalar
+ok_format_change(11, 'scalar')
+---
+...
+-- decimal --X--> string
+fail_format_change(11, 'string')
+---
+- 'Tuple field 11 type does not match one required by operation: expected string'
+...
+-- decimal --X--> integer
+fail_format_change(11, 'integer')
+---
+- 'Tuple field 11 type does not match one required by operation: expected integer'
+...
+-- decimal --X--> unsigned
+fail_format_change(11, 'unsigned')
+---
+- 'Tuple field 11 type does not match one required by operation: expected unsigned'
+...
+-- decimal --X--> map
+fail_format_change(11, 'map')
+---
+- 'Tuple field 11 type does not match one required by operation: expected map'
+...
+-- decimal --X--> array
+fail_format_change(11, 'array')
+---
+- 'Tuple field 11 type does not match one required by operation: expected array'
+...
 s:drop()
 ---
 ...
diff --git a/test/engine/ddl.test.lua b/test/engine/ddl.test.lua
index d15bf1f58..e761966d7 100644
--- a/test/engine/ddl.test.lua
+++ b/test/engine/ddl.test.lua
@@ -355,6 +355,8 @@ s:drop()
 -- gh-2800: space formats checking is broken.
 --
 
+decimal = require('decimal')
+
 -- Ensure that vinyl correctly process field count change.
 s = box.schema.space.create('test', {engine = engine, field_count = 2})
 pk = s:create_index('pk')
@@ -376,9 +378,11 @@ format[7] = {name = 'field7', type = 'boolean'}
 format[8] = {name = 'field8', type = 'scalar'}
 format[9] = {name = 'field9', type = 'array'}
 format[10] = {name = 'field10', type = 'map'}
+format[11] = {name = 'field11', type = 'decimal'}
+
 s = box.schema.space.create('test', {engine = engine, format = format})
 pk = s:create_index('pk')
-t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}}
+t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}, decimal.new(-11.11)}
 
 inspector:cmd("setopt delimiter ';'")
 function fail_format_change(fieldno, new_type)
@@ -415,6 +419,8 @@ ok_format_change(3, 'integer')
 ok_format_change(3, 'scalar')
 -- unsigned --X--> map
 fail_format_change(3, 'map')
+-- unsigned --X--> decimal
+fail_format_change(3, 'decimal')
 
 -- string -----> any
 ok_format_change(4, 'any')
@@ -422,6 +428,8 @@ ok_format_change(4, 'any')
 ok_format_change(4, 'scalar')
 -- string --X--> boolean
 fail_format_change(4, 'boolean')
+-- string --X--> decimal
+fail_format_change(4, 'decimal')
 
 -- number -----> any
 ok_format_change(5, 'any')
@@ -429,6 +437,8 @@ ok_format_change(5, 'any')
 ok_format_change(5, 'scalar')
 -- number --X--> integer
 fail_format_change(5, 'integer')
+-- number --X--> decimal
+fail_format_change(5, 'decimal')
 
 -- integer -----> any
 ok_format_change(6, 'any')
@@ -438,6 +448,8 @@ ok_format_change(6, 'number')
 ok_format_change(6, 'scalar')
 -- integer --X--> unsigned
 fail_format_change(6, 'unsigned')
+-- integer --X--> decimal
+fail_format_change(6, 'decimal')
 
 -- boolean -----> any
 ok_format_change(7, 'any')
@@ -445,22 +457,46 @@ ok_format_change(7, 'any')
 ok_format_change(7, 'scalar')
 -- boolean --X--> string
 fail_format_change(7, 'string')
+-- boolead --X--> decimal
+fail_format_change(7, 'decimal')
 
 -- scalar -----> any
 ok_format_change(8, 'any')
 -- scalar --X--> unsigned
 fail_format_change(8, 'unsigned')
+-- scalar --X--> decimal
+fail_format_change(8, 'decimal')
 
 -- array -----> any
 ok_format_change(9, 'any')
 -- array --X--> scalar
 fail_format_change(9, 'scalar')
+-- arary --X--> decimal
+fail_format_change(9, 'decimal')
 
 -- map -----> any
 ok_format_change(10, 'any')
 -- map --X--> scalar
 fail_format_change(10, 'scalar')
-
+-- map --X--> decimal
+fail_format_change(10, 'decimal')
+
+-- decimal ----> any
+ok_format_change(11, 'any')
+-- decimal ----> number
+ok_format_change(11, 'number')
+-- decimal ----> scalar
+ok_format_change(11, 'scalar')
+-- decimal --X--> string
+fail_format_change(11, 'string')
+-- decimal --X--> integer
+fail_format_change(11, 'integer')
+-- decimal --X--> unsigned
+fail_format_change(11, 'unsigned')
+-- decimal --X--> map
+fail_format_change(11, 'map')
+-- decimal --X--> array
+fail_format_change(11, 'array')
 s:drop()
 
 -- Check new fields adding.
diff --git a/test/engine/decimal.result b/test/engine/decimal.result
index f8888d7c9..415868c89 100644
--- a/test/engine/decimal.result
+++ b/test/engine/decimal.result
@@ -12,6 +12,9 @@ engine = test_run:get_cfg('engine')
 decimal = require('decimal')
  | ---
  | ...
+ffi = require('ffi')
+ | ---
+ | ...
 
 _ = box.schema.space.create('test', {engine=engine})
  | ---
@@ -76,6 +79,278 @@ box.space.test:select{}
  |   - [3, 3.3]
  |   - [4, 1234567890123456789.9876543210987654321, 1.2345]
  | ...
+
+box.space.test:drop()
+ | ---
+ | ...
+
+-- check decimal indexes
+_ = box.schema.space.create('test', {engine=engine})
+ | ---
+ | ...
+_ = box.space.test:create_index('pk', {parts={1,'decimal'}})
+ | ---
+ | ...
+
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+for i = 0,16 do
+    box.space.test:insert{decimal.new((i-8)/4)}
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+
+box.space.test:select{}
+ | ---
+ | - - [-2]
+ |   - [-1.75]
+ |   - [-1.5]
+ |   - [-1.25]
+ |   - [-1]
+ |   - [-0.75]
+ |   - [-0.5]
+ |   - [-0.25]
+ |   - [0]
+ |   - [0.25]
+ |   - [0.5]
+ |   - [0.75]
+ |   - [1]
+ |   - [1.25]
+ |   - [1.5]
+ |   - [1.75]
+ |   - [2]
+ | ...
+
+-- check invalid values
+box.space.test:insert{1.23}
+ | ---
+ | - error: 'Tuple field 1 type does not match one required by operation: expected decimal'
+ | ...
+box.space.test:insert{'str'}
+ | ---
+ | - error: 'Tuple field 1 type does not match one required by operation: expected decimal'
+ | ...
+box.space.test:insert{ffi.new('uint64_t', 0)}
+ | ---
+ | - error: 'Tuple field 1 type does not match one required by operation: expected decimal'
+ | ...
+-- check duplicates
+box.space.test:insert{decimal.new(0)}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+
+box.space.test.index.pk:drop()
+ | ---
+ | ...
+
+_ = box.space.test:create_index('pk', {parts={1, 'number'}})
+ | ---
+ | ...
+
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+for i = 0, 32 do
+    local val = (i - 16) / 8
+    if i % 2 == 1 then val = decimal.new(val) end
+    box.space.test:insert{val}
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+
+box.space.test:select{}
+ | ---
+ | - - [-2]
+ |   - [-1.875]
+ |   - [-1.75]
+ |   - [-1.625]
+ |   - [-1.5]
+ |   - [-1.375]
+ |   - [-1.25]
+ |   - [-1.125]
+ |   - [-1]
+ |   - [-0.875]
+ |   - [-0.75]
+ |   - [-0.625]
+ |   - [-0.5]
+ |   - [-0.375]
+ |   - [-0.25]
+ |   - [-0.125]
+ |   - [0]
+ |   - [0.125]
+ |   - [0.25]
+ |   - [0.375]
+ |   - [0.5]
+ |   - [0.625]
+ |   - [0.75]
+ |   - [0.875]
+ |   - [1]
+ |   - [1.125]
+ |   - [1.25]
+ |   - [1.375]
+ |   - [1.5]
+ |   - [1.625]
+ |   - [1.75]
+ |   - [1.875]
+ |   - [2]
+ | ...
+
+-- check duplicates
+box.space.test:insert{-2}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+box.space.test:insert{decimal.new(-2)}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+box.space.test:insert{decimal.new(-1.875)}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+box.space.test:insert{-1.875}
+ | ---
+ | - error: Duplicate key exists in unique index 'pk' in space 'test'
+ | ...
+
+box.space.test.index.pk:drop()
+ | ---
+ | ...
+
+_ = box.space.test:create_index('pk')
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+for i = 1,10 do
+    box.space.test:insert{i, decimal.new(i/10)}
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+
+-- a bigger test with a secondary index this time.
+box.space.test:insert{11, 'str'}
+ | ---
+ | - [11, 'str']
+ | ...
+box.space.test:insert{12, 0.63}
+ | ---
+ | - [12, 0.63]
+ | ...
+box.space.test:insert{13, 0.57}
+ | ---
+ | - [13, 0.57]
+ | ...
+box.space.test:insert{14, 0.33}
+ | ---
+ | - [14, 0.33]
+ | ...
+box.space.test:insert{16, 0.71}
+ | ---
+ | - [16, 0.71]
+ | ...
+
+_ = box.space.test:create_index('sk', {parts={2, 'scalar'}})
+ | ---
+ | ...
+box.space.test.index.sk:select{}
+ | ---
+ | - - [1, 0.1]
+ |   - [2, 0.2]
+ |   - [3, 0.3]
+ |   - [14, 0.33]
+ |   - [4, 0.4]
+ |   - [5, 0.5]
+ |   - [13, 0.57]
+ |   - [6, 0.6]
+ |   - [12, 0.63]
+ |   - [7, 0.7]
+ |   - [16, 0.71]
+ |   - [8, 0.8]
+ |   - [9, 0.9]
+ |   - [10, 1]
+ |   - [11, 'str']
+ | ...
+
+box.space.test:drop()
+ | ---
+ | ...
+
+-- check index alter
+_ = box.schema.space.create('test', {engine=engine})
+ | ---
+ | ...
+_ = box.space.test:create_index('pk')
+ | ---
+ | ...
+_ = box.space.test:create_index('sk', {parts={2, 'number'}})
+ | ---
+ | ...
+box.space.test:insert{1, decimal.new(-2)}
+ | ---
+ | - [1, -2]
+ | ...
+box.space.test:insert{2, -5}
+ | ---
+ | - [2, -5]
+ | ...
+-- failure
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+ | ---
+ | - error: 'Tuple field 2 type does not match one required by operation: expected decimal'
+ | ...
+_ = box.space.test:delete{2}
+ | ---
+ | ...
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+ | ---
+ | ...
+box.space.test:insert{3, decimal.new(3)}
+ | ---
+ | - [3, 3]
+ | ...
+--failure
+box.space.test:insert{4, 'string'}
+ | ---
+ | - error: 'Tuple field 2 type does not match one required by operation: expected decimal'
+ | ...
+-- failure
+box.space.test:insert{2, -5}
+ | ---
+ | - error: 'Tuple field 2 type does not match one required by operation: expected decimal'
+ | ...
+box.space.test.index.sk:alter{parts={2, 'number'}}
+ | ---
+ | ...
+box.space.test:insert{2, -5}
+ | ---
+ | - [2, -5]
+ | ...
+box.space.test.index.sk:select{}
+ | ---
+ | - - [2, -5]
+ |   - [1, -2]
+ |   - [3, 3]
+ | ...
+
 box.space.test:drop()
  | ---
  | ...
diff --git a/test/engine/decimal.test.lua b/test/engine/decimal.test.lua
index 1b14871b0..3763bf0a3 100644
--- a/test/engine/decimal.test.lua
+++ b/test/engine/decimal.test.lua
@@ -3,6 +3,7 @@ test_run = env.new()
 engine = test_run:get_cfg('engine')
 
 decimal = require('decimal')
+ffi = require('ffi')
 
 _ = box.schema.space.create('test', {engine=engine})
 _ = box.space.test:create_index('pk')
@@ -18,4 +19,86 @@ a:next(2)
 a:slice(-2)
 box.space.test:replace{3, decimal.new(3.3)}
 box.space.test:select{}
+
+box.space.test:drop()
+
+-- check decimal indexes
+_ = box.schema.space.create('test', {engine=engine})
+_ = box.space.test:create_index('pk', {parts={1,'decimal'}})
+
+test_run:cmd('setopt delimiter ";"')
+for i = 0,16 do
+    box.space.test:insert{decimal.new((i-8)/4)}
+end;
+test_run:cmd('setopt delimiter ""');
+
+box.space.test:select{}
+
+-- check invalid values
+box.space.test:insert{1.23}
+box.space.test:insert{'str'}
+box.space.test:insert{ffi.new('uint64_t', 0)}
+-- check duplicates
+box.space.test:insert{decimal.new(0)}
+
+box.space.test.index.pk:drop()
+
+_ = box.space.test:create_index('pk', {parts={1, 'number'}})
+
+test_run:cmd('setopt delimiter ";"')
+for i = 0, 32 do
+    local val = (i - 16) / 8
+    if i % 2 == 1 then val = decimal.new(val) end
+    box.space.test:insert{val}
+end;
+test_run:cmd('setopt delimiter ""');
+
+box.space.test:select{}
+
+-- check duplicates
+box.space.test:insert{-2}
+box.space.test:insert{decimal.new(-2)}
+box.space.test:insert{decimal.new(-1.875)}
+box.space.test:insert{-1.875}
+
+box.space.test.index.pk:drop()
+
+_ = box.space.test:create_index('pk')
+test_run:cmd('setopt delimiter ";"')
+for i = 1,10 do
+    box.space.test:insert{i, decimal.new(i/10)}
+end;
+test_run:cmd('setopt delimiter ""');
+
+-- a bigger test with a secondary index this time.
+box.space.test:insert{11, 'str'}
+box.space.test:insert{12, 0.63}
+box.space.test:insert{13, 0.57}
+box.space.test:insert{14, 0.33}
+box.space.test:insert{16, 0.71}
+
+_ = box.space.test:create_index('sk', {parts={2, 'scalar'}})
+box.space.test.index.sk:select{}
+
+box.space.test:drop()
+
+-- check index alter
+_ = box.schema.space.create('test', {engine=engine})
+_ = box.space.test:create_index('pk')
+_ = box.space.test:create_index('sk', {parts={2, 'number'}})
+box.space.test:insert{1, decimal.new(-2)}
+box.space.test:insert{2, -5}
+-- failure
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+_ = box.space.test:delete{2}
+box.space.test.index.sk:alter{parts={2, 'decimal'}}
+box.space.test:insert{3, decimal.new(3)}
+--failure
+box.space.test:insert{4, 'string'}
+-- failure
+box.space.test:insert{2, -5}
+box.space.test.index.sk:alter{parts={2, 'number'}}
+box.space.test:insert{2, -5}
+box.space.test.index.sk:select{}
+
 box.space.test:drop()
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index 6e03fbbbd..63dc262bd 100644
--- a/test/engine/iterator.result
+++ b/test/engine/iterator.result
@@ -1471,6 +1471,736 @@ t
 space:drop()
 ---
 ...
+-- iterator (decimal)
+decimal = require('decimal')
+---
+...
+space = box.schema.space.create('test', { engine = engine })
+---
+...
+index = space:create_index('primary', { type = 'tree', parts = {1, 'decimal'} })
+---
+...
+for key = 1, 100 do space:replace{decimal.new((key-50)/10)} end
+---
+...
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-4.9]
+  - [-4.8]
+  - [-4.7]
+  - [-4.6]
+  - [-4.5]
+  - [-4.4]
+  - [-4.3]
+  - [-4.2]
+  - [-4.1]
+  - [-4]
+  - [-3.9]
+  - [-3.8]
+  - [-3.7]
+  - [-3.6]
+  - [-3.5]
+  - [-3.4]
+  - [-3.3]
+  - [-3.2]
+  - [-3.1]
+  - [-3]
+  - [-2.9]
+  - [-2.8]
+  - [-2.7]
+  - [-2.6]
+  - [-2.5]
+  - [-2.4]
+  - [-2.3]
+  - [-2.2]
+  - [-2.1]
+  - [-2]
+  - [-1.9]
+  - [-1.8]
+  - [-1.7]
+  - [-1.6]
+  - [-1.5]
+  - [-1.4]
+  - [-1.3]
+  - [-1.2]
+  - [-1.1]
+  - [-1]
+  - [-0.9]
+  - [-0.8]
+  - [-0.7]
+  - [-0.6]
+  - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-4.9]
+  - [-4.8]
+  - [-4.7]
+  - [-4.6]
+  - [-4.5]
+  - [-4.4]
+  - [-4.3]
+  - [-4.2]
+  - [-4.1]
+  - [-4]
+  - [-3.9]
+  - [-3.8]
+  - [-3.7]
+  - [-3.6]
+  - [-3.5]
+  - [-3.4]
+  - [-3.3]
+  - [-3.2]
+  - [-3.1]
+  - [-3]
+  - [-2.9]
+  - [-2.8]
+  - [-2.7]
+  - [-2.6]
+  - [-2.5]
+  - [-2.4]
+  - [-2.3]
+  - [-2.2]
+  - [-2.1]
+  - [-2]
+  - [-1.9]
+  - [-1.8]
+  - [-1.7]
+  - [-1.6]
+  - [-1.5]
+  - [-1.4]
+  - [-1.3]
+  - [-1.2]
+  - [-1.1]
+  - [-1]
+  - [-0.9]
+  - [-0.8]
+  - [-0.7]
+  - [-0.6]
+  - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-0.6]
+  - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [-0.5]
+  - [-0.4]
+  - [-0.3]
+  - [-0.2]
+  - [-0.1]
+  - [0]
+  - [0.1]
+  - [0.2]
+  - [0.3]
+  - [0.4]
+  - [0.5]
+  - [0.6]
+  - [0.7]
+  - [0.8]
+  - [0.9]
+  - [1]
+  - [1.1]
+  - [1.2]
+  - [1.3]
+  - [1.4]
+  - [1.5]
+  - [1.6]
+  - [1.7]
+  - [1.8]
+  - [1.9]
+  - [2]
+  - [2.1]
+  - [2.2]
+  - [2.3]
+  - [2.4]
+  - [2.5]
+  - [2.6]
+  - [2.7]
+  - [2.8]
+  - [2.9]
+  - [3]
+  - [3.1]
+  - [3.2]
+  - [3.3]
+  - [3.4]
+  - [3.5]
+  - [3.6]
+  - [3.7]
+  - [3.8]
+  - [3.9]
+  - [4]
+  - [4.1]
+  - [4.2]
+  - [4.3]
+  - [4.4]
+  - [4.5]
+  - [4.6]
+  - [4.7]
+  - [4.8]
+  - [4.9]
+  - [5]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [5]
+  - [4.9]
+  - [4.8]
+  - [4.7]
+  - [4.6]
+  - [4.5]
+  - [4.4]
+  - [4.3]
+  - [4.2]
+  - [4.1]
+  - [4]
+  - [3.9]
+  - [3.8]
+  - [3.7]
+  - [3.6]
+  - [3.5]
+  - [3.4]
+  - [3.3]
+  - [3.2]
+  - [3.1]
+  - [3]
+  - [2.9]
+  - [2.8]
+  - [2.7]
+  - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [2.7]
+  - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [5]
+  - [4.9]
+  - [4.8]
+  - [4.7]
+  - [4.6]
+  - [4.5]
+  - [4.4]
+  - [4.3]
+  - [4.2]
+  - [4.1]
+  - [4]
+  - [3.9]
+  - [3.8]
+  - [3.7]
+  - [3.6]
+  - [3.5]
+  - [3.4]
+  - [3.3]
+  - [3.2]
+  - [3.1]
+  - [3]
+  - [2.9]
+  - [2.8]
+  - [2.7]
+  - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [2.6]
+  - [2.5]
+  - [2.4]
+  - [2.3]
+  - [2.2]
+  - [2.1]
+  - [2]
+  - [1.9]
+  - [1.8]
+  - [1.7]
+  - [1.6]
+  - [1.5]
+  - [1.4]
+  - [1.3]
+  - [1.2]
+  - [1.1]
+  - [1]
+  - [0.9]
+  - [0.8]
+  - [0.7]
+  - [0.6]
+  - [0.5]
+  - [0.4]
+  - [0.3]
+  - [0.2]
+  - [0.1]
+  - [0]
+  - [-0.1]
+  - [-0.2]
+  - [-0.3]
+  - [-0.4]
+  - [-0.5]
+  - [-0.6]
+  - [-0.7]
+  - [-0.8]
+  - [-0.9]
+  - [-1]
+  - [-1.1]
+  - [-1.2]
+  - [-1.3]
+  - [-1.4]
+  - [-1.5]
+  - [-1.6]
+  - [-1.7]
+  - [-1.8]
+  - [-1.9]
+  - [-2]
+  - [-2.1]
+  - [-2.2]
+  - [-2.3]
+  - [-2.4]
+  - [-2.5]
+  - [-2.6]
+  - [-2.7]
+  - [-2.8]
+  - [-2.9]
+  - [-3]
+  - [-3.1]
+  - [-3.2]
+  - [-3.3]
+  - [-3.4]
+  - [-3.5]
+  - [-3.6]
+  - [-3.7]
+  - [-3.8]
+  - [-3.9]
+  - [-4]
+  - [-4.1]
+  - [-4.2]
+  - [-4.3]
+  - [-4.4]
+  - [-4.5]
+  - [-4.6]
+  - [-4.7]
+  - [-4.8]
+  - [-4.9]
+...
+space:drop()
+---
+...
 -- iterator multi-part (num, num)
 space = box.schema.space.create('test', { engine = engine })
 ---
diff --git a/test/engine/iterator.test.lua b/test/engine/iterator.test.lua
index 9ff51ebe1..2f83d19dd 100644
--- a/test/engine/iterator.test.lua
+++ b/test/engine/iterator.test.lua
@@ -48,6 +48,28 @@ t = {} for state, v in index:pairs(77, {iterator = 'LT'}) do table.insert(t, v)
 t
 space:drop()
 
+-- iterator (decimal)
+decimal = require('decimal')
+space = box.schema.space.create('test', { engine = engine })
+index = space:create_index('primary', { type = 'tree', parts = {1, 'decimal'} })
+for key = 1, 100 do space:replace{decimal.new((key-50)/10)} end
+t = {} for state, v in index:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(-0.6), {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in index:pairs(decimal.new(2.7), {iterator = 'LT'}) do table.insert(t, v) end
+t
+space:drop()
 
 -- iterator multi-part (num, num)
 space = box.schema.space.create('test', { engine = engine })
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 1/6] lua: fix decimal comparison with nil
  2019-08-20 17:09 ` [PATCH v3 1/6] lua: fix decimal comparison with nil Serge Petrenko
@ 2019-08-21 14:13   ` Vladimir Davydov
  0 siblings, 0 replies; 14+ messages in thread
From: Vladimir Davydov @ 2019-08-21 14:13 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Pushed to master.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 2/6] decimal: fix encoding numbers with positive exponent.
  2019-08-20 17:10 ` [PATCH v3 2/6] decimal: fix encoding numbers with positive exponent Serge Petrenko
@ 2019-08-21 14:13   ` Vladimir Davydov
  0 siblings, 0 replies; 14+ messages in thread
From: Vladimir Davydov @ 2019-08-21 14:13 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Pushed to master.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 3/6] lua/pickle: fix a typo
  2019-08-20 17:10 ` [PATCH v3 3/6] lua/pickle: fix a typo Serge Petrenko
@ 2019-08-21 14:13   ` Vladimir Davydov
  0 siblings, 0 replies; 14+ messages in thread
From: Vladimir Davydov @ 2019-08-21 14:13 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Pushed to master.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 4/6] decimal: allow to encode/decode decimals as MsgPack
  2019-08-20 17:10 ` [PATCH v3 4/6] decimal: allow to encode/decode decimals as MsgPack Serge Petrenko
@ 2019-08-21 15:02   ` Vladimir Davydov
  0 siblings, 0 replies; 14+ messages in thread
From: Vladimir Davydov @ 2019-08-21 15:02 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Ack

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 5/6] decimal: add conversions to (u)int64_t
  2019-08-20 17:10 ` [PATCH v3 5/6] decimal: add conversions to (u)int64_t Serge Petrenko
@ 2019-08-21 15:02   ` Vladimir Davydov
  0 siblings, 0 replies; 14+ messages in thread
From: Vladimir Davydov @ 2019-08-21 15:02 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Ack

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v3 6/6] decimal: allow to index decimals
  2019-08-20 17:10 ` [PATCH v3 6/6] decimal: allow to index decimals Serge Petrenko
@ 2019-08-21 15:02   ` Vladimir Davydov
  2019-08-22 10:33   ` [tarantool-patches] " Kirill Yukhin
  1 sibling, 0 replies; 14+ messages in thread
From: Vladimir Davydov @ 2019-08-21 15:02 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Ack

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [tarantool-patches] [PATCH v3 6/6] decimal: allow to index decimals
  2019-08-20 17:10 ` [PATCH v3 6/6] decimal: allow to index decimals Serge Petrenko
  2019-08-21 15:02   ` Vladimir Davydov
@ 2019-08-22 10:33   ` Kirill Yukhin
  1 sibling, 0 replies; 14+ messages in thread
From: Kirill Yukhin @ 2019-08-22 10:33 UTC (permalink / raw)
  To: tarantool-patches; +Cc: vdavydov.dev, kostja, Serge Petrenko

Hello,

On 20 Aug 20:10, Serge Petrenko wrote:
> Closes #4333
> 
> @TarantoolBot document
> Title: Document decimal field type.
> 
> Decimals may now be stored in spaces. A corresponding field type is
> introduced: 'decimal'. Decimal values are also allowed in 'scalar',
> 'any' and 'number' fields.
> 
> 'decimal' field type is appropriate for both memtx HASH and TREE
> indices, as well as for vinyl TREE index.

I've checked the patch into master.

--
Regards, Kirill Yukhin

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2019-08-22 10:33 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-08-20 17:09 [PATCH v3 0/6] Decimal indices Serge Petrenko
2019-08-20 17:09 ` [PATCH v3 1/6] lua: fix decimal comparison with nil Serge Petrenko
2019-08-21 14:13   ` Vladimir Davydov
2019-08-20 17:10 ` [PATCH v3 2/6] decimal: fix encoding numbers with positive exponent Serge Petrenko
2019-08-21 14:13   ` Vladimir Davydov
2019-08-20 17:10 ` [PATCH v3 3/6] lua/pickle: fix a typo Serge Petrenko
2019-08-21 14:13   ` Vladimir Davydov
2019-08-20 17:10 ` [PATCH v3 4/6] decimal: allow to encode/decode decimals as MsgPack Serge Petrenko
2019-08-21 15:02   ` Vladimir Davydov
2019-08-20 17:10 ` [PATCH v3 5/6] decimal: add conversions to (u)int64_t Serge Petrenko
2019-08-21 15:02   ` Vladimir Davydov
2019-08-20 17:10 ` [PATCH v3 6/6] decimal: allow to index decimals Serge Petrenko
2019-08-21 15:02   ` Vladimir Davydov
2019-08-22 10:33   ` [tarantool-patches] " Kirill Yukhin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox