Tarantool development patches archive
 help / color / mirror / Atom feed
* [PATCH v3 0/4] Introduce decimal type to tarantool core.
@ 2019-06-04 16:04 Serge Petrenko
  2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw)
  To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko

https://github.com/tarantool/tarantool/issues/692
https://github.com/tarantool/tarantool/tree/sp/gh-692-mp-encode-decimal

This patchset adds a new type, decimal fixed-point, to tarantool and allows to
encode/decode decimals in msgpack.

The first patch adds decNumber library as a submodule.

The second patch adds a small wrapper to the library to make it work
with fixed-point decimal values, and adds a unit test.
To evaluate number precision and scale from exponent and the amount of
significant digits the following formulas are applied:
 If exponent < 0 then precision = MAX(digits, -exponent) and scale = -exponent
 If exponent > 0 then precision = digits + exponent, and scale = 0

The third patch updates msgpuck library to add extension types, which are used
to encode decimals.

The fourth patch adds methods to encode/decode decimals, adds the first
messagepack ext type, MP_DECIMAL, and tests for encoding/decoding decimals.

Changes in v3:
  - add patches 3 and 4 to
    encode/decode decimals
    as msgpack.

Changes in v2:
 - get rid of explicit precision and scale,
   evaluate them from decNumber digits and exponent.
 - decimal is now an alias for decNumber
 - ln, log10, exp, sqrt, pow now accept precision to
   which the result should be rounded.

Serge Petrenko (4):
  third-party: add decNumber library
  lib/core: introduce decimal type to tarantool
  lib: update msgpuck library
  decimal: add MessagePack encoding/decoding support

 .gitmodules                  |   3 +
 CMakeLists.txt               |   7 +
 cmake/BuildDecNumber.cmake   |  14 +
 src/CMakeLists.txt           |   1 +
 src/lib/core/CMakeLists.txt  |   3 +-
 src/lib/core/decimal.c       | 357 ++++++++++++++++++++++++++
 src/lib/core/decimal.h       | 197 ++++++++++++++
 src/lib/core/mp_user_types.h |  38 +++
 src/lib/msgpuck              |   2 +-
 test/unit/CMakeLists.txt     |   2 +
 test/unit/decimal.c          | 239 +++++++++++++++++
 test/unit/decimal.result     | 253 ++++++++++++++++++
 test/unit/msgpack.result     | 480 ++++++++++++++++++++++++++++++++++-
 third_party/decNumber        |   1 +
 14 files changed, 1582 insertions(+), 15 deletions(-)
 create mode 100644 cmake/BuildDecNumber.cmake
 create mode 100644 src/lib/core/decimal.c
 create mode 100644 src/lib/core/decimal.h
 create mode 100644 src/lib/core/mp_user_types.h
 create mode 100644 test/unit/decimal.c
 create mode 100644 test/unit/decimal.result
 create mode 160000 third_party/decNumber

-- 
2.20.1 (Apple Git-117)

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

* [PATCH v3 1/4] third-party: add decNumber library
  2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko
@ 2019-06-04 16:04 ` Serge Petrenko
  2019-06-05  8:04   ` Георгий Кириченко
  2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 12+ messages in thread
From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw)
  To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko

We will use it for decimal implementation in tarantool

Part of #692
---
 .gitmodules           | 3 +++
 third_party/decNumber | 1 +
 2 files changed, 4 insertions(+)
 create mode 160000 third_party/decNumber

diff --git a/.gitmodules b/.gitmodules
index e3aa7d79e..9f52c3fc1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -31,3 +31,6 @@
 	path = third_party/luarocks
 	url = https://github.com/tarantool/luarocks.git
 	branch = tarantool-1.7
+[submodule "third_party/decNumber"]
+	path = third_party/decNumber
+	url = https://github.com/tarantool/decNumber.git
diff --git a/third_party/decNumber b/third_party/decNumber
new file mode 160000
index 000000000..04e31ac65
--- /dev/null
+++ b/third_party/decNumber
@@ -0,0 +1 @@
+Subproject commit 04e31ac65efbfb426c7fc299e736ea4e591b41a9
-- 
2.20.1 (Apple Git-117)

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

* [PATCH v3 2/4] lib/core: introduce decimal type to tarantool
  2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko
  2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko
@ 2019-06-04 16:04 ` Serge Petrenko
  2019-06-05  8:11   ` Георгий Кириченко
  2019-06-05 12:19   ` Vladimir Davydov
  2019-06-04 16:04 ` [PATCH v3 3/4] lib: update msgpuck library Serge Petrenko
  2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko
  3 siblings, 2 replies; 12+ messages in thread
From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw)
  To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko

Add fixed-point decimal type to tarantool core.
Adapt decNumber floating-point decimal library for the purpose, write a
small wrapper and add unit tests.

A new decimal type is an alias for decNumber numbers from the decNumber
library.
Arithmetic operations (+, -, *, /) and some mathematic functions
(ln, log10, exp, pow, sqrt) are available.

We introduce a single context for all the arithmetic operations
on decimals, which enforces both number precision and scale to be
in range [0, 38].

Every mathematic function has precision as one of its parameters, and
the results are rounded to fit the desired precision.

Part of #692
---
 CMakeLists.txt              |   7 +
 cmake/BuildDecNumber.cmake  |  13 ++
 src/CMakeLists.txt          |   1 +
 src/lib/core/CMakeLists.txt |   3 +-
 src/lib/core/decimal.c      | 293 ++++++++++++++++++++++++++++++++++++
 src/lib/core/decimal.h      | 159 +++++++++++++++++++
 test/unit/CMakeLists.txt    |   2 +
 test/unit/decimal.c         | 168 +++++++++++++++++++++
 test/unit/decimal.result    |  51 +++++++
 9 files changed, 696 insertions(+), 1 deletion(-)
 create mode 100644 cmake/BuildDecNumber.cmake
 create mode 100644 src/lib/core/decimal.c
 create mode 100644 src/lib/core/decimal.h
 create mode 100644 test/unit/decimal.c
 create mode 100644 test/unit/decimal.result

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7658fc6c9..bfb15effb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -431,6 +431,13 @@ else()
     find_package(MsgPuck)
 endif()
 
+#
+# decNumber
+#
+
+include(BuildDecNumber)
+decnumber_build()
+
 #
 # LibYAML
 #
diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake
new file mode 100644
index 000000000..80942fe05
--- /dev/null
+++ b/cmake/BuildDecNumber.cmake
@@ -0,0 +1,13 @@
+#
+# A macro to build the bundled decNumber lisbrary.
+macro(decnumber_build)
+    set(decnumber_src
+	${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c
+	${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c
+    )
+
+    add_library(decNumber STATIC ${decnumber_src})
+
+    set(DECNUMBER_INCLUDE_DIR ${PROJECT_BINARY_DIR}/third_party/decNumber)
+    unset(decnumber_src)
+endmacro(decnumber_build)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 183cc04ef..9d196755f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,6 +13,7 @@ include_directories(${MSGPUCK_INCLUDE_DIRS})
 include_directories(${CURL_INCLUDE_DIRS})
 include_directories(${ICU_INCLUDE_DIRS})
 include_directories(${ICONV_INCLUDE_DIRS})
+include_directories(${DECNUMBER_INCLUDE_DIR})
 
 set(LIBUTIL_FREEBSD_SRC ${CMAKE_SOURCE_DIR}/third_party/libutil_freebsd)
 include_directories(${LIBUTIL_FREEBSD_SRC})
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index eb10b11c3..66e430a25 100644
--- a/src/lib/core/CMakeLists.txt
+++ b/src/lib/core/CMakeLists.txt
@@ -26,6 +26,7 @@ set(core_sources
     trigger.cc
     mpstream.c
     port.c
+    decimal.c
 )
 
 if (TARGET_OS_NETBSD)
@@ -37,7 +38,7 @@ endif()
 
 add_library(core STATIC ${core_sources})
 
-target_link_libraries(core salad small uri ${LIBEV_LIBRARIES}
+target_link_libraries(core salad small uri decNumber ${LIBEV_LIBRARIES}
                       ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES}
                       ${MSGPUCK_LIBRARIES})
 
diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
new file mode 100644
index 000000000..12250da95
--- /dev/null
+++ b/src/lib/core/decimal.c
@@ -0,0 +1,293 @@
+/*
+ * 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 "third_party/decNumber/decContext.h"
+#include <stdlib.h>
+#include <assert.h>
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+
+/** A single context for all the arithmetic operations. */
+static decContext decimal_context = {
+	/* Maximum precision during operations. */
+	TARANTOOL_MAX_DECIMAL_DIGITS,
+	/*
+	 * Maximum decimal lagarithm of the number.
+	 * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS
+	 */
+	TARANTOOL_MAX_DECIMAL_DIGITS - 1,
+	/*
+	 * Minimal adjusted exponent. The smallest absolute value will be
+	 * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) =
+	 * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale =
+	 * TARANTOOL_MAX_DECIMAL_DIGITS
+	 */
+	-1,
+	/* Rounding mode: .5 rounds away from 0. */
+	DEC_ROUND_HALF_UP,
+	/* Turn off signalling for failed operations. */
+	0,
+	/* Status holding occured events. Initially empty. */
+	0,
+	/* Turn off exponent clamping. */
+	0
+};
+
+/**
+ * Return operation status and clear it for future checks.
+ *
+ * @return 0 if ok, bitwise or of decNumber errors, if any.
+ */
+static inline uint32_t
+decimal_get_op_status(decContext *context)
+{
+	uint32_t status = decContextGetStatus(context);
+	decContextZeroStatus(&decimal_context);
+	/*
+	 * Clear warnings. Every value less than 0.1 is
+	 * subnormal, DEC_Inexact and DEC_Rounded result
+	 * from rounding. DEC_Inexact with DEC_Subnormal
+	 * together result in DEC_Underflow. DEC_Clamped
+	 * happens after underflow if rounding to zero.
+	 */
+	return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow |
+				    DEC_Subnormal | DEC_Clamped);
+}
+
+/**
+ * A finalizer for all the operations.
+ * Check the operation context status and empty it.
+ *
+ * @return NULL if finalization failed.
+ *         result pointer otherwise.
+ */
+static inline decimal *
+decimal_finalize(decimal *res, decContext *context)
+{
+	uint32_t status = decimal_get_op_status(context);
+	if (status || ! decNumberIsFinite(res)) {
+		return NULL;
+	}
+	return res;
+}
+
+uint8_t decimal_precision(const decimal *dec) {
+	return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) :
+				    dec->digits + dec->exponent;
+}
+
+uint8_t decimal_scale(const decimal *dec) {
+	return dec->exponent < 0 ? -dec->exponent : 0;
+}
+
+decimal *
+decimal_zero(decimal *dec)
+{
+	decNumberZero(dec);
+	return dec;
+}
+
+decimal *
+decimal_from_string(decimal *dec, const char *str)
+{
+	decNumberFromString(dec, str, &decimal_context);
+	return decimal_finalize(dec, &decimal_context);
+}
+
+decimal *
+decimal_from_int(decimal *dec, int32_t num)
+{
+	decNumberFromInt32(dec, num);
+	return decimal_finalize(dec, &decimal_context);
+}
+
+decimal *
+decimal_from_uint(decimal *dec, uint32_t num)
+{
+	decNumberFromUInt32(dec, num);
+	return decimal_finalize(dec, &decimal_context);
+}
+
+char *
+decimal_to_string(const decimal *dec, char *buf)
+{
+	return decNumberToString(dec, buf);
+}
+
+int32_t
+decimal_to_int(const decimal *dec)
+{
+	decNumber res;
+	decNumberToIntegralValue(&res, dec, &decimal_context);
+	return decNumberToInt32(&res, &decimal_context);
+}
+
+uint32_t
+decimal_to_uint(const decimal *dec)
+{
+	decNumber res;
+	decNumberToIntegralValue(&res, dec, &decimal_context);
+	return decNumberToUInt32(&res, &decimal_context);
+}
+
+int
+decimal_compare(const decimal *lhs, const decimal *rhs)
+{
+	decNumber res;
+	decNumberCompare(&res, lhs, rhs, &decimal_context);
+	return decNumberToInt32(&res, &decimal_context);
+}
+
+decimal *
+decimal_abs(decimal *res, const decimal *dec)
+{
+	decNumberAbs(res, dec, &decimal_context);
+	return res;
+}
+
+decimal *
+decimal_add(decimal *res, const decimal *lhs, const decimal *rhs)
+{
+	decNumberAdd(res, lhs, rhs, &decimal_context);
+
+	return decimal_finalize(res, &decimal_context);
+}
+
+decimal *
+decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs)
+{
+	decNumberSubtract(res, lhs, rhs, &decimal_context);
+
+	return decimal_finalize(res, &decimal_context);
+}
+
+decimal *
+decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs)
+{
+	decNumberMultiply(res, lhs, rhs, &decimal_context);
+
+	return decimal_finalize(res, &decimal_context);
+}
+
+decimal *
+decimal_div(decimal *res, const decimal *lhs, const decimal *rhs)
+{
+	decNumberDivide(res, lhs, rhs, &decimal_context);
+
+	return decimal_finalize(res, &decimal_context);
+}
+
+decimal *
+decimal_log10(decimal *res, const decimal *lhs, uint32_t precision)
+{
+	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
+		return NULL;
+
+	/* A separate context to enforce desired precision. */
+	static decContext op_context;
+	op_context.digits = precision;
+	op_context.emax = op_context.digits - 1;
+	op_context.emin = -1;
+
+	decNumberLog10(res, lhs, &op_context);
+
+	return decimal_finalize(res, &op_context);
+}
+
+decimal *
+decimal_ln(decimal *res, const decimal *lhs, uint32_t precision)
+{
+	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
+		return NULL;
+
+	/* A separate context to enforce desired precision. */
+	static decContext op_context;
+	op_context.digits = precision;
+	op_context.emax = op_context.digits - 1;
+	op_context.emin = -1;
+
+	decNumberLn(res, lhs, &op_context);
+
+	return decimal_finalize(res, &op_context);
+}
+
+decimal *
+decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision)
+{
+	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
+		return NULL;
+
+	/* A separate context to enforce desired precision. */
+	static decContext op_context;
+	op_context.digits = precision;
+	op_context.emax = op_context.digits - 1;
+	op_context.emin = -1;
+
+	decNumberPower(res, lhs, rhs, &op_context);
+
+	return decimal_finalize(res, &op_context);
+}
+
+decimal *
+decimal_exp(decimal *res, const decimal *lhs, uint32_t precision)
+{
+	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
+		return NULL;
+
+	/* A separate context to enforce desired precision. */
+	static decContext op_context;
+	op_context.digits = precision;
+	op_context.emax = op_context.digits - 1;
+	op_context.emin = -1;
+
+	decNumberExp(res, lhs, &op_context);
+
+	return decimal_finalize(res, &op_context);
+}
+
+decimal *
+decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision)
+{
+	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
+		return NULL;
+
+	/* A separate context to enforce desired precision. */
+	static decContext op_context;
+	op_context.digits = precision;
+	op_context.emax = op_context.digits - 1;
+	op_context.emin = -1;
+
+	decNumberSquareRoot(res, lhs, &op_context);
+
+	return decimal_finalize(res, &op_context);
+}
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
new file mode 100644
index 000000000..f2812f500
--- /dev/null
+++ b/src/lib/core/decimal.h
@@ -0,0 +1,159 @@
+#ifndef TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED
+#define TARANTOOL_LIB_CORE_DECIMAL_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.
+ */
+#define TARANTOOL_MAX_DECIMAL_DIGITS 38
+#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS
+#define DECSTRING_NO_EXPONENT
+#include "third_party/decNumber/decNumber.h"
+
+typedef decNumber decimal;
+
+/**
+ * @return decimal precision,
+ * i.e. the amount of decimal digits in
+ * its representation.
+ */
+uint8_t
+decimal_precision(const decimal *dec);
+
+/**
+ * @return decimal scale,
+ * i.e. the number of decimal digits after
+ * the decimal separator.
+ */
+uint8_t
+decimal_scale(const decimal *dec);
+
+/**
+ * Initialize a zero decimal number.
+ */
+decimal *
+decimal_zero(decimal *dec);
+
+/**
+ * Initialize a decimal with a value from the string.
+ *
+ * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS,
+ * but has excess digits in fractional part, it will be rounded.
+ *
+ * @return NULL if string is invalid or
+ * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS)
+ */
+decimal *
+decimal_from_string(decimal *dec, const char *str);
+
+/**
+ * Initialize a decimal with an integer value.
+ *
+ * @return NULL if precicion is insufficient to hold
+ * the value or precision/scale are out of bounds.
+*/
+decimal *
+decimal_from_int(decimal *dec, int32_t num);
+
+/** @copydoc decimal_from_int */
+decimal *
+decimal_from_uint(decimal *dec, uint32_t num);
+
+/**
+ * Write the decimal to a string.
+ * A string has to be at least dec->digits + 3 bytes in size.
+ */
+char *
+decimal_to_string(const decimal *dec, char *buf);
+
+/**
+ * Cast decimal to an integer value. The number will be rounded
+ * if it has a fractional part.
+ */
+int32_t
+decimal_to_int(const decimal *dec);
+
+/** @copydoc decimal_to_int */
+uint32_t
+decimal_to_uint(const decimal *dec);
+
+/**
+ * Compare 2 decimal values.
+ * @return -1, lhs < rhs,
+ *	    0, lhs = rhs,
+ *	    1, lhs > rhs
+ */
+int
+decimal_compare(const decimal *lhs, const decimal *rhs);
+
+/**
+ * res is set to the absolute value of dec
+ * decimal_abs(&a, &a) is allowed.
+ */
+decimal *
+decimal_abs(decimal *res, const decimal *dec);
+
+/*
+ * Arithmetic ops: add, subtract, multiply and divide.
+ * Return result pointer on success, NULL on an error (overflow).
+ */
+
+decimal *
+decimal_add(decimal *res, const decimal *lhs, const decimal *rhs);
+
+decimal *
+decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs);
+
+decimal *
+decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs);
+
+decimal *
+decimal_div(decimal *res, const decimal *lhs, const decimal *rhs);
+
+/*
+ * log10, ln, pow, exp, sqrt.
+ * Calculate the appropriate function and then round the result
+ * to the desired precision.
+ * Return result pointer on success, NULL on an error (overflow).
+ */
+decimal *
+decimal_log10(decimal *res, const decimal *lhs, uint32_t precision);
+
+decimal *
+decimal_ln(decimal *res, const decimal *lhs, uint32_t precision);
+
+decimal *
+decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision);
+
+decimal *
+decimal_exp(decimal *res, const decimal *lhs, uint32_t precision);
+
+decimal *
+decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);
+
+#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 70be6366c..e3de34b16 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -68,6 +68,8 @@ add_executable(vclock.test vclock.cc)
 target_link_libraries(vclock.test vclock unit)
 add_executable(xrow.test xrow.cc)
 target_link_libraries(xrow.test xrow unit)
+add_executable(decimal.test decimal.c)
+target_link_libraries(decimal.test core unit)
 
 add_executable(fiber.test fiber.cc)
 set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0)
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
new file mode 100644
index 000000000..adfbead91
--- /dev/null
+++ b/test/unit/decimal.c
@@ -0,0 +1,168 @@
+#include "unit.h"
+#include "decimal.h"
+#include <limits.h>
+#include <string.h>
+
+int
+main(void)
+{
+	plan(50);
+
+	char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
+	char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
+	char *b = "2.718281828";
+
+	decimal s;
+	decimal *ret;
+
+	ret = decimal_from_string(&s, b);
+	isnt(ret, NULL, "Basic construction from string.");
+	decimal_to_string(&s, buf);
+	is(strcmp(buf, b), 0, "Correct construction and to_string conversion.");
+
+	ret = decimal_from_int(&s, INT_MAX);
+	decimal_to_string(&s, buf);
+	sprintf(buf2, "%d", INT_MAX);
+	is(strcmp(buf, buf2), 0, "Correct construction from INT_MAX.");
+	is(decimal_to_int(&s), INT_MAX, "Simple conversion back to int INT_MAX");
+
+	ret = decimal_from_int(&s, INT_MIN);
+	decimal_to_string(&s, buf);
+	sprintf(buf2, "%d", INT_MIN);
+	is(strcmp(buf, buf2), 0, "Correct construction from INT_MIN.");
+	is(decimal_to_int(&s), INT_MIN, "Simple conversion bact to int INT_MIN");
+
+	char *up1 = "2.5";
+	char *down1 = "2.49";
+	decimal_from_string(&s, up1);
+	is(decimal_to_int(&s), 3, ".5 Rounds up");
+	decimal_from_string(&s, down1);
+	is(decimal_to_int(&s), 2, ".49 Rounds down");
+
+	char *l = "1.23456789123456789123456789123456789123";
+	ret = decimal_from_string(&s, l);
+	isnt(ret, NULL, "Precision too high. Rounding happens.");
+	ok(decimal_precision(&s) == 38 && decimal_scale(&s) == 37 , "Construction is correct.");
+	char *ll = "123456789123456789123456789123456789123";
+	ret = decimal_from_string(&s, ll);
+	is(ret, NULL, "Precision too high and scale = 0. Cannot round");
+
+	/* 38 digits. */
+	char *long_str = "0.0000000000000000000000000000000000001";
+	ret = decimal_from_string(&s, long_str);
+	isnt(ret, NULL, "Construncting the smallest possible number from string");
+	decimal_to_string(&s, buf);
+	is(strcmp(buf, long_str), 0, "Correct representation of smallest possible number");
+
+	/* Comparsions. */
+	char *max_str = "3.11", *min_str = "3.0999";
+	decimal max, min;
+	decimal_from_string(&max, max_str);
+	decimal_from_string(&min, min_str);
+	is(decimal_compare(&max, &min), 1, "max > min");
+	is(decimal_compare(&min, &max), -1, "min < max");
+	is(decimal_compare(&max, &max), 0, "max == max");
+
+	ret = decimal_from_string(&s, "-3.456");
+	isnt(ret, NULL, "Construction from negative numbers");
+	decimal_to_string(&s, buf);
+	is(strcmp(buf, "-3.456"), 0, "Correct construction for negatives");
+	ret = decimal_abs(&s, &s);
+	isnt(ret, NULL, "Abs");
+	decimal_to_string(&s, buf);
+	is(strcmp(buf, "3.456"), 0, "Correct abs");
+
+	/* Arithmetic ops. */
+	decimal d, check;
+	ret = decimal_from_string(&s, b);
+	ret = decimal_from_string(&d, "1.25");
+	sprintf(buf2, "%.9f", 1.25 + 2.718281828);
+	ret = decimal_add(&d, &d, &s);
+	isnt(ret, NULL, "Simple addition");
+	decimal_to_string(&d, buf);
+	is(strcmp(buf, buf2), 0, "Simple addition is correct");
+
+	ret = decimal_sub(&d, &d, &s);
+	isnt(ret, NULL, "Simple subtraction");
+	decimal_from_string(&check, "1.25");
+	is(decimal_compare(&d, &check), 0, "Simple subtraction is correct");
+
+	decimal_from_int(&s, 4);
+	ret = decimal_mul(&s, &s, &d);
+	isnt(ret, NULL, "Simple multiplication");
+	decimal_from_string(&check, "5.0");
+	is(decimal_compare(&s, &check), 0 , "Simple multiplication is correct");
+
+	ret = decimal_div(&s, &s, &d);
+	isnt(ret, NULL, "Simple division");
+	decimal_from_string(&check, "4.0");
+	is(decimal_compare(&s, &check), 0, "Simple division is correct");
+
+	/* Math. */
+	ret = decimal_from_string(&s, "40.96");
+	ret = decimal_from_string(&check, "6.4");
+	ret = decimal_sqrt(&s, &s, 2);
+	isnt(ret, NULL, "sqrt");
+	is(decimal_compare(&s, &check), 0, "sqrt is correct");
+
+	ret = decimal_from_string(&s, "40.96");
+	ret = decimal_from_string(&d, "0.5");
+	ret = decimal_pow(&s, &s, &d, 2);
+	isnt(ret, NULL, "pow");
+	is(decimal_compare(&s, &check), 0, "pow is correct");
+
+	ret = decimal_from_string(&s, "3.000");
+	ret = decimal_exp(&d, &s, 4);
+	isnt(ret, NULL, "exp");
+
+	ret = decimal_from_string(&check, "20.09");
+	is(decimal_compare(&d, &check), 0, "exp is correct")
+	ret = decimal_ln(&d, &d, 4);
+	isnt(ret, NULL, "ln");
+	/*
+	 * ln is wrong by 1 unit in last position.
+	 * (3.001 instead of 3.000, 3.0001 instead of 3.0000 and so on)
+	 */
+	decimal_from_string(&s, "3.001");
+	is(decimal_compare(&d, &s), 0, "ln is correct");
+
+				      /* 10^3.5 */
+	ret = decimal_from_string(&s, "3162.27766");
+	ret = decimal_log10(&d, &s, 2);
+	isnt(ret, NULL, "log10");
+	ret = decimal_from_string(&check, "3.5");
+	is(decimal_compare(&d, &check), 0, "log10 is correct");
+
+	/* Advanced test. */
+	/* 38 digits. */
+	char *bignum = "33.333333333333333333333333333333333333";
+	char *test =   "133.33333333333333333333333333333333333";
+	ret = decimal_from_string(&s, bignum);
+	ret = decimal_from_int(&d, 4);
+	ret = decimal_mul(&s, &s, &d);
+	isnt(ret, NULL, "Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits");
+	ret = decimal_from_string(&check, test);
+	is(decimal_compare(&s, &check), 0, "Rounding is correct");
+	is(decimal_precision(&s), 38, "Correct precision");
+	is(decimal_scale(&s), 35, "Correct scale");
+
+	char *small = "0.000000000000000000000000001";
+	ret = decimal_from_string(&s, small);
+	ret = decimal_mul(&s, &s, &s);
+	isnt(ret, NULL, "Rounding too small number to zero");
+	ret = decimal_from_int(&check, 0);
+	is(decimal_compare(&s, &check), 0, "Rounding is correct");
+	is(decimal_precision(&s), 38, "Correct precision");
+	is(decimal_scale(&s), 38, "Correct scale");
+
+	decimal_from_string(&s, small);
+	decimal_from_string(&d, "10000000000000000000");
+	ret = decimal_div(&s, &s, &d);
+	isnt(ret, NULL, "Rounding too small number to zero");
+	is(decimal_compare(&s, &check), 0, "Rounding is correct");
+	decimal_to_string(&s, buf);
+	is(decimal_precision(&s), 38, "Correct precision");
+	is(decimal_scale(&s), 38, "Correct scale");
+
+	check_plan();
+}
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
new file mode 100644
index 000000000..02bee118a
--- /dev/null
+++ b/test/unit/decimal.result
@@ -0,0 +1,51 @@
+1..50
+ok 1 - Basic construction from string.
+ok 2 - Correct construction and to_string conversion.
+ok 3 - Correct construction from INT_MAX.
+ok 4 - Simple conversion back to int INT_MAX
+ok 5 - Correct construction from INT_MIN.
+ok 6 - Simple conversion bact to int INT_MIN
+ok 7 - .5 Rounds up
+ok 8 - .49 Rounds down
+ok 9 - Precision too high. Rounding happens.
+ok 10 - Construction is correct.
+ok 11 - Precision too high and scale = 0. Cannot round
+ok 12 - Construncting the smallest possible number from string
+ok 13 - Correct representation of smallest possible number
+ok 14 - max > min
+ok 15 - min < max
+ok 16 - max == max
+ok 17 - Construction from negative numbers
+ok 18 - Correct construction for negatives
+ok 19 - Abs
+ok 20 - Correct abs
+ok 21 - Simple addition
+ok 22 - Simple addition is correct
+ok 23 - Simple subtraction
+ok 24 - Simple subtraction is correct
+ok 25 - Simple multiplication
+ok 26 - Simple multiplication is correct
+ok 27 - Simple division
+ok 28 - Simple division is correct
+ok 29 - sqrt
+ok 30 - sqrt is correct
+ok 31 - pow
+ok 32 - pow is correct
+ok 33 - exp
+ok 34 - exp is correct
+ok 35 - ln
+ok 36 - ln is correct
+ok 37 - log10
+ok 38 - log10 is correct
+ok 39 - Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits
+ok 40 - Rounding is correct
+ok 41 - Correct precision
+ok 42 - Correct scale
+ok 43 - Rounding too small number to zero
+ok 44 - Rounding is correct
+ok 45 - Correct precision
+ok 46 - Correct scale
+ok 47 - Rounding too small number to zero
+ok 48 - Rounding is correct
+ok 49 - Correct precision
+ok 50 - Correct scale
-- 
2.20.1 (Apple Git-117)

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

* [PATCH v3 3/4] lib: update msgpuck library
  2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko
  2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko
  2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko
@ 2019-06-04 16:04 ` Serge Petrenko
  2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko
  3 siblings, 0 replies; 12+ messages in thread
From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw)
  To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko

Provide msgpack ext type handling.

Needed for #692
---
 src/lib/msgpuck          |   2 +-
 test/unit/msgpack.result | 480 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 468 insertions(+), 14 deletions(-)

diff --git a/src/lib/msgpuck b/src/lib/msgpuck
index 22f3fc901..8ae606a16 160000
--- a/src/lib/msgpuck
+++ b/src/lib/msgpuck
@@ -1 +1 @@
-Subproject commit 22f3fc901baf13fc9e8563e6fe0242b9f352114b
+Subproject commit 8ae606a1636dd89b2d61b154e5a1db03dce91657
diff --git a/test/unit/msgpack.result b/test/unit/msgpack.result
index 385e7da0e..02c0b05fd 100644
--- a/test/unit/msgpack.result
+++ b/test/unit/msgpack.result
@@ -1,4 +1,4 @@
-1..20
+1..22
     1..135
     # *** test_uints ***
     # uint 0U
@@ -620,6 +620,206 @@ ok 7 - subtests
     ok 78 - mp_encode(0xffffffffU) == "\xc6\xff\xff\xff\xff"
     # *** test_binls: done ***
 ok 8 - subtests
+    1..168
+    # *** test_extls ***
+    # extl 0x01U
+    ok 1 - mp_check_extl(0x01U) == 0
+    ok 2 - mp_decode(mp_encode(0x01U)) == 0x01U
+    ok 3 - len(mp_encode_extl(0x01U)
+    ok 4 - len(mp_decode_extl(0x01U))
+    ok 5 - mp_sizeof_extl(0x01U)
+    ok 6 - mp_encode(0x01U) == "\xd4\x00"
+    # extl 0x02U
+    ok 7 - mp_check_extl(0x02U) == 0
+    ok 8 - mp_decode(mp_encode(0x02U)) == 0x02U
+    ok 9 - len(mp_encode_extl(0x02U)
+    ok 10 - len(mp_decode_extl(0x02U))
+    ok 11 - mp_sizeof_extl(0x02U)
+    ok 12 - mp_encode(0x02U) == "\xd5\x00"
+    # extl 0x04U
+    ok 13 - mp_check_extl(0x04U) == 0
+    ok 14 - mp_decode(mp_encode(0x04U)) == 0x04U
+    ok 15 - len(mp_encode_extl(0x04U)
+    ok 16 - len(mp_decode_extl(0x04U))
+    ok 17 - mp_sizeof_extl(0x04U)
+    ok 18 - mp_encode(0x04U) == "\xd6\x00"
+    # extl 0x08U
+    ok 19 - mp_check_extl(0x08U) == 0
+    ok 20 - mp_decode(mp_encode(0x08U)) == 0x08U
+    ok 21 - len(mp_encode_extl(0x08U)
+    ok 22 - len(mp_decode_extl(0x08U))
+    ok 23 - mp_sizeof_extl(0x08U)
+    ok 24 - mp_encode(0x08U) == "\xd7\x00"
+    # extl 0x10U
+    ok 25 - mp_check_extl(0x10U) == 0
+    ok 26 - mp_decode(mp_encode(0x10U)) == 0x10U
+    ok 27 - len(mp_encode_extl(0x10U)
+    ok 28 - len(mp_decode_extl(0x10U))
+    ok 29 - mp_sizeof_extl(0x10U)
+    ok 30 - mp_encode(0x10U) == "\xd8\x00"
+    # extl 0x11U
+    ok 31 - mp_check_extl(0x11U) == 0
+    ok 32 - mp_decode(mp_encode(0x11U)) == 0x11U
+    ok 33 - len(mp_encode_extl(0x11U)
+    ok 34 - len(mp_decode_extl(0x11U))
+    ok 35 - mp_sizeof_extl(0x11U)
+    ok 36 - mp_encode(0x11U) == "\xc7\x11\x00"
+    # extl 0xfeU
+    ok 37 - mp_check_extl(0xfeU) == 0
+    ok 38 - mp_decode(mp_encode(0xfeU)) == 0xfeU
+    ok 39 - len(mp_encode_extl(0xfeU)
+    ok 40 - len(mp_decode_extl(0xfeU))
+    ok 41 - mp_sizeof_extl(0xfeU)
+    ok 42 - mp_encode(0xfeU) == "\xc7\xfe\x00"
+    # extl 0xffU
+    ok 43 - mp_check_extl(0xffU) == 0
+    ok 44 - mp_decode(mp_encode(0xffU)) == 0xffU
+    ok 45 - len(mp_encode_extl(0xffU)
+    ok 46 - len(mp_decode_extl(0xffU))
+    ok 47 - mp_sizeof_extl(0xffU)
+    ok 48 - mp_encode(0xffU) == "\xc7\xff\x00"
+    # extl 0x00U
+    ok 49 - mp_check_extl(0x00U) == 0
+    ok 50 - mp_decode(mp_encode(0x00U)) == 0x00U
+    ok 51 - len(mp_encode_extl(0x00U)
+    ok 52 - len(mp_decode_extl(0x00U))
+    ok 53 - mp_sizeof_extl(0x00U)
+    ok 54 - mp_encode(0x00U) == "\xc7\x00\x00"
+    # extl 0x03U
+    ok 55 - mp_check_extl(0x03U) == 0
+    ok 56 - mp_decode(mp_encode(0x03U)) == 0x03U
+    ok 57 - len(mp_encode_extl(0x03U)
+    ok 58 - len(mp_decode_extl(0x03U))
+    ok 59 - mp_sizeof_extl(0x03U)
+    ok 60 - mp_encode(0x03U) == "\xc7\x03\x00"
+    # extl 0x05U
+    ok 61 - mp_check_extl(0x05U) == 0
+    ok 62 - mp_decode(mp_encode(0x05U)) == 0x05U
+    ok 63 - len(mp_encode_extl(0x05U)
+    ok 64 - len(mp_decode_extl(0x05U))
+    ok 65 - mp_sizeof_extl(0x05U)
+    ok 66 - mp_encode(0x05U) == "\xc7\x05\x00"
+    # extl 0x06U
+    ok 67 - mp_check_extl(0x06U) == 0
+    ok 68 - mp_decode(mp_encode(0x06U)) == 0x06U
+    ok 69 - len(mp_encode_extl(0x06U)
+    ok 70 - len(mp_decode_extl(0x06U))
+    ok 71 - mp_sizeof_extl(0x06U)
+    ok 72 - mp_encode(0x06U) == "\xc7\x06\x00"
+    # extl 0x07U
+    ok 73 - mp_check_extl(0x07U) == 0
+    ok 74 - mp_decode(mp_encode(0x07U)) == 0x07U
+    ok 75 - len(mp_encode_extl(0x07U)
+    ok 76 - len(mp_decode_extl(0x07U))
+    ok 77 - mp_sizeof_extl(0x07U)
+    ok 78 - mp_encode(0x07U) == "\xc7\x07\x00"
+    # extl 0x09U
+    ok 79 - mp_check_extl(0x09U) == 0
+    ok 80 - mp_decode(mp_encode(0x09U)) == 0x09U
+    ok 81 - len(mp_encode_extl(0x09U)
+    ok 82 - len(mp_decode_extl(0x09U))
+    ok 83 - mp_sizeof_extl(0x09U)
+    ok 84 - mp_encode(0x09U) == "\xc7\x09\x00"
+    # extl 0x0aU
+    ok 85 - mp_check_extl(0x0aU) == 0
+    ok 86 - mp_decode(mp_encode(0x0aU)) == 0x0aU
+    ok 87 - len(mp_encode_extl(0x0aU)
+    ok 88 - len(mp_decode_extl(0x0aU))
+    ok 89 - mp_sizeof_extl(0x0aU)
+    ok 90 - mp_encode(0x0aU) == "\xc7\x0a\x00"
+    # extl 0x0bU
+    ok 91 - mp_check_extl(0x0bU) == 0
+    ok 92 - mp_decode(mp_encode(0x0bU)) == 0x0bU
+    ok 93 - len(mp_encode_extl(0x0bU)
+    ok 94 - len(mp_decode_extl(0x0bU))
+    ok 95 - mp_sizeof_extl(0x0bU)
+    ok 96 - mp_encode(0x0bU) == "\xc7\x0b\x00"
+    # extl 0x0cU
+    ok 97 - mp_check_extl(0x0cU) == 0
+    ok 98 - mp_decode(mp_encode(0x0cU)) == 0x0cU
+    ok 99 - len(mp_encode_extl(0x0cU)
+    ok 100 - len(mp_decode_extl(0x0cU))
+    ok 101 - mp_sizeof_extl(0x0cU)
+    ok 102 - mp_encode(0x0cU) == "\xc7\x0c\x00"
+    # extl 0x0dU
+    ok 103 - mp_check_extl(0x0dU) == 0
+    ok 104 - mp_decode(mp_encode(0x0dU)) == 0x0dU
+    ok 105 - len(mp_encode_extl(0x0dU)
+    ok 106 - len(mp_decode_extl(0x0dU))
+    ok 107 - mp_sizeof_extl(0x0dU)
+    ok 108 - mp_encode(0x0dU) == "\xc7\x0d\x00"
+    # extl 0x0eU
+    ok 109 - mp_check_extl(0x0eU) == 0
+    ok 110 - mp_decode(mp_encode(0x0eU)) == 0x0eU
+    ok 111 - len(mp_encode_extl(0x0eU)
+    ok 112 - len(mp_decode_extl(0x0eU))
+    ok 113 - mp_sizeof_extl(0x0eU)
+    ok 114 - mp_encode(0x0eU) == "\xc7\x0e\x00"
+    # extl 0x0fU
+    ok 115 - mp_check_extl(0x0fU) == 0
+    ok 116 - mp_decode(mp_encode(0x0fU)) == 0x0fU
+    ok 117 - len(mp_encode_extl(0x0fU)
+    ok 118 - len(mp_decode_extl(0x0fU))
+    ok 119 - mp_sizeof_extl(0x0fU)
+    ok 120 - mp_encode(0x0fU) == "\xc7\x0f\x00"
+    # extl 0x0100U
+    ok 121 - mp_check_extl(0x0100U) == 0
+    ok 122 - mp_decode(mp_encode(0x0100U)) == 0x0100U
+    ok 123 - len(mp_encode_extl(0x0100U)
+    ok 124 - len(mp_decode_extl(0x0100U))
+    ok 125 - mp_sizeof_extl(0x0100U)
+    ok 126 - mp_encode(0x0100U) == "\xc8\x01\x00\x00"
+    # extl 0x0101U
+    ok 127 - mp_check_extl(0x0101U) == 0
+    ok 128 - mp_decode(mp_encode(0x0101U)) == 0x0101U
+    ok 129 - len(mp_encode_extl(0x0101U)
+    ok 130 - len(mp_decode_extl(0x0101U))
+    ok 131 - mp_sizeof_extl(0x0101U)
+    ok 132 - mp_encode(0x0101U) == "\xc8\x01\x01\x00"
+    # extl 0xfffeU
+    ok 133 - mp_check_extl(0xfffeU) == 0
+    ok 134 - mp_decode(mp_encode(0xfffeU)) == 0xfffeU
+    ok 135 - len(mp_encode_extl(0xfffeU)
+    ok 136 - len(mp_decode_extl(0xfffeU))
+    ok 137 - mp_sizeof_extl(0xfffeU)
+    ok 138 - mp_encode(0xfffeU) == "\xc8\xff\xfe\x00"
+    # extl 0xffffU
+    ok 139 - mp_check_extl(0xffffU) == 0
+    ok 140 - mp_decode(mp_encode(0xffffU)) == 0xffffU
+    ok 141 - len(mp_encode_extl(0xffffU)
+    ok 142 - len(mp_decode_extl(0xffffU))
+    ok 143 - mp_sizeof_extl(0xffffU)
+    ok 144 - mp_encode(0xffffU) == "\xc8\xff\xff\x00"
+    # extl 0x00010000U
+    ok 145 - mp_check_extl(0x00010000U) == 0
+    ok 146 - mp_decode(mp_encode(0x00010000U)) == 0x00010000U
+    ok 147 - len(mp_encode_extl(0x00010000U)
+    ok 148 - len(mp_decode_extl(0x00010000U))
+    ok 149 - mp_sizeof_extl(0x00010000U)
+    ok 150 - mp_encode(0x00010000U) == "\xc9\x00\x01\x00\x00\x00"
+    # extl 0x00010001U
+    ok 151 - mp_check_extl(0x00010001U) == 0
+    ok 152 - mp_decode(mp_encode(0x00010001U)) == 0x00010001U
+    ok 153 - len(mp_encode_extl(0x00010001U)
+    ok 154 - len(mp_decode_extl(0x00010001U))
+    ok 155 - mp_sizeof_extl(0x00010001U)
+    ok 156 - mp_encode(0x00010001U) == "\xc9\x00\x01\x00\x01\x00"
+    # extl 0xfffffffeU
+    ok 157 - mp_check_extl(0xfffffffeU) == 0
+    ok 158 - mp_decode(mp_encode(0xfffffffeU)) == 0xfffffffeU
+    ok 159 - len(mp_encode_extl(0xfffffffeU)
+    ok 160 - len(mp_decode_extl(0xfffffffeU))
+    ok 161 - mp_sizeof_extl(0xfffffffeU)
+    ok 162 - mp_encode(0xfffffffeU) == "\xc9\xff\xff\xff\xfe\x00"
+    # extl 0xffffffffU
+    ok 163 - mp_check_extl(0xffffffffU) == 0
+    ok 164 - mp_decode(mp_encode(0xffffffffU)) == 0xffffffffU
+    ok 165 - len(mp_encode_extl(0xffffffffU)
+    ok 166 - len(mp_decode_extl(0xffffffffU))
+    ok 167 - mp_sizeof_extl(0xffffffffU)
+    ok 168 - mp_encode(0xffffffffU) == "\xc9\xff\xff\xff\xff\x00"
+    # *** test_extls: done ***
+ok 9 - subtests
     1..96
     # *** test_strs ***
     # str len=0x01
@@ -731,7 +931,7 @@ ok 8 - subtests
     ok 95 - mp_sizeof_str(0x10001)
     ok 96 - mp_encode_str(x, 0x10001) == x
     # *** test_strs: done ***
-ok 9 - subtests
+ok 10 - subtests
     1..96
     # *** test_bins ***
     # bin len=0x01
@@ -843,7 +1043,261 @@ ok 9 - subtests
     ok 95 - mp_sizeof_bin(0x10001)
     ok 96 - mp_encode_bin(x, 0x10001) == x
     # *** test_bins: done ***
-ok 10 - subtests
+ok 11 - subtests
+    1..225
+    # *** test_exts ***
+    # ext len=0x01
+    ok 1 - len(mp_decode_ext(x, 1))
+    ok 2 - type(mp_decode_ext(x))
+    ok 3 - len(mp_decode_strbin(x, 1))
+    ok 4 - mp_check_ext(mp_encode_ext(x, 0x01))
+    ok 5 - len(mp_decode_ext(x, 0x01)
+    ok 6 - len(mp_next_ext(x, 0x01)
+    ok 7 - len(mp_check_ext(x, 0x01)
+    ok 8 - mp_sizeof_ext(0x01)
+    ok 9 - mp_encode_ext(x, 0x01) == x
+    # ext len=0x02
+    ok 10 - len(mp_decode_ext(x, 2))
+    ok 11 - type(mp_decode_ext(x))
+    ok 12 - len(mp_decode_strbin(x, 2))
+    ok 13 - mp_check_ext(mp_encode_ext(x, 0x02))
+    ok 14 - len(mp_decode_ext(x, 0x02)
+    ok 15 - len(mp_next_ext(x, 0x02)
+    ok 16 - len(mp_check_ext(x, 0x02)
+    ok 17 - mp_sizeof_ext(0x02)
+    ok 18 - mp_encode_ext(x, 0x02) == x
+    # ext len=0x03
+    ok 19 - len(mp_decode_ext(x, 3))
+    ok 20 - type(mp_decode_ext(x))
+    ok 21 - len(mp_decode_strbin(x, 3))
+    ok 22 - mp_check_ext(mp_encode_ext(x, 0x03))
+    ok 23 - len(mp_decode_ext(x, 0x03)
+    ok 24 - len(mp_next_ext(x, 0x03)
+    ok 25 - len(mp_check_ext(x, 0x03)
+    ok 26 - mp_sizeof_ext(0x03)
+    ok 27 - mp_encode_ext(x, 0x03) == x
+    # ext len=0x04
+    ok 28 - len(mp_decode_ext(x, 4))
+    ok 29 - type(mp_decode_ext(x))
+    ok 30 - len(mp_decode_strbin(x, 4))
+    ok 31 - mp_check_ext(mp_encode_ext(x, 0x04))
+    ok 32 - len(mp_decode_ext(x, 0x04)
+    ok 33 - len(mp_next_ext(x, 0x04)
+    ok 34 - len(mp_check_ext(x, 0x04)
+    ok 35 - mp_sizeof_ext(0x04)
+    ok 36 - mp_encode_ext(x, 0x04) == x
+    # ext len=0x05
+    ok 37 - len(mp_decode_ext(x, 5))
+    ok 38 - type(mp_decode_ext(x))
+    ok 39 - len(mp_decode_strbin(x, 5))
+    ok 40 - mp_check_ext(mp_encode_ext(x, 0x05))
+    ok 41 - len(mp_decode_ext(x, 0x05)
+    ok 42 - len(mp_next_ext(x, 0x05)
+    ok 43 - len(mp_check_ext(x, 0x05)
+    ok 44 - mp_sizeof_ext(0x05)
+    ok 45 - mp_encode_ext(x, 0x05) == x
+    # ext len=0x06
+    ok 46 - len(mp_decode_ext(x, 6))
+    ok 47 - type(mp_decode_ext(x))
+    ok 48 - len(mp_decode_strbin(x, 6))
+    ok 49 - mp_check_ext(mp_encode_ext(x, 0x06))
+    ok 50 - len(mp_decode_ext(x, 0x06)
+    ok 51 - len(mp_next_ext(x, 0x06)
+    ok 52 - len(mp_check_ext(x, 0x06)
+    ok 53 - mp_sizeof_ext(0x06)
+    ok 54 - mp_encode_ext(x, 0x06) == x
+    # ext len=0x07
+    ok 55 - len(mp_decode_ext(x, 7))
+    ok 56 - type(mp_decode_ext(x))
+    ok 57 - len(mp_decode_strbin(x, 7))
+    ok 58 - mp_check_ext(mp_encode_ext(x, 0x07))
+    ok 59 - len(mp_decode_ext(x, 0x07)
+    ok 60 - len(mp_next_ext(x, 0x07)
+    ok 61 - len(mp_check_ext(x, 0x07)
+    ok 62 - mp_sizeof_ext(0x07)
+    ok 63 - mp_encode_ext(x, 0x07) == x
+    # ext len=0x08
+    ok 64 - len(mp_decode_ext(x, 8))
+    ok 65 - type(mp_decode_ext(x))
+    ok 66 - len(mp_decode_strbin(x, 8))
+    ok 67 - mp_check_ext(mp_encode_ext(x, 0x08))
+    ok 68 - len(mp_decode_ext(x, 0x08)
+    ok 69 - len(mp_next_ext(x, 0x08)
+    ok 70 - len(mp_check_ext(x, 0x08)
+    ok 71 - mp_sizeof_ext(0x08)
+    ok 72 - mp_encode_ext(x, 0x08) == x
+    # ext len=0x09
+    ok 73 - len(mp_decode_ext(x, 9))
+    ok 74 - type(mp_decode_ext(x))
+    ok 75 - len(mp_decode_strbin(x, 9))
+    ok 76 - mp_check_ext(mp_encode_ext(x, 0x09))
+    ok 77 - len(mp_decode_ext(x, 0x09)
+    ok 78 - len(mp_next_ext(x, 0x09)
+    ok 79 - len(mp_check_ext(x, 0x09)
+    ok 80 - mp_sizeof_ext(0x09)
+    ok 81 - mp_encode_ext(x, 0x09) == x
+    # ext len=0x0a
+    ok 82 - len(mp_decode_ext(x, 10))
+    ok 83 - type(mp_decode_ext(x))
+    ok 84 - len(mp_decode_strbin(x, 10))
+    ok 85 - mp_check_ext(mp_encode_ext(x, 0x0a))
+    ok 86 - len(mp_decode_ext(x, 0x0a)
+    ok 87 - len(mp_next_ext(x, 0x0a)
+    ok 88 - len(mp_check_ext(x, 0x0a)
+    ok 89 - mp_sizeof_ext(0x0a)
+    ok 90 - mp_encode_ext(x, 0x0a) == x
+    # ext len=0x0b
+    ok 91 - len(mp_decode_ext(x, 11))
+    ok 92 - type(mp_decode_ext(x))
+    ok 93 - len(mp_decode_strbin(x, 11))
+    ok 94 - mp_check_ext(mp_encode_ext(x, 0x0b))
+    ok 95 - len(mp_decode_ext(x, 0x0b)
+    ok 96 - len(mp_next_ext(x, 0x0b)
+    ok 97 - len(mp_check_ext(x, 0x0b)
+    ok 98 - mp_sizeof_ext(0x0b)
+    ok 99 - mp_encode_ext(x, 0x0b) == x
+    # ext len=0x0c
+    ok 100 - len(mp_decode_ext(x, 12))
+    ok 101 - type(mp_decode_ext(x))
+    ok 102 - len(mp_decode_strbin(x, 12))
+    ok 103 - mp_check_ext(mp_encode_ext(x, 0x0c))
+    ok 104 - len(mp_decode_ext(x, 0x0c)
+    ok 105 - len(mp_next_ext(x, 0x0c)
+    ok 106 - len(mp_check_ext(x, 0x0c)
+    ok 107 - mp_sizeof_ext(0x0c)
+    ok 108 - mp_encode_ext(x, 0x0c) == x
+    # ext len=0x0d
+    ok 109 - len(mp_decode_ext(x, 13))
+    ok 110 - type(mp_decode_ext(x))
+    ok 111 - len(mp_decode_strbin(x, 13))
+    ok 112 - mp_check_ext(mp_encode_ext(x, 0x0d))
+    ok 113 - len(mp_decode_ext(x, 0x0d)
+    ok 114 - len(mp_next_ext(x, 0x0d)
+    ok 115 - len(mp_check_ext(x, 0x0d)
+    ok 116 - mp_sizeof_ext(0x0d)
+    ok 117 - mp_encode_ext(x, 0x0d) == x
+    # ext len=0x0e
+    ok 118 - len(mp_decode_ext(x, 14))
+    ok 119 - type(mp_decode_ext(x))
+    ok 120 - len(mp_decode_strbin(x, 14))
+    ok 121 - mp_check_ext(mp_encode_ext(x, 0x0e))
+    ok 122 - len(mp_decode_ext(x, 0x0e)
+    ok 123 - len(mp_next_ext(x, 0x0e)
+    ok 124 - len(mp_check_ext(x, 0x0e)
+    ok 125 - mp_sizeof_ext(0x0e)
+    ok 126 - mp_encode_ext(x, 0x0e) == x
+    # ext len=0x0f
+    ok 127 - len(mp_decode_ext(x, 15))
+    ok 128 - type(mp_decode_ext(x))
+    ok 129 - len(mp_decode_strbin(x, 15))
+    ok 130 - mp_check_ext(mp_encode_ext(x, 0x0f))
+    ok 131 - len(mp_decode_ext(x, 0x0f)
+    ok 132 - len(mp_next_ext(x, 0x0f)
+    ok 133 - len(mp_check_ext(x, 0x0f)
+    ok 134 - mp_sizeof_ext(0x0f)
+    ok 135 - mp_encode_ext(x, 0x0f) == x
+    # ext len=0x10
+    ok 136 - len(mp_decode_ext(x, 16))
+    ok 137 - type(mp_decode_ext(x))
+    ok 138 - len(mp_decode_strbin(x, 16))
+    ok 139 - mp_check_ext(mp_encode_ext(x, 0x10))
+    ok 140 - len(mp_decode_ext(x, 0x10)
+    ok 141 - len(mp_next_ext(x, 0x10)
+    ok 142 - len(mp_check_ext(x, 0x10)
+    ok 143 - mp_sizeof_ext(0x10)
+    ok 144 - mp_encode_ext(x, 0x10) == x
+    # ext len=0x11
+    ok 145 - len(mp_decode_ext(x, 17))
+    ok 146 - type(mp_decode_ext(x))
+    ok 147 - len(mp_decode_strbin(x, 17))
+    ok 148 - mp_check_ext(mp_encode_ext(x, 0x11))
+    ok 149 - len(mp_decode_ext(x, 0x11)
+    ok 150 - len(mp_next_ext(x, 0x11)
+    ok 151 - len(mp_check_ext(x, 0x11)
+    ok 152 - mp_sizeof_ext(0x11)
+    ok 153 - mp_encode_ext(x, 0x11) == x
+    # ext len=0xfe
+    ok 154 - len(mp_decode_ext(x, 254))
+    ok 155 - type(mp_decode_ext(x))
+    ok 156 - len(mp_decode_strbin(x, 254))
+    ok 157 - mp_check_ext(mp_encode_ext(x, 0xfe))
+    ok 158 - len(mp_decode_ext(x, 0xfe)
+    ok 159 - len(mp_next_ext(x, 0xfe)
+    ok 160 - len(mp_check_ext(x, 0xfe)
+    ok 161 - mp_sizeof_ext(0xfe)
+    ok 162 - mp_encode_ext(x, 0xfe) == x
+    # ext len=0xff
+    ok 163 - len(mp_decode_ext(x, 255))
+    ok 164 - type(mp_decode_ext(x))
+    ok 165 - len(mp_decode_strbin(x, 255))
+    ok 166 - mp_check_ext(mp_encode_ext(x, 0xff))
+    ok 167 - len(mp_decode_ext(x, 0xff)
+    ok 168 - len(mp_next_ext(x, 0xff)
+    ok 169 - len(mp_check_ext(x, 0xff)
+    ok 170 - mp_sizeof_ext(0xff)
+    ok 171 - mp_encode_ext(x, 0xff) == x
+    # ext len=0x0100
+    ok 172 - len(mp_decode_ext(x, 256))
+    ok 173 - type(mp_decode_ext(x))
+    ok 174 - len(mp_decode_strbin(x, 256))
+    ok 175 - mp_check_ext(mp_encode_ext(x, 0x0100))
+    ok 176 - len(mp_decode_ext(x, 0x0100)
+    ok 177 - len(mp_next_ext(x, 0x0100)
+    ok 178 - len(mp_check_ext(x, 0x0100)
+    ok 179 - mp_sizeof_ext(0x0100)
+    ok 180 - mp_encode_ext(x, 0x0100) == x
+    # ext len=0x0101
+    ok 181 - len(mp_decode_ext(x, 257))
+    ok 182 - type(mp_decode_ext(x))
+    ok 183 - len(mp_decode_strbin(x, 257))
+    ok 184 - mp_check_ext(mp_encode_ext(x, 0x0101))
+    ok 185 - len(mp_decode_ext(x, 0x0101)
+    ok 186 - len(mp_next_ext(x, 0x0101)
+    ok 187 - len(mp_check_ext(x, 0x0101)
+    ok 188 - mp_sizeof_ext(0x0101)
+    ok 189 - mp_encode_ext(x, 0x0101) == x
+    # ext len=0xfffe
+    ok 190 - len(mp_decode_ext(x, 65534))
+    ok 191 - type(mp_decode_ext(x))
+    ok 192 - len(mp_decode_strbin(x, 65534))
+    ok 193 - mp_check_ext(mp_encode_ext(x, 0xfffe))
+    ok 194 - len(mp_decode_ext(x, 0xfffe)
+    ok 195 - len(mp_next_ext(x, 0xfffe)
+    ok 196 - len(mp_check_ext(x, 0xfffe)
+    ok 197 - mp_sizeof_ext(0xfffe)
+    ok 198 - mp_encode_ext(x, 0xfffe) == x
+    # ext len=0xffff
+    ok 199 - len(mp_decode_ext(x, 65535))
+    ok 200 - type(mp_decode_ext(x))
+    ok 201 - len(mp_decode_strbin(x, 65535))
+    ok 202 - mp_check_ext(mp_encode_ext(x, 0xffff))
+    ok 203 - len(mp_decode_ext(x, 0xffff)
+    ok 204 - len(mp_next_ext(x, 0xffff)
+    ok 205 - len(mp_check_ext(x, 0xffff)
+    ok 206 - mp_sizeof_ext(0xffff)
+    ok 207 - mp_encode_ext(x, 0xffff) == x
+    # ext len=0x00010000
+    ok 208 - len(mp_decode_ext(x, 65536))
+    ok 209 - type(mp_decode_ext(x))
+    ok 210 - len(mp_decode_strbin(x, 65536))
+    ok 211 - mp_check_ext(mp_encode_ext(x, 0x00010000))
+    ok 212 - len(mp_decode_ext(x, 0x00010000)
+    ok 213 - len(mp_next_ext(x, 0x00010000)
+    ok 214 - len(mp_check_ext(x, 0x00010000)
+    ok 215 - mp_sizeof_ext(0x00010000)
+    ok 216 - mp_encode_ext(x, 0x00010000) == x
+    # ext len=0x00010001
+    ok 217 - len(mp_decode_ext(x, 65537))
+    ok 218 - type(mp_decode_ext(x))
+    ok 219 - len(mp_decode_strbin(x, 65537))
+    ok 220 - mp_check_ext(mp_encode_ext(x, 0x00010001))
+    ok 221 - len(mp_decode_ext(x, 0x00010001)
+    ok 222 - len(mp_next_ext(x, 0x00010001)
+    ok 223 - len(mp_check_ext(x, 0x00010001)
+    ok 224 - mp_sizeof_ext(0x00010001)
+    ok 225 - mp_encode_ext(x, 0x00010001) == x
+    # *** test_exts: done ***
+ok 12 - subtests
     1..54
     # *** test_arrays ***
     # array 0
@@ -910,7 +1364,7 @@ ok 10 - subtests
     ok 53 - mp_sizeof_array(0xffffffffU)
     ok 54 - mp_encode(0xffffffffU) == "\xdd\xff\xff\xff\xff"
     # *** test_arrays: done ***
-ok 11 - subtests
+ok 13 - subtests
     1..54
     # *** test_maps ***
     # map 0
@@ -977,7 +1431,7 @@ ok 11 - subtests
     ok 53 - mp_sizeof_map(0xffffffffU)
     ok 54 - mp_encode(0xffffffffU) == "\xdf\xff\xff\xff\xff"
     # *** test_maps: done ***
-ok 12 - subtests
+ok 14 - subtests
     1..52
     # *** test_next_on_arrays ***
     # next/check on array(0)
@@ -1046,7 +1500,7 @@ ok 12 - subtests
     ok 51 - len(mp_check(array 65537)) == 65542
     ok 52 - len(mp_next(array 65537)) == 65542
     # *** test_next_on_arrays: done ***
-ok 13 - subtests
+ok 15 - subtests
     1..52
     # *** test_next_on_maps ***
     # next/check on map(0)
@@ -1115,7 +1569,7 @@ ok 13 - subtests
     ok 51 - len(mp_check(map 65537)) == 131079
     ok 52 - len(mp_next(map 65537)) == 131079
     # *** test_next_on_maps: done ***
-ok 14 - subtests
+ok 16 - subtests
     1..227
     # *** test_compare_uints ***
     ok 1 - mp_compare_uint(0, 0) == 0
@@ -1346,7 +1800,7 @@ ok 14 - subtests
     ok 226 - mp_compare_uint(18446744073709551615, 18446744073709551614) > 0
     ok 227 - mp_compare_uint(18446744073709551615, 18446744073709551615) == 0
     # *** test_compare_uints: done ***
-ok 15 - subtests
+ok 17 - subtests
     1..282
     # *** test_format ***
     ok 1 - Test type on step 0
@@ -1632,7 +2086,7 @@ ok 15 - subtests
     ok 281 - return value on step 70
     ok 282 - buffer overflow on step 70
     # *** test_format: done ***
-ok 16 - subtests
+ok 18 - subtests
     1..12
     # *** test_mp_print ***
     ok 1 - mp_snprint return value
@@ -1648,7 +2102,7 @@ ok 16 - subtests
     ok 11 - mp_snprint max nesting depth return value
     ok 12 - mp_snprint max nesting depth result
     # *** test_mp_print: done ***
-ok 17 - subtests
+ok 19 - subtests
     1..65
     # *** test_mp_check ***
     ok 1 - invalid fixmap 1
@@ -1717,7 +2171,7 @@ ok 17 - subtests
     ok 64 - invalid map32 2
     ok 65 - invalid map32 3
     # *** test_mp_check: done ***
-ok 18 - subtests
+ok 20 - subtests
     1..96
     # *** test_numbers ***
     ok 1 - mp_read_int32(mp_encode_uint(123)) check success
@@ -1817,7 +2271,7 @@ ok 18 - subtests
     ok 95 - mp_read_double(mp_encode_strl(100)) check fail
     ok 96 - mp_read_double(mp_encode_strl(100)) check pos unchanged
     # *** test_numbers: done ***
-ok 19 - subtests
+ok 21 - subtests
     1..4
     # *** test_overflow ***
     ok 1 - mp_check array overflow
@@ -1825,4 +2279,4 @@ ok 19 - subtests
     ok 3 - mp_check str overflow
     ok 4 - mp_check bin overflow
     # *** test_overflow: done ***
-ok 20 - subtests
+ok 22 - subtests
-- 
2.20.1 (Apple Git-117)

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

* [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support
  2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko
                   ` (2 preceding siblings ...)
  2019-06-04 16:04 ` [PATCH v3 3/4] lib: update msgpuck library Serge Petrenko
@ 2019-06-04 16:04 ` Serge Petrenko
  2019-06-05  8:20   ` Георгий Кириченко
  2019-06-05 12:40   ` Vladimir Davydov
  3 siblings, 2 replies; 12+ messages in thread
From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw)
  To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko

Add methods to encode/decode decimals as msgpack.

Part of #692
---
 cmake/BuildDecNumber.cmake   |   1 +
 src/lib/core/decimal.c       |  64 +++++++++++
 src/lib/core/decimal.h       |  38 +++++++
 src/lib/core/mp_user_types.h |  38 +++++++
 test/unit/decimal.c          |  73 ++++++++++++-
 test/unit/decimal.result     | 204 ++++++++++++++++++++++++++++++++++-
 6 files changed, 416 insertions(+), 2 deletions(-)
 create mode 100644 src/lib/core/mp_user_types.h

diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake
index 80942fe05..abc6c64c4 100644
--- a/cmake/BuildDecNumber.cmake
+++ b/cmake/BuildDecNumber.cmake
@@ -4,6 +4,7 @@ macro(decnumber_build)
     set(decnumber_src
 	${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c
 	${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c
+	${PROJECT_SOURCE_DIR}/third_party/decNumber/decPacked.c
     )
 
     add_library(decNumber STATIC ${decnumber_src})
diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 12250da95..7b3b1d8c9 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -30,7 +30,10 @@
  */
 
 #include "decimal.h"
+#include "mp_user_types.h"
+#include "src/lib/msgpuck/msgpuck.h"
 #include "third_party/decNumber/decContext.h"
+#include "third_party/decNumber/decPacked.h"
 #include <stdlib.h>
 #include <assert.h>
 
@@ -291,3 +294,64 @@ decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision)
 
 	return decimal_finalize(res, &op_context);
 }
+
+static inline uint32_t
+decimal_len(decimal *dec)
+{
+	       /* 1 + ceil((digits + 1) / 2) */
+	return 2 + dec->digits / 2;
+}
+
+uint32_t
+mp_sizeof_decimal(decimal *dec) {
+	uint32_t l = decimal_len(dec);
+	return mp_sizeof_extl(l) + l;
+}
+
+char *
+mp_encode_decimal(char *data, decimal *dec)
+{
+	uint32_t len = decimal_len(dec);
+	data = mp_encode_extl(data, MP_DECIMAL, len);
+	data = mp_store_u8(data, decimal_scale(dec));
+	len--;
+	int32_t scale;
+	char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec);
+	assert(tmp == data);
+	assert(scale == (int32_t)decimal_scale(dec));
+	data += len;
+	return data;
+}
+
+decimal *
+mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len)
+{
+	int32_t scale = mp_load_u8(data);
+	len--;
+	decimal *res = decPackedToNumber((uint8_t *)*data, len, &scale, dec);
+	if (res)
+		*data += len;
+	else
+		(*data)--;
+	return res;
+}
+
+decimal *
+mp_decode_decimal(const char **data, decimal *dec)
+{
+	int8_t type;
+	uint32_t len;
+
+	if (mp_typeof(**data) != MP_EXT)
+		return NULL;
+	const char *const svp = *data;
+	len = mp_decode_extl(data, &type);
+
+	if (type != MP_DECIMAL || len == 0)
+		return NULL;
+	decimal *res = mp_decode_decimal_raw(data, dec, len);
+	if (!res) {
+		*data = svp;
+	}
+	return res;
+}
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
index f2812f500..cfcc98073 100644
--- a/src/lib/core/decimal.h
+++ b/src/lib/core/decimal.h
@@ -156,4 +156,42 @@ decimal_exp(decimal *res, const decimal *lhs, uint32_t precision);
 decimal *
 decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);
 
+
+/**
+ * Return length of message pack encoded decimal with extension header.
+ * Equivalent to mp_sizeof_ext(decimal representation length)
+ */
+uint32_t
+mp_sizeof_decimal(decimal *dec);
+
+/**
+ * Encode a decimal value to msgpack.
+ *
+ * @return data + mp_sizeof_decimal(dec)
+ */
+char *
+mp_encode_decimal(char *data, decimal *dec);
+
+/**
+ * Decode a decimal value as though msgpack
+ * ext header was already decoded.
+ *
+ * \post *data = *data + value representation length
+ * @return NULL if value encoding is incorrect
+ *	    dec  otherwise.
+ */
+decimal *
+mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len);
+
+/**
+ * Decode a decimal encoded as msgpack with ext header.
+ *
+ * \post *data = *data + mp_sizeof_decimal(dec)
+ * @return NULL if mp_typeof(**data) != MP_EXT
+ *		   or value encoding is incorrect
+ *	   dec  otherwise.
+ */
+decimal *
+mp_decode_decimal(const char **data, decimal *dec);
+
 #endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
diff --git a/src/lib/core/mp_user_types.h b/src/lib/core/mp_user_types.h
new file mode 100644
index 000000000..d5694a386
--- /dev/null
+++ b/src/lib/core/mp_user_types.h
@@ -0,0 +1,38 @@
+#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.
+ */
+
+enum mp_user_type {
+    MP_DECIMAL = 0
+};
+
+#endif /* TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED */
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index adfbead91..1f4c9f385 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -1,12 +1,81 @@
 #include "unit.h"
 #include "decimal.h"
+#include "msgpuck.h"
+#include "mp_user_types.h"
+
 #include <limits.h>
 #include <string.h>
 
+char buf[32];
+char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
+
+#define test_mpdec(_str) ({  \
+	decimal dec;\
+	decimal_from_string(&dec, _str);\
+	uint32_t l1 = mp_sizeof_decimal(&dec);\
+	ok(l1 <= 25 && l1 >= 4, "mp_sizeof_decimal(%s)", _str);\
+	char *b1 = mp_encode_decimal(buf, &dec);\
+	is(b1, buf + l1, "mp_sizeof_decimal(%s) == len(mp_encode_decimal(%s)))", _str, _str);\
+	const char *b2 = buf;\
+	const char *b3 = buf;\
+	decimal d2;\
+	mp_next(&b3);\
+	is(b3, b1, "mp_next(mp_encode(%s))", _str);\
+	mp_decode_decimal(&b2, &d2);\
+	is(b1, b2, "mp_decode(mp_encode(%s)) len", _str);\
+	is(decimal_compare(&dec, &d2), 0, "mp_decode(mp_encode(%s)) value", _str);\
+	is(decimal_scale(&dec), decimal_scale(&d2), "mp_decode(mp_encode(%s)) scale", _str);\
+	decimal_to_string(&d2, buf2);\
+	is(strcmp(buf2, _str), 0, "str(mp_decode(mp_encode())) == %s", _str);\
+	b2 = buf;\
+	int8_t type;\
+	uint32_t l2 = mp_decode_extl(&b2, &type);\
+	is(type, MP_DECIMAL, "mp_ext_type is MP_DECIMAL");\
+	ok(&d2 == mp_decode_decimal_raw(&b2, &d2, l2), "mp_decode_decimal_raw()");\
+	is(decimal_compare(&dec, &d2), 0, "mp_decode_decimal_raw() is correct");\
+	is(b2, buf + l1, "mp_decode_extl + mp_decode_decimal_raw len");\
+})
+
+static int
+test_encode_decode(void)
+{
+	plan(200);
+	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");
+
+	/* Encode an invalid decimal. */
+	char *b = mp_encode_extl(buf, MP_DECIMAL, 2);
+	b = mp_store_u8(b, 1);
+	*b++ = '\xab';
+	*b++ = '\xcd';
+	const char *bb = buf;
+	decimal dec;
+	is(mp_decode_decimal(&bb, &dec), NULL, "decode malformed decimal fails");
+	is (bb, buf, "decode malformed decimal saves buffer position");
+
+	return check_plan();
+}
+
 int
 main(void)
 {
-	plan(50);
+	plan(51);
 
 	char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
 	char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
@@ -164,5 +233,7 @@ main(void)
 	is(decimal_precision(&s), 38, "Correct precision");
 	is(decimal_scale(&s), 38, "Correct scale");
 
+	test_encode_decode();
+
 	check_plan();
 }
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index 02bee118a..da34aa0fd 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -1,4 +1,4 @@
-1..50
+1..51
 ok 1 - Basic construction from string.
 ok 2 - Correct construction and to_string conversion.
 ok 3 - Correct construction from INT_MAX.
@@ -49,3 +49,205 @@ ok 47 - Rounding too small number to zero
 ok 48 - Rounding is correct
 ok 49 - Correct precision
 ok 50 - Correct scale
+    1..200
+    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
+    ok 8 - mp_ext_type is MP_DECIMAL
+    ok 9 - mp_decode_decimal_raw()
+    ok 10 - mp_decode_decimal_raw() is correct
+    ok 11 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 19 - mp_ext_type is MP_DECIMAL
+    ok 20 - mp_decode_decimal_raw()
+    ok 21 - mp_decode_decimal_raw() is correct
+    ok 22 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 30 - mp_ext_type is MP_DECIMAL
+    ok 31 - mp_decode_decimal_raw()
+    ok 32 - mp_decode_decimal_raw() is correct
+    ok 33 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 41 - mp_ext_type is MP_DECIMAL
+    ok 42 - mp_decode_decimal_raw()
+    ok 43 - mp_decode_decimal_raw() is correct
+    ok 44 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 52 - mp_ext_type is MP_DECIMAL
+    ok 53 - mp_decode_decimal_raw()
+    ok 54 - mp_decode_decimal_raw() is correct
+    ok 55 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 63 - mp_ext_type is MP_DECIMAL
+    ok 64 - mp_decode_decimal_raw()
+    ok 65 - mp_decode_decimal_raw() is correct
+    ok 66 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 74 - mp_ext_type is MP_DECIMAL
+    ok 75 - mp_decode_decimal_raw()
+    ok 76 - mp_decode_decimal_raw() is correct
+    ok 77 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 85 - mp_ext_type is MP_DECIMAL
+    ok 86 - mp_decode_decimal_raw()
+    ok 87 - mp_decode_decimal_raw() is correct
+    ok 88 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 96 - mp_ext_type is MP_DECIMAL
+    ok 97 - mp_decode_decimal_raw()
+    ok 98 - mp_decode_decimal_raw() is correct
+    ok 99 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 107 - mp_ext_type is MP_DECIMAL
+    ok 108 - mp_decode_decimal_raw()
+    ok 109 - mp_decode_decimal_raw() is correct
+    ok 110 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 118 - mp_ext_type is MP_DECIMAL
+    ok 119 - mp_decode_decimal_raw()
+    ok 120 - mp_decode_decimal_raw() is correct
+    ok 121 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 129 - mp_ext_type is MP_DECIMAL
+    ok 130 - mp_decode_decimal_raw()
+    ok 131 - mp_decode_decimal_raw() is correct
+    ok 132 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 140 - mp_ext_type is MP_DECIMAL
+    ok 141 - mp_decode_decimal_raw()
+    ok 142 - mp_decode_decimal_raw() is correct
+    ok 143 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 151 - mp_ext_type is MP_DECIMAL
+    ok 152 - mp_decode_decimal_raw()
+    ok 153 - mp_decode_decimal_raw() is correct
+    ok 154 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 162 - mp_ext_type is MP_DECIMAL
+    ok 163 - mp_decode_decimal_raw()
+    ok 164 - mp_decode_decimal_raw() is correct
+    ok 165 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 173 - mp_ext_type is MP_DECIMAL
+    ok 174 - mp_decode_decimal_raw()
+    ok 175 - mp_decode_decimal_raw() is correct
+    ok 176 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 184 - mp_ext_type is MP_DECIMAL
+    ok 185 - mp_decode_decimal_raw()
+    ok 186 - mp_decode_decimal_raw() is correct
+    ok 187 - mp_decode_extl + mp_decode_decimal_raw 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
+    ok 195 - mp_ext_type is MP_DECIMAL
+    ok 196 - mp_decode_decimal_raw()
+    ok 197 - mp_decode_decimal_raw() is correct
+    ok 198 - mp_decode_extl + mp_decode_decimal_raw len
+    ok 199 - decode malformed decimal fails
+    ok 200 - decode malformed decimal saves buffer position
+ok 51 - subtests
-- 
2.20.1 (Apple Git-117)

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

* Re: [PATCH v3 1/4] third-party: add decNumber library
  2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko
@ 2019-06-05  8:04   ` Георгий Кириченко
  0 siblings, 0 replies; 12+ messages in thread
From: Георгий Кириченко @ 2019-06-05  8:04 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: vdavydov.dev, tarantool-patches, kostja

[-- Attachment #1: Type: text/plain, Size: 907 bytes --]

LGTM

On Tuesday, June 4, 2019 7:04:16 PM MSK Serge Petrenko wrote:
> We will use it for decimal implementation in tarantool
> 
> Part of #692
> ---
>  .gitmodules           | 3 +++
>  third_party/decNumber | 1 +
>  2 files changed, 4 insertions(+)
>  create mode 160000 third_party/decNumber
> 
> diff --git a/.gitmodules b/.gitmodules
> index e3aa7d79e..9f52c3fc1 100644
> --- a/.gitmodules
> +++ b/.gitmodules
> @@ -31,3 +31,6 @@
>  	path = third_party/luarocks
>  	url = https://github.com/tarantool/luarocks.git
>  	branch = tarantool-1.7
> +[submodule "third_party/decNumber"]
> +	path = third_party/decNumber
> +	url = https://github.com/tarantool/decNumber.git
> diff --git a/third_party/decNumber b/third_party/decNumber
> new file mode 160000
> index 000000000..04e31ac65
> --- /dev/null
> +++ b/third_party/decNumber
> @@ -0,0 +1 @@
> +Subproject commit 04e31ac65efbfb426c7fc299e736ea4e591b41a9


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool
  2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko
@ 2019-06-05  8:11   ` Георгий Кириченко
  2019-06-05 10:03     ` Serge Petrenko
  2019-06-05 12:19   ` Vladimir Davydov
  1 sibling, 1 reply; 12+ messages in thread
From: Георгий Кириченко @ 2019-06-05  8:11 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: vdavydov.dev, tarantool-patches, kostja

[-- Attachment #1: Type: text/plain, Size: 27549 bytes --]

Overall it looks great but please check travis has an error:
/tarantool/src/lib/core/decimal.c:319:8: error: unused variable ‘tmp’ [-
Werror=unused-variable]
char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec);

Perhaps, it was caused by assert elimination because of the Release-type 
build.

On Tuesday, June 4, 2019 7:04:17 PM MSK Serge Petrenko wrote:
> Add fixed-point decimal type to tarantool core.
> Adapt decNumber floating-point decimal library for the purpose, write a
> small wrapper and add unit tests.
> 
> A new decimal type is an alias for decNumber numbers from the decNumber
> library.
> Arithmetic operations (+, -, *, /) and some mathematic functions
> (ln, log10, exp, pow, sqrt) are available.
> 
> We introduce a single context for all the arithmetic operations
> on decimals, which enforces both number precision and scale to be
> in range [0, 38].
> 
> Every mathematic function has precision as one of its parameters, and
> the results are rounded to fit the desired precision.
> 
> Part of #692
> ---
>  CMakeLists.txt              |   7 +
>  cmake/BuildDecNumber.cmake  |  13 ++
>  src/CMakeLists.txt          |   1 +
>  src/lib/core/CMakeLists.txt |   3 +-
>  src/lib/core/decimal.c      | 293 ++++++++++++++++++++++++++++++++++++
>  src/lib/core/decimal.h      | 159 +++++++++++++++++++
>  test/unit/CMakeLists.txt    |   2 +
>  test/unit/decimal.c         | 168 +++++++++++++++++++++
>  test/unit/decimal.result    |  51 +++++++
>  9 files changed, 696 insertions(+), 1 deletion(-)
>  create mode 100644 cmake/BuildDecNumber.cmake
>  create mode 100644 src/lib/core/decimal.c
>  create mode 100644 src/lib/core/decimal.h
>  create mode 100644 test/unit/decimal.c
>  create mode 100644 test/unit/decimal.result
> 
> diff --git a/CMakeLists.txt b/CMakeLists.txt
> index 7658fc6c9..bfb15effb 100644
> --- a/CMakeLists.txt
> +++ b/CMakeLists.txt
> @@ -431,6 +431,13 @@ else()
>      find_package(MsgPuck)
>  endif()
> 
> +#
> +# decNumber
> +#
> +
> +include(BuildDecNumber)
> +decnumber_build()
> +
>  #
>  # LibYAML
>  #
> diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake
> new file mode 100644
> index 000000000..80942fe05
> --- /dev/null
> +++ b/cmake/BuildDecNumber.cmake
> @@ -0,0 +1,13 @@
> +#
> +# A macro to build the bundled decNumber lisbrary.
> +macro(decnumber_build)
> +    set(decnumber_src
> +	${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c
> +	${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c
> +    )
> +
> +    add_library(decNumber STATIC ${decnumber_src})
> +
> +    set(DECNUMBER_INCLUDE_DIR ${PROJECT_BINARY_DIR}/third_party/decNumber)
> +    unset(decnumber_src)
> +endmacro(decnumber_build)
> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
> index 183cc04ef..9d196755f 100644
> --- a/src/CMakeLists.txt
> +++ b/src/CMakeLists.txt
> @@ -13,6 +13,7 @@ include_directories(${MSGPUCK_INCLUDE_DIRS})
>  include_directories(${CURL_INCLUDE_DIRS})
>  include_directories(${ICU_INCLUDE_DIRS})
>  include_directories(${ICONV_INCLUDE_DIRS})
> +include_directories(${DECNUMBER_INCLUDE_DIR})
> 
>  set(LIBUTIL_FREEBSD_SRC ${CMAKE_SOURCE_DIR}/third_party/libutil_freebsd)
>  include_directories(${LIBUTIL_FREEBSD_SRC})
> diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
> index eb10b11c3..66e430a25 100644
> --- a/src/lib/core/CMakeLists.txt
> +++ b/src/lib/core/CMakeLists.txt
> @@ -26,6 +26,7 @@ set(core_sources
>      trigger.cc
>      mpstream.c
>      port.c
> +    decimal.c
>  )
> 
>  if (TARGET_OS_NETBSD)
> @@ -37,7 +38,7 @@ endif()
> 
>  add_library(core STATIC ${core_sources})
> 
> -target_link_libraries(core salad small uri ${LIBEV_LIBRARIES}
> +target_link_libraries(core salad small uri decNumber ${LIBEV_LIBRARIES}
>                        ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES}
>                        ${MSGPUCK_LIBRARIES})
> 
> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
> new file mode 100644
> index 000000000..12250da95
> --- /dev/null
> +++ b/src/lib/core/decimal.c
> @@ -0,0 +1,293 @@
> +/*
> + * 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 "third_party/decNumber/decContext.h"
> +#include <stdlib.h>
> +#include <assert.h>
> +
> +#define MAX(a, b) ((a) > (b) ? (a) : (b))
> +#define MIN(a, b) ((a) > (b) ? (b) : (a))
> +
> +/** A single context for all the arithmetic operations. */
> +static decContext decimal_context = {
> +	/* Maximum precision during operations. */
> +	TARANTOOL_MAX_DECIMAL_DIGITS,
> +	/*
> +	 * Maximum decimal lagarithm of the number.
> +	 * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS
> +	 */
> +	TARANTOOL_MAX_DECIMAL_DIGITS - 1,
> +	/*
> +	 * Minimal adjusted exponent. The smallest absolute value will be
> +	 * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) =
> +	 * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale =
> +	 * TARANTOOL_MAX_DECIMAL_DIGITS
> +	 */
> +	-1,
> +	/* Rounding mode: .5 rounds away from 0. */
> +	DEC_ROUND_HALF_UP,
> +	/* Turn off signalling for failed operations. */
> +	0,
> +	/* Status holding occured events. Initially empty. */
> +	0,
> +	/* Turn off exponent clamping. */
> +	0
> +};
> +
> +/**
> + * Return operation status and clear it for future checks.
> + *
> + * @return 0 if ok, bitwise or of decNumber errors, if any.
> + */
> +static inline uint32_t
> +decimal_get_op_status(decContext *context)
> +{
> +	uint32_t status = decContextGetStatus(context);
> +	decContextZeroStatus(&decimal_context);
> +	/*
> +	 * Clear warnings. Every value less than 0.1 is
> +	 * subnormal, DEC_Inexact and DEC_Rounded result
> +	 * from rounding. DEC_Inexact with DEC_Subnormal
> +	 * together result in DEC_Underflow. DEC_Clamped
> +	 * happens after underflow if rounding to zero.
> +	 */
> +	return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | 
DEC_Underflow |
> +				    DEC_Subnormal | DEC_Clamped);
> +}
> +
> +/**
> + * A finalizer for all the operations.
> + * Check the operation context status and empty it.
> + *
> + * @return NULL if finalization failed.
> + *         result pointer otherwise.
> + */
> +static inline decimal *
> +decimal_finalize(decimal *res, decContext *context)
> +{
> +	uint32_t status = decimal_get_op_status(context);
> +	if (status || ! decNumberIsFinite(res)) {
> +		return NULL;
> +	}
> +	return res;
> +}
> +
> +uint8_t decimal_precision(const decimal *dec) {
> +	return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) :
> +				    dec->digits + dec->exponent;
> +}
> +
> +uint8_t decimal_scale(const decimal *dec) {
> +	return dec->exponent < 0 ? -dec->exponent : 0;
> +}
> +
> +decimal *
> +decimal_zero(decimal *dec)
> +{
> +	decNumberZero(dec);
> +	return dec;
> +}
> +
> +decimal *
> +decimal_from_string(decimal *dec, const char *str)
> +{
> +	decNumberFromString(dec, str, &decimal_context);
> +	return decimal_finalize(dec, &decimal_context);
> +}
> +
> +decimal *
> +decimal_from_int(decimal *dec, int32_t num)
> +{
> +	decNumberFromInt32(dec, num);
> +	return decimal_finalize(dec, &decimal_context);
> +}
> +
> +decimal *
> +decimal_from_uint(decimal *dec, uint32_t num)
> +{
> +	decNumberFromUInt32(dec, num);
> +	return decimal_finalize(dec, &decimal_context);
> +}
> +
> +char *
> +decimal_to_string(const decimal *dec, char *buf)
> +{
> +	return decNumberToString(dec, buf);
> +}
> +
> +int32_t
> +decimal_to_int(const decimal *dec)
> +{
> +	decNumber res;
> +	decNumberToIntegralValue(&res, dec, &decimal_context);
> +	return decNumberToInt32(&res, &decimal_context);
> +}
> +
> +uint32_t
> +decimal_to_uint(const decimal *dec)
> +{
> +	decNumber res;
> +	decNumberToIntegralValue(&res, dec, &decimal_context);
> +	return decNumberToUInt32(&res, &decimal_context);
> +}
> +
> +int
> +decimal_compare(const decimal *lhs, const decimal *rhs)
> +{
> +	decNumber res;
> +	decNumberCompare(&res, lhs, rhs, &decimal_context);
> +	return decNumberToInt32(&res, &decimal_context);
> +}
> +
> +decimal *
> +decimal_abs(decimal *res, const decimal *dec)
> +{
> +	decNumberAbs(res, dec, &decimal_context);
> +	return res;
> +}
> +
> +decimal *
> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs)
> +{
> +	decNumberAdd(res, lhs, rhs, &decimal_context);
> +
> +	return decimal_finalize(res, &decimal_context);
> +}
> +
> +decimal *
> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs)
> +{
> +	decNumberSubtract(res, lhs, rhs, &decimal_context);
> +
> +	return decimal_finalize(res, &decimal_context);
> +}
> +
> +decimal *
> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs)
> +{
> +	decNumberMultiply(res, lhs, rhs, &decimal_context);
> +
> +	return decimal_finalize(res, &decimal_context);
> +}
> +
> +decimal *
> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs)
> +{
> +	decNumberDivide(res, lhs, rhs, &decimal_context);
> +
> +	return decimal_finalize(res, &decimal_context);
> +}
> +
> +decimal *
> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision)
> +{
> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
> +		return NULL;
> +
> +	/* A separate context to enforce desired precision. */
> +	static decContext op_context;
> +	op_context.digits = precision;
> +	op_context.emax = op_context.digits - 1;
> +	op_context.emin = -1;
> +
> +	decNumberLog10(res, lhs, &op_context);
> +
> +	return decimal_finalize(res, &op_context);
> +}
> +
> +decimal *
> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision)
> +{
> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
> +		return NULL;
> +
> +	/* A separate context to enforce desired precision. */
> +	static decContext op_context;
> +	op_context.digits = precision;
> +	op_context.emax = op_context.digits - 1;
> +	op_context.emin = -1;
> +
> +	decNumberLn(res, lhs, &op_context);
> +
> +	return decimal_finalize(res, &op_context);
> +}
> +
> +decimal *
> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t
> precision) +{
> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
> +		return NULL;
> +
> +	/* A separate context to enforce desired precision. */
> +	static decContext op_context;
> +	op_context.digits = precision;
> +	op_context.emax = op_context.digits - 1;
> +	op_context.emin = -1;
> +
> +	decNumberPower(res, lhs, rhs, &op_context);
> +
> +	return decimal_finalize(res, &op_context);
> +}
> +
> +decimal *
> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision)
> +{
> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
> +		return NULL;
> +
> +	/* A separate context to enforce desired precision. */
> +	static decContext op_context;
> +	op_context.digits = precision;
> +	op_context.emax = op_context.digits - 1;
> +	op_context.emin = -1;
> +
> +	decNumberExp(res, lhs, &op_context);
> +
> +	return decimal_finalize(res, &op_context);
> +}
> +
> +decimal *
> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision)
> +{
> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
> +		return NULL;
> +
> +	/* A separate context to enforce desired precision. */
> +	static decContext op_context;
> +	op_context.digits = precision;
> +	op_context.emax = op_context.digits - 1;
> +	op_context.emin = -1;
> +
> +	decNumberSquareRoot(res, lhs, &op_context);
> +
> +	return decimal_finalize(res, &op_context);
> +}
> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
> new file mode 100644
> index 000000000..f2812f500
> --- /dev/null
> +++ b/src/lib/core/decimal.h
> @@ -0,0 +1,159 @@
> +#ifndef TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED
> +#define TARANTOOL_LIB_CORE_DECIMAL_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.
> + */
> +#define TARANTOOL_MAX_DECIMAL_DIGITS 38
> +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS
> +#define DECSTRING_NO_EXPONENT
> +#include "third_party/decNumber/decNumber.h"
> +
> +typedef decNumber decimal;
> +
> +/**
> + * @return decimal precision,
> + * i.e. the amount of decimal digits in
> + * its representation.
> + */
> +uint8_t
> +decimal_precision(const decimal *dec);
> +
> +/**
> + * @return decimal scale,
> + * i.e. the number of decimal digits after
> + * the decimal separator.
> + */
> +uint8_t
> +decimal_scale(const decimal *dec);
> +
> +/**
> + * Initialize a zero decimal number.
> + */
> +decimal *
> +decimal_zero(decimal *dec);
> +
> +/**
> + * Initialize a decimal with a value from the string.
> + *
> + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS,
> + * but has excess digits in fractional part, it will be rounded.
> + *
> + * @return NULL if string is invalid or
> + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS)
> + */
> +decimal *
> +decimal_from_string(decimal *dec, const char *str);
> +
> +/**
> + * Initialize a decimal with an integer value.
> + *
> + * @return NULL if precicion is insufficient to hold
> + * the value or precision/scale are out of bounds.
> +*/
> +decimal *
> +decimal_from_int(decimal *dec, int32_t num);
> +
> +/** @copydoc decimal_from_int */
> +decimal *
> +decimal_from_uint(decimal *dec, uint32_t num);
> +
> +/**
> + * Write the decimal to a string.
> + * A string has to be at least dec->digits + 3 bytes in size.
> + */
> +char *
> +decimal_to_string(const decimal *dec, char *buf);
> +
> +/**
> + * Cast decimal to an integer value. The number will be rounded
> + * if it has a fractional part.
> + */
> +int32_t
> +decimal_to_int(const decimal *dec);
> +
> +/** @copydoc decimal_to_int */
> +uint32_t
> +decimal_to_uint(const decimal *dec);
> +
> +/**
> + * Compare 2 decimal values.
> + * @return -1, lhs < rhs,
> + *	    0, lhs = rhs,
> + *	    1, lhs > rhs
> + */
> +int
> +decimal_compare(const decimal *lhs, const decimal *rhs);
> +
> +/**
> + * res is set to the absolute value of dec
> + * decimal_abs(&a, &a) is allowed.
> + */
> +decimal *
> +decimal_abs(decimal *res, const decimal *dec);
> +
> +/*
> + * Arithmetic ops: add, subtract, multiply and divide.
> + * Return result pointer on success, NULL on an error (overflow).
> + */
> +
> +decimal *
> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +decimal *
> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +decimal *
> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +decimal *
> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +/*
> + * log10, ln, pow, exp, sqrt.
> + * Calculate the appropriate function and then round the result
> + * to the desired precision.
> + * Return result pointer on success, NULL on an error (overflow).
> + */
> +decimal *
> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision);
> +
> +decimal *
> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision);
> +
> +decimal *
> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t
> precision); +
> +decimal *
> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision);
> +
> +decimal *
> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);
> +
> +#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
> diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
> index 70be6366c..e3de34b16 100644
> --- a/test/unit/CMakeLists.txt
> +++ b/test/unit/CMakeLists.txt
> @@ -68,6 +68,8 @@ add_executable(vclock.test vclock.cc)
>  target_link_libraries(vclock.test vclock unit)
>  add_executable(xrow.test xrow.cc)
>  target_link_libraries(xrow.test xrow unit)
> +add_executable(decimal.test decimal.c)
> +target_link_libraries(decimal.test core unit)
> 
>  add_executable(fiber.test fiber.cc)
>  set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0)
> diff --git a/test/unit/decimal.c b/test/unit/decimal.c
> new file mode 100644
> index 000000000..adfbead91
> --- /dev/null
> +++ b/test/unit/decimal.c
> @@ -0,0 +1,168 @@
> +#include "unit.h"
> +#include "decimal.h"
> +#include <limits.h>
> +#include <string.h>
> +
> +int
> +main(void)
> +{
> +	plan(50);
> +
> +	char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
> +	char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
> +	char *b = "2.718281828";
> +
> +	decimal s;
> +	decimal *ret;
> +
> +	ret = decimal_from_string(&s, b);
> +	isnt(ret, NULL, "Basic construction from string.");
> +	decimal_to_string(&s, buf);
> +	is(strcmp(buf, b), 0, "Correct construction and to_string 
conversion.");
> +
> +	ret = decimal_from_int(&s, INT_MAX);
> +	decimal_to_string(&s, buf);
> +	sprintf(buf2, "%d", INT_MAX);
> +	is(strcmp(buf, buf2), 0, "Correct construction from INT_MAX.");
> +	is(decimal_to_int(&s), INT_MAX, "Simple conversion back to int 
INT_MAX");
> +
> +	ret = decimal_from_int(&s, INT_MIN);
> +	decimal_to_string(&s, buf);
> +	sprintf(buf2, "%d", INT_MIN);
> +	is(strcmp(buf, buf2), 0, "Correct construction from INT_MIN.");
> +	is(decimal_to_int(&s), INT_MIN, "Simple conversion bact to int 
INT_MIN");
> +
> +	char *up1 = "2.5";
> +	char *down1 = "2.49";
> +	decimal_from_string(&s, up1);
> +	is(decimal_to_int(&s), 3, ".5 Rounds up");
> +	decimal_from_string(&s, down1);
> +	is(decimal_to_int(&s), 2, ".49 Rounds down");
> +
> +	char *l = "1.23456789123456789123456789123456789123";
> +	ret = decimal_from_string(&s, l);
> +	isnt(ret, NULL, "Precision too high. Rounding happens.");
> +	ok(decimal_precision(&s) == 38 && decimal_scale(&s) == 37 , 
"Construction
> is correct."); +	char *ll = 
"123456789123456789123456789123456789123";
> +	ret = decimal_from_string(&s, ll);
> +	is(ret, NULL, "Precision too high and scale = 0. Cannot round");
> +
> +	/* 38 digits. */
> +	char *long_str = "0.0000000000000000000000000000000000001";
> +	ret = decimal_from_string(&s, long_str);
> +	isnt(ret, NULL, "Construncting the smallest possible number from 
string");
> +	decimal_to_string(&s, buf);
> +	is(strcmp(buf, long_str), 0, "Correct representation of smallest 
possible
> number"); +
> +	/* Comparsions. */
> +	char *max_str = "3.11", *min_str = "3.0999";
> +	decimal max, min;
> +	decimal_from_string(&max, max_str);
> +	decimal_from_string(&min, min_str);
> +	is(decimal_compare(&max, &min), 1, "max > min");
> +	is(decimal_compare(&min, &max), -1, "min < max");
> +	is(decimal_compare(&max, &max), 0, "max == max");
> +
> +	ret = decimal_from_string(&s, "-3.456");
> +	isnt(ret, NULL, "Construction from negative numbers");
> +	decimal_to_string(&s, buf);
> +	is(strcmp(buf, "-3.456"), 0, "Correct construction for negatives");
> +	ret = decimal_abs(&s, &s);
> +	isnt(ret, NULL, "Abs");
> +	decimal_to_string(&s, buf);
> +	is(strcmp(buf, "3.456"), 0, "Correct abs");
> +
> +	/* Arithmetic ops. */
> +	decimal d, check;
> +	ret = decimal_from_string(&s, b);
> +	ret = decimal_from_string(&d, "1.25");
> +	sprintf(buf2, "%.9f", 1.25 + 2.718281828);
> +	ret = decimal_add(&d, &d, &s);
> +	isnt(ret, NULL, "Simple addition");
> +	decimal_to_string(&d, buf);
> +	is(strcmp(buf, buf2), 0, "Simple addition is correct");
> +
> +	ret = decimal_sub(&d, &d, &s);
> +	isnt(ret, NULL, "Simple subtraction");
> +	decimal_from_string(&check, "1.25");
> +	is(decimal_compare(&d, &check), 0, "Simple subtraction is 
correct");
> +
> +	decimal_from_int(&s, 4);
> +	ret = decimal_mul(&s, &s, &d);
> +	isnt(ret, NULL, "Simple multiplication");
> +	decimal_from_string(&check, "5.0");
> +	is(decimal_compare(&s, &check), 0 , "Simple multiplication is 
correct");
> +
> +	ret = decimal_div(&s, &s, &d);
> +	isnt(ret, NULL, "Simple division");
> +	decimal_from_string(&check, "4.0");
> +	is(decimal_compare(&s, &check), 0, "Simple division is correct");
> +
> +	/* Math. */
> +	ret = decimal_from_string(&s, "40.96");
> +	ret = decimal_from_string(&check, "6.4");
> +	ret = decimal_sqrt(&s, &s, 2);
> +	isnt(ret, NULL, "sqrt");
> +	is(decimal_compare(&s, &check), 0, "sqrt is correct");
> +
> +	ret = decimal_from_string(&s, "40.96");
> +	ret = decimal_from_string(&d, "0.5");
> +	ret = decimal_pow(&s, &s, &d, 2);
> +	isnt(ret, NULL, "pow");
> +	is(decimal_compare(&s, &check), 0, "pow is correct");
> +
> +	ret = decimal_from_string(&s, "3.000");
> +	ret = decimal_exp(&d, &s, 4);
> +	isnt(ret, NULL, "exp");
> +
> +	ret = decimal_from_string(&check, "20.09");
> +	is(decimal_compare(&d, &check), 0, "exp is correct")
> +	ret = decimal_ln(&d, &d, 4);
> +	isnt(ret, NULL, "ln");
> +	/*
> +	 * ln is wrong by 1 unit in last position.
> +	 * (3.001 instead of 3.000, 3.0001 instead of 3.0000 and so on)
> +	 */
> +	decimal_from_string(&s, "3.001");
> +	is(decimal_compare(&d, &s), 0, "ln is correct");
> +
> +				      /* 10^3.5 */
> +	ret = decimal_from_string(&s, "3162.27766");
> +	ret = decimal_log10(&d, &s, 2);
> +	isnt(ret, NULL, "log10");
> +	ret = decimal_from_string(&check, "3.5");
> +	is(decimal_compare(&d, &check), 0, "log10 is correct");
> +
> +	/* Advanced test. */
> +	/* 38 digits. */
> +	char *bignum = "33.333333333333333333333333333333333333";
> +	char *test =   "133.33333333333333333333333333333333333";
> +	ret = decimal_from_string(&s, bignum);
> +	ret = decimal_from_int(&d, 4);
> +	ret = decimal_mul(&s, &s, &d);
> +	isnt(ret, NULL, "Rounding when more than 
TARANTOOL_MAX_DECIMAL_DIGITS
> digits"); +	ret = decimal_from_string(&check, test);
> +	is(decimal_compare(&s, &check), 0, "Rounding is correct");
> +	is(decimal_precision(&s), 38, "Correct precision");
> +	is(decimal_scale(&s), 35, "Correct scale");
> +
> +	char *small = "0.000000000000000000000000001";
> +	ret = decimal_from_string(&s, small);
> +	ret = decimal_mul(&s, &s, &s);
> +	isnt(ret, NULL, "Rounding too small number to zero");
> +	ret = decimal_from_int(&check, 0);
> +	is(decimal_compare(&s, &check), 0, "Rounding is correct");
> +	is(decimal_precision(&s), 38, "Correct precision");
> +	is(decimal_scale(&s), 38, "Correct scale");
> +
> +	decimal_from_string(&s, small);
> +	decimal_from_string(&d, "10000000000000000000");
> +	ret = decimal_div(&s, &s, &d);
> +	isnt(ret, NULL, "Rounding too small number to zero");
> +	is(decimal_compare(&s, &check), 0, "Rounding is correct");
> +	decimal_to_string(&s, buf);
> +	is(decimal_precision(&s), 38, "Correct precision");
> +	is(decimal_scale(&s), 38, "Correct scale");
> +
> +	check_plan();
> +}
> diff --git a/test/unit/decimal.result b/test/unit/decimal.result
> new file mode 100644
> index 000000000..02bee118a
> --- /dev/null
> +++ b/test/unit/decimal.result
> @@ -0,0 +1,51 @@
> +1..50
> +ok 1 - Basic construction from string.
> +ok 2 - Correct construction and to_string conversion.
> +ok 3 - Correct construction from INT_MAX.
> +ok 4 - Simple conversion back to int INT_MAX
> +ok 5 - Correct construction from INT_MIN.
> +ok 6 - Simple conversion bact to int INT_MIN
> +ok 7 - .5 Rounds up
> +ok 8 - .49 Rounds down
> +ok 9 - Precision too high. Rounding happens.
> +ok 10 - Construction is correct.
> +ok 11 - Precision too high and scale = 0. Cannot round
> +ok 12 - Construncting the smallest possible number from string
> +ok 13 - Correct representation of smallest possible number
> +ok 14 - max > min
> +ok 15 - min < max
> +ok 16 - max == max
> +ok 17 - Construction from negative numbers
> +ok 18 - Correct construction for negatives
> +ok 19 - Abs
> +ok 20 - Correct abs
> +ok 21 - Simple addition
> +ok 22 - Simple addition is correct
> +ok 23 - Simple subtraction
> +ok 24 - Simple subtraction is correct
> +ok 25 - Simple multiplication
> +ok 26 - Simple multiplication is correct
> +ok 27 - Simple division
> +ok 28 - Simple division is correct
> +ok 29 - sqrt
> +ok 30 - sqrt is correct
> +ok 31 - pow
> +ok 32 - pow is correct
> +ok 33 - exp
> +ok 34 - exp is correct
> +ok 35 - ln
> +ok 36 - ln is correct
> +ok 37 - log10
> +ok 38 - log10 is correct
> +ok 39 - Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits
> +ok 40 - Rounding is correct
> +ok 41 - Correct precision
> +ok 42 - Correct scale
> +ok 43 - Rounding too small number to zero
> +ok 44 - Rounding is correct
> +ok 45 - Correct precision
> +ok 46 - Correct scale
> +ok 47 - Rounding too small number to zero
> +ok 48 - Rounding is correct
> +ok 49 - Correct precision
> +ok 50 - Correct scale


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support
  2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko
@ 2019-06-05  8:20   ` Георгий Кириченко
  2019-06-05 12:40   ` Vladimir Davydov
  1 sibling, 0 replies; 12+ messages in thread
From: Георгий Кириченко @ 2019-06-05  8:20 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: vdavydov.dev, tarantool-patches, kostja

[-- Attachment #1: Type: text/plain, Size: 22671 bytes --]

LGTM, but I would like to postpone this commit until we would be able to 
process decimals in lua. I'm not sure what would happen if lua faced this in a 
msgpuck/tuple.

Thanks!

On Tuesday, June 4, 2019 7:04:19 PM MSK Serge Petrenko wrote:
> Add methods to encode/decode decimals as msgpack.
> 
> Part of #692
> ---
>  cmake/BuildDecNumber.cmake   |   1 +
>  src/lib/core/decimal.c       |  64 +++++++++++
>  src/lib/core/decimal.h       |  38 +++++++
>  src/lib/core/mp_user_types.h |  38 +++++++
>  test/unit/decimal.c          |  73 ++++++++++++-
>  test/unit/decimal.result     | 204 ++++++++++++++++++++++++++++++++++-
>  6 files changed, 416 insertions(+), 2 deletions(-)
>  create mode 100644 src/lib/core/mp_user_types.h
> 
> diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake
> index 80942fe05..abc6c64c4 100644
> --- a/cmake/BuildDecNumber.cmake
> +++ b/cmake/BuildDecNumber.cmake
> @@ -4,6 +4,7 @@ macro(decnumber_build)
>      set(decnumber_src
>  	${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c
>  	${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c
> +	${PROJECT_SOURCE_DIR}/third_party/decNumber/decPacked.c
>      )
> 
>      add_library(decNumber STATIC ${decnumber_src})
> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
> index 12250da95..7b3b1d8c9 100644
> --- a/src/lib/core/decimal.c
> +++ b/src/lib/core/decimal.c
> @@ -30,7 +30,10 @@
>   */
> 
>  #include "decimal.h"
> +#include "mp_user_types.h"
> +#include "src/lib/msgpuck/msgpuck.h"
>  #include "third_party/decNumber/decContext.h"
> +#include "third_party/decNumber/decPacked.h"
>  #include <stdlib.h>
>  #include <assert.h>
> 
> @@ -291,3 +294,64 @@ decimal_sqrt(decimal *res, const decimal *lhs, uint32_t
> precision)
> 
>  	return decimal_finalize(res, &op_context);
>  }
> +
> +static inline uint32_t
> +decimal_len(decimal *dec)
> +{
> +	       /* 1 + ceil((digits + 1) / 2) */
> +	return 2 + dec->digits / 2;
> +}
> +
> +uint32_t
> +mp_sizeof_decimal(decimal *dec) {
> +	uint32_t l = decimal_len(dec);
> +	return mp_sizeof_extl(l) + l;
> +}
> +
> +char *
> +mp_encode_decimal(char *data, decimal *dec)
> +{
> +	uint32_t len = decimal_len(dec);
> +	data = mp_encode_extl(data, MP_DECIMAL, len);
> +	data = mp_store_u8(data, decimal_scale(dec));
> +	len--;
> +	int32_t scale;
> +	char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, 
&scale,
> dec); +	assert(tmp == data);
> +	assert(scale == (int32_t)decimal_scale(dec));
> +	data += len;
> +	return data;
> +}
> +
> +decimal *
> +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len)
> +{
> +	int32_t scale = mp_load_u8(data);
> +	len--;
> +	decimal *res = decPackedToNumber((uint8_t *)*data, len, &scale, 
dec);
> +	if (res)
> +		*data += len;
> +	else
> +		(*data)--;
> +	return res;
> +}
> +
> +decimal *
> +mp_decode_decimal(const char **data, decimal *dec)
> +{
> +	int8_t type;
> +	uint32_t len;
> +
> +	if (mp_typeof(**data) != MP_EXT)
> +		return NULL;
> +	const char *const svp = *data;
> +	len = mp_decode_extl(data, &type);
> +
> +	if (type != MP_DECIMAL || len == 0)
> +		return NULL;
> +	decimal *res = mp_decode_decimal_raw(data, dec, len);
> +	if (!res) {
> +		*data = svp;
> +	}
> +	return res;
> +}
> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
> index f2812f500..cfcc98073 100644
> --- a/src/lib/core/decimal.h
> +++ b/src/lib/core/decimal.h
> @@ -156,4 +156,42 @@ decimal_exp(decimal *res, const decimal *lhs, uint32_t
> precision); decimal *
>  decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);
> 
> +
> +/**
> + * Return length of message pack encoded decimal with extension header.
> + * Equivalent to mp_sizeof_ext(decimal representation length)
> + */
> +uint32_t
> +mp_sizeof_decimal(decimal *dec);
> +
> +/**
> + * Encode a decimal value to msgpack.
> + *
> + * @return data + mp_sizeof_decimal(dec)
> + */
> +char *
> +mp_encode_decimal(char *data, decimal *dec);
> +
> +/**
> + * Decode a decimal value as though msgpack
> + * ext header was already decoded.
> + *
> + * \post *data = *data + value representation length
> + * @return NULL if value encoding is incorrect
> + *	    dec  otherwise.
> + */
> +decimal *
> +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len);
> +
> +/**
> + * Decode a decimal encoded as msgpack with ext header.
> + *
> + * \post *data = *data + mp_sizeof_decimal(dec)
> + * @return NULL if mp_typeof(**data) != MP_EXT
> + *		   or value encoding is incorrect
> + *	   dec  otherwise.
> + */
> +decimal *
> +mp_decode_decimal(const char **data, decimal *dec);
> +
>  #endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
> diff --git a/src/lib/core/mp_user_types.h b/src/lib/core/mp_user_types.h
> new file mode 100644
> index 000000000..d5694a386
> --- /dev/null
> +++ b/src/lib/core/mp_user_types.h
> @@ -0,0 +1,38 @@
> +#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.
> + */
> +
> +enum mp_user_type {
> +    MP_DECIMAL = 0
> +};
> +
> +#endif /* TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED */
> diff --git a/test/unit/decimal.c b/test/unit/decimal.c
> index adfbead91..1f4c9f385 100644
> --- a/test/unit/decimal.c
> +++ b/test/unit/decimal.c
> @@ -1,12 +1,81 @@
>  #include "unit.h"
>  #include "decimal.h"
> +#include "msgpuck.h"
> +#include "mp_user_types.h"
> +
>  #include <limits.h>
>  #include <string.h>
> 
> +char buf[32];
> +char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
> +
> +#define test_mpdec(_str) ({  \
> +	decimal dec;\
> +	decimal_from_string(&dec, _str);\
> +	uint32_t l1 = mp_sizeof_decimal(&dec);\
> +	ok(l1 <= 25 && l1 >= 4, "mp_sizeof_decimal(%s)", _str);\
> +	char *b1 = mp_encode_decimal(buf, &dec);\
> +	is(b1, buf + l1, "mp_sizeof_decimal(%s) == 
len(mp_encode_decimal(%s)))",
> _str, _str);\ +	const char *b2 = buf;\
> +	const char *b3 = buf;\
> +	decimal d2;\
> +	mp_next(&b3);\
> +	is(b3, b1, "mp_next(mp_encode(%s))", _str);\
> +	mp_decode_decimal(&b2, &d2);\
> +	is(b1, b2, "mp_decode(mp_encode(%s)) len", _str);\
> +	is(decimal_compare(&dec, &d2), 0, "mp_decode(mp_encode(%s)) value",
> _str);\ +	is(decimal_scale(&dec), decimal_scale(&d2),
> "mp_decode(mp_encode(%s)) scale", _str);\ +	decimal_to_string(&d2, 
buf2);\
> +	is(strcmp(buf2, _str), 0, "str(mp_decode(mp_encode())) == %s", 
_str);\
> +	b2 = buf;\
> +	int8_t type;\
> +	uint32_t l2 = mp_decode_extl(&b2, &type);\
> +	is(type, MP_DECIMAL, "mp_ext_type is MP_DECIMAL");\
> +	ok(&d2 == mp_decode_decimal_raw(&b2, &d2, l2),
> "mp_decode_decimal_raw()");\ +	is(decimal_compare(&dec, &d2), 0,
> "mp_decode_decimal_raw() is correct");\ +	is(b2, buf + l1, 
"mp_decode_extl
> + mp_decode_decimal_raw len");\ +})
> +
> +static int
> +test_encode_decode(void)
> +{
> +	plan(200);
> +	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");
> +
> +	/* Encode an invalid decimal. */
> +	char *b = mp_encode_extl(buf, MP_DECIMAL, 2);
> +	b = mp_store_u8(b, 1);
> +	*b++ = '\xab';
> +	*b++ = '\xcd';
> +	const char *bb = buf;
> +	decimal dec;
> +	is(mp_decode_decimal(&bb, &dec), NULL, "decode malformed decimal 
fails");
> +	is (bb, buf, "decode malformed decimal saves buffer position");
> +
> +	return check_plan();
> +}
> +
>  int
>  main(void)
>  {
> -	plan(50);
> +	plan(51);
> 
>  	char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
>  	char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
> @@ -164,5 +233,7 @@ main(void)
>  	is(decimal_precision(&s), 38, "Correct precision");
>  	is(decimal_scale(&s), 38, "Correct scale");
> 
> +	test_encode_decode();
> +
>  	check_plan();
>  }
> diff --git a/test/unit/decimal.result b/test/unit/decimal.result
> index 02bee118a..da34aa0fd 100644
> --- a/test/unit/decimal.result
> +++ b/test/unit/decimal.result
> @@ -1,4 +1,4 @@
> -1..50
> +1..51
>  ok 1 - Basic construction from string.
>  ok 2 - Correct construction and to_string conversion.
>  ok 3 - Correct construction from INT_MAX.
> @@ -49,3 +49,205 @@ ok 47 - Rounding too small number to zero
>  ok 48 - Rounding is correct
>  ok 49 - Correct precision
>  ok 50 - Correct scale
> +    1..200
> +    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
> +    ok 8 - mp_ext_type is MP_DECIMAL
> +    ok 9 - mp_decode_decimal_raw()
> +    ok 10 - mp_decode_decimal_raw() is correct
> +    ok 11 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 19 - mp_ext_type is MP_DECIMAL
> +    ok 20 - mp_decode_decimal_raw()
> +    ok 21 - mp_decode_decimal_raw() is correct
> +    ok 22 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 30 - mp_ext_type is MP_DECIMAL
> +    ok 31 - mp_decode_decimal_raw()
> +    ok 32 - mp_decode_decimal_raw() is correct
> +    ok 33 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 41 - mp_ext_type is MP_DECIMAL
> +    ok 42 - mp_decode_decimal_raw()
> +    ok 43 - mp_decode_decimal_raw() is correct
> +    ok 44 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 52 - mp_ext_type is MP_DECIMAL
> +    ok 53 - mp_decode_decimal_raw()
> +    ok 54 - mp_decode_decimal_raw() is correct
> +    ok 55 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 63 - mp_ext_type is MP_DECIMAL
> +    ok 64 - mp_decode_decimal_raw()
> +    ok 65 - mp_decode_decimal_raw() is correct
> +    ok 66 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 74 - mp_ext_type is MP_DECIMAL
> +    ok 75 - mp_decode_decimal_raw()
> +    ok 76 - mp_decode_decimal_raw() is correct
> +    ok 77 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 85 - mp_ext_type is MP_DECIMAL
> +    ok 86 - mp_decode_decimal_raw()
> +    ok 87 - mp_decode_decimal_raw() is correct
> +    ok 88 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 96 - mp_ext_type is MP_DECIMAL
> +    ok 97 - mp_decode_decimal_raw()
> +    ok 98 - mp_decode_decimal_raw() is correct
> +    ok 99 - mp_decode_extl + mp_decode_decimal_raw 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
> +    ok 107 - mp_ext_type is MP_DECIMAL
> +    ok 108 - mp_decode_decimal_raw()
> +    ok 109 - mp_decode_decimal_raw() is correct
> +    ok 110 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 118 - mp_ext_type is
> MP_DECIMAL
> +    ok 119 - mp_decode_decimal_raw()
> +    ok 120 - mp_decode_decimal_raw() is correct
> +    ok 121 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 129 - mp_ext_type is
> MP_DECIMAL
> +    ok 130 - mp_decode_decimal_raw()
> +    ok 131 - mp_decode_decimal_raw() is correct
> +    ok 132 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 140 - mp_ext_type is
> MP_DECIMAL
> +    ok 141 - mp_decode_decimal_raw()
> +    ok 142 - mp_decode_decimal_raw() is correct
> +    ok 143 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 151 - mp_ext_type is
> MP_DECIMAL
> +    ok 152 - mp_decode_decimal_raw()
> +    ok 153 - mp_decode_decimal_raw() is correct
> +    ok 154 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 162 - mp_ext_type is
> MP_DECIMAL
> +    ok 163 - mp_decode_decimal_raw()
> +    ok 164 - mp_decode_decimal_raw() is correct
> +    ok 165 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 173 - mp_ext_type is
> MP_DECIMAL
> +    ok 174 - mp_decode_decimal_raw()
> +    ok 175 - mp_decode_decimal_raw() is correct
> +    ok 176 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 184 - mp_ext_type is
> MP_DECIMAL
> +    ok 185 - mp_decode_decimal_raw()
> +    ok 186 - mp_decode_decimal_raw() is correct
> +    ok 187 - mp_decode_extl + mp_decode_decimal_raw 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 +    ok 195 - mp_ext_type is
> MP_DECIMAL
> +    ok 196 - mp_decode_decimal_raw()
> +    ok 197 - mp_decode_decimal_raw() is correct
> +    ok 198 - mp_decode_extl + mp_decode_decimal_raw len
> +    ok 199 - decode malformed decimal fails
> +    ok 200 - decode malformed decimal saves buffer position
> +ok 51 - subtests


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool
  2019-06-05  8:11   ` Георгий Кириченко
@ 2019-06-05 10:03     ` Serge Petrenko
  0 siblings, 0 replies; 12+ messages in thread
From: Serge Petrenko @ 2019-06-05 10:03 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: Vladimir Davydov, tarantool-patches, kostja



> 5 июня 2019 г., в 11:11, Георгий Кириченко <georgy@tarantool.org> написал(а):
> 
> Overall it looks great but please check travis has an error:
> /tarantool/src/lib/core/decimal.c:319:8: error: unused variable ‘tmp’ [-
> Werror=unused-variable]
> char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec);
> 
> Perhaps, it was caused by assert elimination because of the Release-type 
> build.

Hi! Thank you for review.
Fixed. mp_encode_decimal() is actually from the fourth patch.

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 7b3b1d8c9..fab3eb866 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -319,6 +319,7 @@ mp_encode_decimal(char *data, decimal *dec)
        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;
 }

> 
> On Tuesday, June 4, 2019 7:04:17 PM MSK Serge Petrenko wrote:
>> Add fixed-point decimal type to tarantool core.
>> Adapt decNumber floating-point decimal library for the purpose, write a
>> small wrapper and add unit tests.
>> 
>> A new decimal type is an alias for decNumber numbers from the decNumber
>> library.
>> Arithmetic operations (+, -, *, /) and some mathematic functions
>> (ln, log10, exp, pow, sqrt) are available.
>> 
>> We introduce a single context for all the arithmetic operations
>> on decimals, which enforces both number precision and scale to be
>> in range [0, 38].
>> 
>> Every mathematic function has precision as one of its parameters, and
>> the results are rounded to fit the desired precision.
>> 
>> Part of #692
>> ---
>> CMakeLists.txt              |   7 +
>> cmake/BuildDecNumber.cmake  |  13 ++
>> src/CMakeLists.txt          |   1 +
>> src/lib/core/CMakeLists.txt |   3 +-
>> src/lib/core/decimal.c      | 293 ++++++++++++++++++++++++++++++++++++
>> src/lib/core/decimal.h      | 159 +++++++++++++++++++
>> test/unit/CMakeLists.txt    |   2 +
>> test/unit/decimal.c         | 168 +++++++++++++++++++++
>> test/unit/decimal.result    |  51 +++++++
>> 9 files changed, 696 insertions(+), 1 deletion(-)
>> create mode 100644 cmake/BuildDecNumber.cmake
>> create mode 100644 src/lib/core/decimal.c
>> create mode 100644 src/lib/core/decimal.h
>> create mode 100644 test/unit/decimal.c
>> create mode 100644 test/unit/decimal.result
>> 
>> diff --git a/CMakeLists.txt b/CMakeLists.txt
>> index 7658fc6c9..bfb15effb 100644
>> --- a/CMakeLists.txt
>> +++ b/CMakeLists.txt
>> @@ -431,6 +431,13 @@ else()
>>     find_package(MsgPuck)
>> endif()
>> 
>> +#
>> +# decNumber
>> +#
>> +
>> +include(BuildDecNumber)
>> +decnumber_build()
>> +
>> #
>> # LibYAML
>> #
>> diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake
>> new file mode 100644
>> index 000000000..80942fe05
>> --- /dev/null
>> +++ b/cmake/BuildDecNumber.cmake
>> @@ -0,0 +1,13 @@
>> +#
>> +# A macro to build the bundled decNumber lisbrary.
>> +macro(decnumber_build)
>> +    set(decnumber_src
>> +	${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c
>> +	${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c
>> +    )
>> +
>> +    add_library(decNumber STATIC ${decnumber_src})
>> +
>> +    set(DECNUMBER_INCLUDE_DIR ${PROJECT_BINARY_DIR}/third_party/decNumber)
>> +    unset(decnumber_src)
>> +endmacro(decnumber_build)
>> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
>> index 183cc04ef..9d196755f 100644
>> --- a/src/CMakeLists.txt
>> +++ b/src/CMakeLists.txt
>> @@ -13,6 +13,7 @@ include_directories(${MSGPUCK_INCLUDE_DIRS})
>> include_directories(${CURL_INCLUDE_DIRS})
>> include_directories(${ICU_INCLUDE_DIRS})
>> include_directories(${ICONV_INCLUDE_DIRS})
>> +include_directories(${DECNUMBER_INCLUDE_DIR})
>> 
>> set(LIBUTIL_FREEBSD_SRC ${CMAKE_SOURCE_DIR}/third_party/libutil_freebsd)
>> include_directories(${LIBUTIL_FREEBSD_SRC})
>> diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
>> index eb10b11c3..66e430a25 100644
>> --- a/src/lib/core/CMakeLists.txt
>> +++ b/src/lib/core/CMakeLists.txt
>> @@ -26,6 +26,7 @@ set(core_sources
>>     trigger.cc
>>     mpstream.c
>>     port.c
>> +    decimal.c
>> )
>> 
>> if (TARGET_OS_NETBSD)
>> @@ -37,7 +38,7 @@ endif()
>> 
>> add_library(core STATIC ${core_sources})
>> 
>> -target_link_libraries(core salad small uri ${LIBEV_LIBRARIES}
>> +target_link_libraries(core salad small uri decNumber ${LIBEV_LIBRARIES}
>>                       ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES}
>>                       ${MSGPUCK_LIBRARIES})
>> 
>> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
>> new file mode 100644
>> index 000000000..12250da95
>> --- /dev/null
>> +++ b/src/lib/core/decimal.c
>> @@ -0,0 +1,293 @@
>> +/*
>> + * 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 "third_party/decNumber/decContext.h"
>> +#include <stdlib.h>
>> +#include <assert.h>
>> +
>> +#define MAX(a, b) ((a) > (b) ? (a) : (b))
>> +#define MIN(a, b) ((a) > (b) ? (b) : (a))
>> +
>> +/** A single context for all the arithmetic operations. */
>> +static decContext decimal_context = {
>> +	/* Maximum precision during operations. */
>> +	TARANTOOL_MAX_DECIMAL_DIGITS,
>> +	/*
>> +	 * Maximum decimal lagarithm of the number.
>> +	 * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS
>> +	 */
>> +	TARANTOOL_MAX_DECIMAL_DIGITS - 1,
>> +	/*
>> +	 * Minimal adjusted exponent. The smallest absolute value will be
>> +	 * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) =
>> +	 * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale =
>> +	 * TARANTOOL_MAX_DECIMAL_DIGITS
>> +	 */
>> +	-1,
>> +	/* Rounding mode: .5 rounds away from 0. */
>> +	DEC_ROUND_HALF_UP,
>> +	/* Turn off signalling for failed operations. */
>> +	0,
>> +	/* Status holding occured events. Initially empty. */
>> +	0,
>> +	/* Turn off exponent clamping. */
>> +	0
>> +};
>> +
>> +/**
>> + * Return operation status and clear it for future checks.
>> + *
>> + * @return 0 if ok, bitwise or of decNumber errors, if any.
>> + */
>> +static inline uint32_t
>> +decimal_get_op_status(decContext *context)
>> +{
>> +	uint32_t status = decContextGetStatus(context);
>> +	decContextZeroStatus(&decimal_context);
>> +	/*
>> +	 * Clear warnings. Every value less than 0.1 is
>> +	 * subnormal, DEC_Inexact and DEC_Rounded result
>> +	 * from rounding. DEC_Inexact with DEC_Subnormal
>> +	 * together result in DEC_Underflow. DEC_Clamped
>> +	 * happens after underflow if rounding to zero.
>> +	 */
>> +	return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | 
> DEC_Underflow |
>> +				    DEC_Subnormal | DEC_Clamped);
>> +}
>> +
>> +/**
>> + * A finalizer for all the operations.
>> + * Check the operation context status and empty it.
>> + *
>> + * @return NULL if finalization failed.
>> + *         result pointer otherwise.
>> + */
>> +static inline decimal *
>> +decimal_finalize(decimal *res, decContext *context)
>> +{
>> +	uint32_t status = decimal_get_op_status(context);
>> +	if (status || ! decNumberIsFinite(res)) {
>> +		return NULL;
>> +	}
>> +	return res;
>> +}
>> +
>> +uint8_t decimal_precision(const decimal *dec) {
>> +	return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) :
>> +				    dec->digits + dec->exponent;
>> +}
>> +
>> +uint8_t decimal_scale(const decimal *dec) {
>> +	return dec->exponent < 0 ? -dec->exponent : 0;
>> +}
>> +
>> +decimal *
>> +decimal_zero(decimal *dec)
>> +{
>> +	decNumberZero(dec);
>> +	return dec;
>> +}
>> +
>> +decimal *
>> +decimal_from_string(decimal *dec, const char *str)
>> +{
>> +	decNumberFromString(dec, str, &decimal_context);
>> +	return decimal_finalize(dec, &decimal_context);
>> +}
>> +
>> +decimal *
>> +decimal_from_int(decimal *dec, int32_t num)
>> +{
>> +	decNumberFromInt32(dec, num);
>> +	return decimal_finalize(dec, &decimal_context);
>> +}
>> +
>> +decimal *
>> +decimal_from_uint(decimal *dec, uint32_t num)
>> +{
>> +	decNumberFromUInt32(dec, num);
>> +	return decimal_finalize(dec, &decimal_context);
>> +}
>> +
>> +char *
>> +decimal_to_string(const decimal *dec, char *buf)
>> +{
>> +	return decNumberToString(dec, buf);
>> +}
>> +
>> +int32_t
>> +decimal_to_int(const decimal *dec)
>> +{
>> +	decNumber res;
>> +	decNumberToIntegralValue(&res, dec, &decimal_context);
>> +	return decNumberToInt32(&res, &decimal_context);
>> +}
>> +
>> +uint32_t
>> +decimal_to_uint(const decimal *dec)
>> +{
>> +	decNumber res;
>> +	decNumberToIntegralValue(&res, dec, &decimal_context);
>> +	return decNumberToUInt32(&res, &decimal_context);
>> +}
>> +
>> +int
>> +decimal_compare(const decimal *lhs, const decimal *rhs)
>> +{
>> +	decNumber res;
>> +	decNumberCompare(&res, lhs, rhs, &decimal_context);
>> +	return decNumberToInt32(&res, &decimal_context);
>> +}
>> +
>> +decimal *
>> +decimal_abs(decimal *res, const decimal *dec)
>> +{
>> +	decNumberAbs(res, dec, &decimal_context);
>> +	return res;
>> +}
>> +
>> +decimal *
>> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs)
>> +{
>> +	decNumberAdd(res, lhs, rhs, &decimal_context);
>> +
>> +	return decimal_finalize(res, &decimal_context);
>> +}
>> +
>> +decimal *
>> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs)
>> +{
>> +	decNumberSubtract(res, lhs, rhs, &decimal_context);
>> +
>> +	return decimal_finalize(res, &decimal_context);
>> +}
>> +
>> +decimal *
>> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs)
>> +{
>> +	decNumberMultiply(res, lhs, rhs, &decimal_context);
>> +
>> +	return decimal_finalize(res, &decimal_context);
>> +}
>> +
>> +decimal *
>> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs)
>> +{
>> +	decNumberDivide(res, lhs, rhs, &decimal_context);
>> +
>> +	return decimal_finalize(res, &decimal_context);
>> +}
>> +
>> +decimal *
>> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision)
>> +{
>> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
>> +		return NULL;
>> +
>> +	/* A separate context to enforce desired precision. */
>> +	static decContext op_context;
>> +	op_context.digits = precision;
>> +	op_context.emax = op_context.digits - 1;
>> +	op_context.emin = -1;
>> +
>> +	decNumberLog10(res, lhs, &op_context);
>> +
>> +	return decimal_finalize(res, &op_context);
>> +}
>> +
>> +decimal *
>> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision)
>> +{
>> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
>> +		return NULL;
>> +
>> +	/* A separate context to enforce desired precision. */
>> +	static decContext op_context;
>> +	op_context.digits = precision;
>> +	op_context.emax = op_context.digits - 1;
>> +	op_context.emin = -1;
>> +
>> +	decNumberLn(res, lhs, &op_context);
>> +
>> +	return decimal_finalize(res, &op_context);
>> +}
>> +
>> +decimal *
>> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t
>> precision) +{
>> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
>> +		return NULL;
>> +
>> +	/* A separate context to enforce desired precision. */
>> +	static decContext op_context;
>> +	op_context.digits = precision;
>> +	op_context.emax = op_context.digits - 1;
>> +	op_context.emin = -1;
>> +
>> +	decNumberPower(res, lhs, rhs, &op_context);
>> +
>> +	return decimal_finalize(res, &op_context);
>> +}
>> +
>> +decimal *
>> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision)
>> +{
>> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
>> +		return NULL;
>> +
>> +	/* A separate context to enforce desired precision. */
>> +	static decContext op_context;
>> +	op_context.digits = precision;
>> +	op_context.emax = op_context.digits - 1;
>> +	op_context.emin = -1;
>> +
>> +	decNumberExp(res, lhs, &op_context);
>> +
>> +	return decimal_finalize(res, &op_context);
>> +}
>> +
>> +decimal *
>> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision)
>> +{
>> +	if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS)
>> +		return NULL;
>> +
>> +	/* A separate context to enforce desired precision. */
>> +	static decContext op_context;
>> +	op_context.digits = precision;
>> +	op_context.emax = op_context.digits - 1;
>> +	op_context.emin = -1;
>> +
>> +	decNumberSquareRoot(res, lhs, &op_context);
>> +
>> +	return decimal_finalize(res, &op_context);
>> +}
>> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
>> new file mode 100644
>> index 000000000..f2812f500
>> --- /dev/null
>> +++ b/src/lib/core/decimal.h
>> @@ -0,0 +1,159 @@
>> +#ifndef TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED
>> +#define TARANTOOL_LIB_CORE_DECIMAL_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.
>> + */
>> +#define TARANTOOL_MAX_DECIMAL_DIGITS 38
>> +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS
>> +#define DECSTRING_NO_EXPONENT
>> +#include "third_party/decNumber/decNumber.h"
>> +
>> +typedef decNumber decimal;
>> +
>> +/**
>> + * @return decimal precision,
>> + * i.e. the amount of decimal digits in
>> + * its representation.
>> + */
>> +uint8_t
>> +decimal_precision(const decimal *dec);
>> +
>> +/**
>> + * @return decimal scale,
>> + * i.e. the number of decimal digits after
>> + * the decimal separator.
>> + */
>> +uint8_t
>> +decimal_scale(const decimal *dec);
>> +
>> +/**
>> + * Initialize a zero decimal number.
>> + */
>> +decimal *
>> +decimal_zero(decimal *dec);
>> +
>> +/**
>> + * Initialize a decimal with a value from the string.
>> + *
>> + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS,
>> + * but has excess digits in fractional part, it will be rounded.
>> + *
>> + * @return NULL if string is invalid or
>> + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS)
>> + */
>> +decimal *
>> +decimal_from_string(decimal *dec, const char *str);
>> +
>> +/**
>> + * Initialize a decimal with an integer value.
>> + *
>> + * @return NULL if precicion is insufficient to hold
>> + * the value or precision/scale are out of bounds.
>> +*/
>> +decimal *
>> +decimal_from_int(decimal *dec, int32_t num);
>> +
>> +/** @copydoc decimal_from_int */
>> +decimal *
>> +decimal_from_uint(decimal *dec, uint32_t num);
>> +
>> +/**
>> + * Write the decimal to a string.
>> + * A string has to be at least dec->digits + 3 bytes in size.
>> + */
>> +char *
>> +decimal_to_string(const decimal *dec, char *buf);
>> +
>> +/**
>> + * Cast decimal to an integer value. The number will be rounded
>> + * if it has a fractional part.
>> + */
>> +int32_t
>> +decimal_to_int(const decimal *dec);
>> +
>> +/** @copydoc decimal_to_int */
>> +uint32_t
>> +decimal_to_uint(const decimal *dec);
>> +
>> +/**
>> + * Compare 2 decimal values.
>> + * @return -1, lhs < rhs,
>> + *	    0, lhs = rhs,
>> + *	    1, lhs > rhs
>> + */
>> +int
>> +decimal_compare(const decimal *lhs, const decimal *rhs);
>> +
>> +/**
>> + * res is set to the absolute value of dec
>> + * decimal_abs(&a, &a) is allowed.
>> + */
>> +decimal *
>> +decimal_abs(decimal *res, const decimal *dec);
>> +
>> +/*
>> + * Arithmetic ops: add, subtract, multiply and divide.
>> + * Return result pointer on success, NULL on an error (overflow).
>> + */
>> +
>> +decimal *
>> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +decimal *
>> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +decimal *
>> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +decimal *
>> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +/*
>> + * log10, ln, pow, exp, sqrt.
>> + * Calculate the appropriate function and then round the result
>> + * to the desired precision.
>> + * Return result pointer on success, NULL on an error (overflow).
>> + */
>> +decimal *
>> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision);
>> +
>> +decimal *
>> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision);
>> +
>> +decimal *
>> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t
>> precision); +
>> +decimal *
>> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision);
>> +
>> +decimal *
>> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);
>> +
>> +#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
>> diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
>> index 70be6366c..e3de34b16 100644
>> --- a/test/unit/CMakeLists.txt
>> +++ b/test/unit/CMakeLists.txt
>> @@ -68,6 +68,8 @@ add_executable(vclock.test vclock.cc)
>> target_link_libraries(vclock.test vclock unit)
>> add_executable(xrow.test xrow.cc)
>> target_link_libraries(xrow.test xrow unit)
>> +add_executable(decimal.test decimal.c)
>> +target_link_libraries(decimal.test core unit)
>> 
>> add_executable(fiber.test fiber.cc)
>> set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0)
>> diff --git a/test/unit/decimal.c b/test/unit/decimal.c
>> new file mode 100644
>> index 000000000..adfbead91
>> --- /dev/null
>> +++ b/test/unit/decimal.c
>> @@ -0,0 +1,168 @@
>> +#include "unit.h"
>> +#include "decimal.h"
>> +#include <limits.h>
>> +#include <string.h>
>> +
>> +int
>> +main(void)
>> +{
>> +	plan(50);
>> +
>> +	char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
>> +	char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
>> +	char *b = "2.718281828";
>> +
>> +	decimal s;
>> +	decimal *ret;
>> +
>> +	ret = decimal_from_string(&s, b);
>> +	isnt(ret, NULL, "Basic construction from string.");
>> +	decimal_to_string(&s, buf);
>> +	is(strcmp(buf, b), 0, "Correct construction and to_string 
> conversion.");
>> +
>> +	ret = decimal_from_int(&s, INT_MAX);
>> +	decimal_to_string(&s, buf);
>> +	sprintf(buf2, "%d", INT_MAX);
>> +	is(strcmp(buf, buf2), 0, "Correct construction from INT_MAX.");
>> +	is(decimal_to_int(&s), INT_MAX, "Simple conversion back to int 
> INT_MAX");
>> +
>> +	ret = decimal_from_int(&s, INT_MIN);
>> +	decimal_to_string(&s, buf);
>> +	sprintf(buf2, "%d", INT_MIN);
>> +	is(strcmp(buf, buf2), 0, "Correct construction from INT_MIN.");
>> +	is(decimal_to_int(&s), INT_MIN, "Simple conversion bact to int 
> INT_MIN");
>> +
>> +	char *up1 = "2.5";
>> +	char *down1 = "2.49";
>> +	decimal_from_string(&s, up1);
>> +	is(decimal_to_int(&s), 3, ".5 Rounds up");
>> +	decimal_from_string(&s, down1);
>> +	is(decimal_to_int(&s), 2, ".49 Rounds down");
>> +
>> +	char *l = "1.23456789123456789123456789123456789123";
>> +	ret = decimal_from_string(&s, l);
>> +	isnt(ret, NULL, "Precision too high. Rounding happens.");
>> +	ok(decimal_precision(&s) == 38 && decimal_scale(&s) == 37 , 
> "Construction
>> is correct."); +	char *ll = 
> "123456789123456789123456789123456789123";
>> +	ret = decimal_from_string(&s, ll);
>> +	is(ret, NULL, "Precision too high and scale = 0. Cannot round");
>> +
>> +	/* 38 digits. */
>> +	char *long_str = "0.0000000000000000000000000000000000001";
>> +	ret = decimal_from_string(&s, long_str);
>> +	isnt(ret, NULL, "Construncting the smallest possible number from 
> string");
>> +	decimal_to_string(&s, buf);
>> +	is(strcmp(buf, long_str), 0, "Correct representation of smallest 
> possible
>> number"); +
>> +	/* Comparsions. */
>> +	char *max_str = "3.11", *min_str = "3.0999";
>> +	decimal max, min;
>> +	decimal_from_string(&max, max_str);
>> +	decimal_from_string(&min, min_str);
>> +	is(decimal_compare(&max, &min), 1, "max > min");
>> +	is(decimal_compare(&min, &max), -1, "min < max");
>> +	is(decimal_compare(&max, &max), 0, "max == max");
>> +
>> +	ret = decimal_from_string(&s, "-3.456");
>> +	isnt(ret, NULL, "Construction from negative numbers");
>> +	decimal_to_string(&s, buf);
>> +	is(strcmp(buf, "-3.456"), 0, "Correct construction for negatives");
>> +	ret = decimal_abs(&s, &s);
>> +	isnt(ret, NULL, "Abs");
>> +	decimal_to_string(&s, buf);
>> +	is(strcmp(buf, "3.456"), 0, "Correct abs");
>> +
>> +	/* Arithmetic ops. */
>> +	decimal d, check;
>> +	ret = decimal_from_string(&s, b);
>> +	ret = decimal_from_string(&d, "1.25");
>> +	sprintf(buf2, "%.9f", 1.25 + 2.718281828);
>> +	ret = decimal_add(&d, &d, &s);
>> +	isnt(ret, NULL, "Simple addition");
>> +	decimal_to_string(&d, buf);
>> +	is(strcmp(buf, buf2), 0, "Simple addition is correct");
>> +
>> +	ret = decimal_sub(&d, &d, &s);
>> +	isnt(ret, NULL, "Simple subtraction");
>> +	decimal_from_string(&check, "1.25");
>> +	is(decimal_compare(&d, &check), 0, "Simple subtraction is 
> correct");
>> +
>> +	decimal_from_int(&s, 4);
>> +	ret = decimal_mul(&s, &s, &d);
>> +	isnt(ret, NULL, "Simple multiplication");
>> +	decimal_from_string(&check, "5.0");
>> +	is(decimal_compare(&s, &check), 0 , "Simple multiplication is 
> correct");
>> +
>> +	ret = decimal_div(&s, &s, &d);
>> +	isnt(ret, NULL, "Simple division");
>> +	decimal_from_string(&check, "4.0");
>> +	is(decimal_compare(&s, &check), 0, "Simple division is correct");
>> +
>> +	/* Math. */
>> +	ret = decimal_from_string(&s, "40.96");
>> +	ret = decimal_from_string(&check, "6.4");
>> +	ret = decimal_sqrt(&s, &s, 2);
>> +	isnt(ret, NULL, "sqrt");
>> +	is(decimal_compare(&s, &check), 0, "sqrt is correct");
>> +
>> +	ret = decimal_from_string(&s, "40.96");
>> +	ret = decimal_from_string(&d, "0.5");
>> +	ret = decimal_pow(&s, &s, &d, 2);
>> +	isnt(ret, NULL, "pow");
>> +	is(decimal_compare(&s, &check), 0, "pow is correct");
>> +
>> +	ret = decimal_from_string(&s, "3.000");
>> +	ret = decimal_exp(&d, &s, 4);
>> +	isnt(ret, NULL, "exp");
>> +
>> +	ret = decimal_from_string(&check, "20.09");
>> +	is(decimal_compare(&d, &check), 0, "exp is correct")
>> +	ret = decimal_ln(&d, &d, 4);
>> +	isnt(ret, NULL, "ln");
>> +	/*
>> +	 * ln is wrong by 1 unit in last position.
>> +	 * (3.001 instead of 3.000, 3.0001 instead of 3.0000 and so on)
>> +	 */
>> +	decimal_from_string(&s, "3.001");
>> +	is(decimal_compare(&d, &s), 0, "ln is correct");
>> +
>> +				      /* 10^3.5 */
>> +	ret = decimal_from_string(&s, "3162.27766");
>> +	ret = decimal_log10(&d, &s, 2);
>> +	isnt(ret, NULL, "log10");
>> +	ret = decimal_from_string(&check, "3.5");
>> +	is(decimal_compare(&d, &check), 0, "log10 is correct");
>> +
>> +	/* Advanced test. */
>> +	/* 38 digits. */
>> +	char *bignum = "33.333333333333333333333333333333333333";
>> +	char *test =   "133.33333333333333333333333333333333333";
>> +	ret = decimal_from_string(&s, bignum);
>> +	ret = decimal_from_int(&d, 4);
>> +	ret = decimal_mul(&s, &s, &d);
>> +	isnt(ret, NULL, "Rounding when more than 
> TARANTOOL_MAX_DECIMAL_DIGITS
>> digits"); +	ret = decimal_from_string(&check, test);
>> +	is(decimal_compare(&s, &check), 0, "Rounding is correct");
>> +	is(decimal_precision(&s), 38, "Correct precision");
>> +	is(decimal_scale(&s), 35, "Correct scale");
>> +
>> +	char *small = "0.000000000000000000000000001";
>> +	ret = decimal_from_string(&s, small);
>> +	ret = decimal_mul(&s, &s, &s);
>> +	isnt(ret, NULL, "Rounding too small number to zero");
>> +	ret = decimal_from_int(&check, 0);
>> +	is(decimal_compare(&s, &check), 0, "Rounding is correct");
>> +	is(decimal_precision(&s), 38, "Correct precision");
>> +	is(decimal_scale(&s), 38, "Correct scale");
>> +
>> +	decimal_from_string(&s, small);
>> +	decimal_from_string(&d, "10000000000000000000");
>> +	ret = decimal_div(&s, &s, &d);
>> +	isnt(ret, NULL, "Rounding too small number to zero");
>> +	is(decimal_compare(&s, &check), 0, "Rounding is correct");
>> +	decimal_to_string(&s, buf);
>> +	is(decimal_precision(&s), 38, "Correct precision");
>> +	is(decimal_scale(&s), 38, "Correct scale");
>> +
>> +	check_plan();
>> +}
>> diff --git a/test/unit/decimal.result b/test/unit/decimal.result
>> new file mode 100644
>> index 000000000..02bee118a
>> --- /dev/null
>> +++ b/test/unit/decimal.result
>> @@ -0,0 +1,51 @@
>> +1..50
>> +ok 1 - Basic construction from string.
>> +ok 2 - Correct construction and to_string conversion.
>> +ok 3 - Correct construction from INT_MAX.
>> +ok 4 - Simple conversion back to int INT_MAX
>> +ok 5 - Correct construction from INT_MIN.
>> +ok 6 - Simple conversion bact to int INT_MIN
>> +ok 7 - .5 Rounds up
>> +ok 8 - .49 Rounds down
>> +ok 9 - Precision too high. Rounding happens.
>> +ok 10 - Construction is correct.
>> +ok 11 - Precision too high and scale = 0. Cannot round
>> +ok 12 - Construncting the smallest possible number from string
>> +ok 13 - Correct representation of smallest possible number
>> +ok 14 - max > min
>> +ok 15 - min < max
>> +ok 16 - max == max
>> +ok 17 - Construction from negative numbers
>> +ok 18 - Correct construction for negatives
>> +ok 19 - Abs
>> +ok 20 - Correct abs
>> +ok 21 - Simple addition
>> +ok 22 - Simple addition is correct
>> +ok 23 - Simple subtraction
>> +ok 24 - Simple subtraction is correct
>> +ok 25 - Simple multiplication
>> +ok 26 - Simple multiplication is correct
>> +ok 27 - Simple division
>> +ok 28 - Simple division is correct
>> +ok 29 - sqrt
>> +ok 30 - sqrt is correct
>> +ok 31 - pow
>> +ok 32 - pow is correct
>> +ok 33 - exp
>> +ok 34 - exp is correct
>> +ok 35 - ln
>> +ok 36 - ln is correct
>> +ok 37 - log10
>> +ok 38 - log10 is correct
>> +ok 39 - Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits
>> +ok 40 - Rounding is correct
>> +ok 41 - Correct precision
>> +ok 42 - Correct scale
>> +ok 43 - Rounding too small number to zero
>> +ok 44 - Rounding is correct
>> +ok 45 - Correct precision
>> +ok 46 - Correct scale
>> +ok 47 - Rounding too small number to zero
>> +ok 48 - Rounding is correct
>> +ok 49 - Correct precision
>> +ok 50 - Correct scale
> 

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

* Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool
  2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko
  2019-06-05  8:11   ` Георгий Кириченко
@ 2019-06-05 12:19   ` Vladimir Davydov
  2019-06-11 16:14     ` [tarantool-patches] " Serge Petrenko
  1 sibling, 1 reply; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-05 12:19 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: georgy, tarantool-patches, kostja

On Tue, Jun 04, 2019 at 07:04:17PM +0300, Serge Petrenko wrote:
> Add fixed-point decimal type to tarantool core.
> Adapt decNumber floating-point decimal library for the purpose, write a
> small wrapper and add unit tests.
> 
> A new decimal type is an alias for decNumber numbers from the decNumber
> library.
> Arithmetic operations (+, -, *, /) and some mathematic functions
> (ln, log10, exp, pow, sqrt) are available.
> 
> We introduce a single context for all the arithmetic operations
> on decimals, which enforces both number precision and scale to be
> in range [0, 38].
> 
> Every mathematic function has precision as one of its parameters, and
> the results are rounded to fit the desired precision.
> 
> Part of #692

Are NaN and inf supported? I assume not. Please mention it in
the commit log.

> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
> new file mode 100644
> index 000000000..12250da95
> --- /dev/null
> +++ b/src/lib/core/decimal.c
> @@ -0,0 +1,293 @@

> +#include "decimal.h"
> +#include "third_party/decNumber/decContext.h"
> +#include <stdlib.h>

Do you really need stdlib.h? Isn't stddef.h enough?

Also, you use intN_t hence you should include stdint.h.
I'm not sure you really need to use fixed size ints in
this module though.

> +#include <assert.h>
> +
> +#define MAX(a, b) ((a) > (b) ? (a) : (b))
> +#define MIN(a, b) ((a) > (b) ? (b) : (a))

Why not include trivial/util.h instead?

> +
> +/** A single context for all the arithmetic operations. */
> +static decContext decimal_context = {
> +	/* Maximum precision during operations. */
> +	TARANTOOL_MAX_DECIMAL_DIGITS,
> +	/*
> +	 * Maximum decimal lagarithm of the number.
> +	 * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS
> +	 */
> +	TARANTOOL_MAX_DECIMAL_DIGITS - 1,
> +	/*
> +	 * Minimal adjusted exponent. The smallest absolute value will be
> +	 * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) =
> +	 * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale =
> +	 * TARANTOOL_MAX_DECIMAL_DIGITS
> +	 */
> +	-1,
> +	/* Rounding mode: .5 rounds away from 0. */
> +	DEC_ROUND_HALF_UP,
> +	/* Turn off signalling for failed operations. */
> +	0,
> +	/* Status holding occured events. Initially empty. */
> +	0,
> +	/* Turn off exponent clamping. */
> +	0
> +};
> +
> +/**
> + * Return operation status and clear it for future checks.
> + *
> + * @return 0 if ok, bitwise or of decNumber errors, if any.
> + */
> +static inline uint32_t
> +decimal_get_op_status(decContext *context)
> +{
> +	uint32_t status = decContextGetStatus(context);
> +	decContextZeroStatus(&decimal_context);

Should be 'context', not decimal_context?

> +	/*
> +	 * Clear warnings. Every value less than 0.1 is
> +	 * subnormal, DEC_Inexact and DEC_Rounded result
> +	 * from rounding. DEC_Inexact with DEC_Subnormal
> +	 * together result in DEC_Underflow. DEC_Clamped
> +	 * happens after underflow if rounding to zero.
> +	 */
> +	return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow |
> +				    DEC_Subnormal | DEC_Clamped);

I'd fold this function in decimal_finalize as it's short and you don't
use it anywhere else.

> +}
> +
> +/**
> + * A finalizer for all the operations.
> + * Check the operation context status and empty it.
> + *
> + * @return NULL if finalization failed.
> + *         result pointer otherwise.
> + */
> +static inline decimal *
> +decimal_finalize(decimal *res, decContext *context)
> +{
> +	uint32_t status = decimal_get_op_status(context);
> +	if (status || ! decNumberIsFinite(res)) {

Do we really need decNumberIsFinite here? According to the context,
you don't allow inf.

> +		return NULL;
> +	}
> +	return res;
> +}
> +
> +uint8_t decimal_precision(const decimal *dec) {
> +	return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) :
> +				    dec->digits + dec->exponent;
> +}
> +
> +uint8_t decimal_scale(const decimal *dec) {
> +	return dec->exponent < 0 ? -dec->exponent : 0;
> +}
> +
> +decimal *
> +decimal_zero(decimal *dec)
> +{
> +	decNumberZero(dec);
> +	return dec;
> +}
> +
> +decimal *
> +decimal_from_string(decimal *dec, const char *str)
> +{
> +	decNumberFromString(dec, str, &decimal_context);
> +	return decimal_finalize(dec, &decimal_context);
> +}

It's unsafe to use a global context in all these functions as they might
be used from different threads while the context is used for propagating
errors. I think you should copy it to stack.

> +int
> +decimal_compare(const decimal *lhs, const decimal *rhs)
> +{
> +	decNumber res;
> +	decNumberCompare(&res, lhs, rhs, &decimal_context);
> +	return decNumberToInt32(&res, &decimal_context);
> +}

I'd add an assertion here checking the context status if this function
aren't supposed to fail. The same is fair to all other functions that
must not fail.

> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
> new file mode 100644
> index 000000000..f2812f500
> --- /dev/null
> +++ b/src/lib/core/decimal.h
> @@ -0,0 +1,159 @@

> +#define TARANTOOL_MAX_DECIMAL_DIGITS 38

This macro definition must be accompanied by a comment. Also, I don't
see any point in adding TARANTOOL_ prefix - you don't use the prefix
with the object type so using it with one constant looks inconsistent.
Let's rename it to DECIMAL_MAX_DIGITS?

> +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS

> +#define DECSTRING_NO_EXPONENT

AFAICS this macro is defined by default so there's no need to define it
here.

> +#include "third_party/decNumber/decNumber.h"
> +
> +typedef decNumber decimal;

We typically add _t suffix to opaque types.

> +
> +/**
> + * @return decimal precision,
> + * i.e. the amount of decimal digits in
> + * its representation.
> + */
> +uint8_t
> +decimal_precision(const decimal *dec);

We prefer using int unless we really need fixed size ints
for some reason.

> +
> +/**
> + * @return decimal scale,
> + * i.e. the number of decimal digits after
> + * the decimal separator.
> + */
> +uint8_t
> +decimal_scale(const decimal *dec);
> +
> +/**
> + * Initialize a zero decimal number.
> + */
> +decimal *
> +decimal_zero(decimal *dec);
> +
> +/**
> + * Initialize a decimal with a value from the string.
> + *
> + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS,
> + * but has excess digits in fractional part, it will be rounded.
> + *
> + * @return NULL if string is invalid or
> + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS)
> + */
> +decimal *
> +decimal_from_string(decimal *dec, const char *str);

AFAIK in SQL one can set column type to DECIMAL(precision,scale), i.e.
specify the precision/scale explicitly. An attempt to insert a value
that doesn't fit results in an error. That said, we might need to allow
specifying precision/scale here. Not sure about this. Please consult
SQL guys.

> +
> +/**
> + * Initialize a decimal with an integer value.
> + *
> + * @return NULL if precicion is insufficient to hold
> + * the value or precision/scale are out of bounds.
> +*/
> +decimal *
> +decimal_from_int(decimal *dec, int32_t num);
> +
> +/** @copydoc decimal_from_int */
> +decimal *
> +decimal_from_uint(decimal *dec, uint32_t num);

Why int32_t? Why not int64_t? Anyway, why do you need this operations
at all? Are you going to try to compare decimals with numbers when
the indexed field is scalar? That is, are you going to make decimal
a part of MP_CLASS_NUMBER? If so, you need _from_float() helpers, too.
Or are planning to arrange decimals before any simple number?

> +
> +/**
> + * Write the decimal to a string.
> + * A string has to be at least dec->digits + 3 bytes in size.
> + */
> +char *
> +decimal_to_string(const decimal *dec, char *buf);

This is kinda inconsistent with others formatting routines, e.g.
take a look at vclock_to_string:

 - there should be decimal_snprint which is an snprintf like function
   that dumps decimal to the provided buffer.
 - there should also be decimal_to_string wrapper which formats a
   decimal on a static buffer (tt_static_buf).

> +
> +/**
> + * Cast decimal to an integer value. The number will be rounded
> + * if it has a fractional part.
> + */
> +int32_t
> +decimal_to_int(const decimal *dec);
> +
> +/** @copydoc decimal_to_int */
> +uint32_t
> +decimal_to_uint(const decimal *dec);

These functions can easily fail, but the comment says nothing about it.
Looks like there's no way to figure that out. Anyway, why do we need
these functions at all?

> +
> +/**
> + * Compare 2 decimal values.
> + * @return -1, lhs < rhs,
> + *	    0, lhs = rhs,
> + *	    1, lhs > rhs
> + */
> +int
> +decimal_compare(const decimal *lhs, const decimal *rhs);
> +
> +/**
> + * res is set to the absolute value of dec
> + * decimal_abs(&a, &a) is allowed.
> + */
> +decimal *
> +decimal_abs(decimal *res, const decimal *dec);

What about unary minus?

> +
> +/*
> + * Arithmetic ops: add, subtract, multiply and divide.
> + * Return result pointer on success, NULL on an error (overflow).
> + */
> +
> +decimal *
> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +decimal *
> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +decimal *
> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +decimal *
> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs);
> +
> +/*
> + * log10, ln, pow, exp, sqrt.
> + * Calculate the appropriate function and then round the result
> + * to the desired precision.
> + * Return result pointer on success, NULL on an error (overflow).
> + */
> +decimal *
> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision);
> +
> +decimal *
> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision);

I'd expect to see decimal_log. Yeah, I know that

  log(a, b) = log(c, b) / log(c, a)

but a helper function would come in handy.

> +
> +decimal *
> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision);
> +
> +decimal *
> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision);
> +
> +decimal *
> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);

What's the point in specifying precision for these functions? Wouldn't
having decimal_round be better? Why doesn't decimal_div have this option
then?

> diff --git a/test/unit/decimal.c b/test/unit/decimal.c
> new file mode 100644
> index 000000000..adfbead91
> --- /dev/null
> +++ b/test/unit/decimal.c
> @@ -0,0 +1,168 @@

> +	decimal_from_int(&s, 4);
> +	ret = decimal_mul(&s, &s, &d);
> +	isnt(ret, NULL, "Simple multiplication");

Please also test all possible overflow/underflow cases.

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

* Re: [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support
  2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko
  2019-06-05  8:20   ` Георгий Кириченко
@ 2019-06-05 12:40   ` Vladimir Davydov
  1 sibling, 0 replies; 12+ messages in thread
From: Vladimir Davydov @ 2019-06-05 12:40 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: georgy, tarantool-patches, kostja

On Tue, Jun 04, 2019 at 07:04:19PM +0300, Serge Petrenko wrote:
> Add methods to encode/decode decimals as msgpack.
> 
> Part of #692
> ---
>  cmake/BuildDecNumber.cmake   |   1 +
>  src/lib/core/decimal.c       |  64 +++++++++++
>  src/lib/core/decimal.h       |  38 +++++++
>  src/lib/core/mp_user_types.h |  38 +++++++
>  test/unit/decimal.c          |  73 ++++++++++++-
>  test/unit/decimal.result     | 204 ++++++++++++++++++++++++++++++++++-
>  6 files changed, 416 insertions(+), 2 deletions(-)
>  create mode 100644 src/lib/core/mp_user_types.h
> 
> diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake
> index 80942fe05..abc6c64c4 100644
> --- a/cmake/BuildDecNumber.cmake
> +++ b/cmake/BuildDecNumber.cmake
> @@ -4,6 +4,7 @@ macro(decnumber_build)
>      set(decnumber_src
>  	${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c
>  	${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c
> +	${PROJECT_SOURCE_DIR}/third_party/decNumber/decPacked.c
>      )
>  
>      add_library(decNumber STATIC ${decnumber_src})
> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
> index 12250da95..7b3b1d8c9 100644
> --- a/src/lib/core/decimal.c
> +++ b/src/lib/core/decimal.c
> @@ -30,7 +30,10 @@
>   */
>  
>  #include "decimal.h"
> +#include "mp_user_types.h"
> +#include "src/lib/msgpuck/msgpuck.h"
>  #include "third_party/decNumber/decContext.h"
> +#include "third_party/decNumber/decPacked.h"
>  #include <stdlib.h>
>  #include <assert.h>
>  
> @@ -291,3 +294,64 @@ decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision)
>  
>  	return decimal_finalize(res, &op_context);
>  }
> +
> +static inline uint32_t
> +decimal_len(decimal *dec)
> +{
> +	       /* 1 + ceil((digits + 1) / 2) */

Bad indentation.

> +	return 2 + dec->digits / 2;
> +}
> +
> +uint32_t
> +mp_sizeof_decimal(decimal *dec) {
> +	uint32_t l = decimal_len(dec);
> +	return mp_sizeof_extl(l) + l;

Better use mp_sizeof_ext instead of mp_sizeof_extl here?

> +}
> +
> +char *
> +mp_encode_decimal(char *data, decimal *dec)
> +{
> +	uint32_t len = decimal_len(dec);
> +	data = mp_encode_extl(data, MP_DECIMAL, len);
> +	data = mp_store_u8(data, decimal_scale(dec));
> +	len--;
> +	int32_t scale;
> +	char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec);
> +	assert(tmp == data);
> +	assert(scale == (int32_t)decimal_scale(dec));
> +	data += len;
> +	return data;
> +}
> +
> +decimal *
> +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len)
> +{
> +	int32_t scale = mp_load_u8(data);
> +	len--;
> +	decimal *res = decPackedToNumber((uint8_t *)*data, len, &scale, dec);
> +	if (res)
> +		*data += len;
> +	else
> +		(*data)--;
> +	return res;
> +}
> +
> +decimal *
> +mp_decode_decimal(const char **data, decimal *dec)
> +{
> +	int8_t type;
> +	uint32_t len;
> +
> +	if (mp_typeof(**data) != MP_EXT)
> +		return NULL;
> +	const char *const svp = *data;
> +	len = mp_decode_extl(data, &type);
> +
> +	if (type != MP_DECIMAL || len == 0)
> +		return NULL;
> +	decimal *res = mp_decode_decimal_raw(data, dec, len);
> +	if (!res) {
> +		*data = svp;
> +	}
> +	return res;
> +}
> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
> index f2812f500..cfcc98073 100644
> --- a/src/lib/core/decimal.h
> +++ b/src/lib/core/decimal.h
> @@ -156,4 +156,42 @@ decimal_exp(decimal *res, const decimal *lhs, uint32_t precision);
>  decimal *
>  decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);
>  
> +
> +/**
> + * Return length of message pack encoded decimal with extension header.
> + * Equivalent to mp_sizeof_ext(decimal representation length)
> + */
> +uint32_t
> +mp_sizeof_decimal(decimal *dec);
> +
> +/**
> + * Encode a decimal value to msgpack.
> + *
> + * @return data + mp_sizeof_decimal(dec)
> + */
> +char *
> +mp_encode_decimal(char *data, decimal *dec);
> +
> +/**
> + * Decode a decimal value as though msgpack
> + * ext header was already decoded.
> + *
> + * \post *data = *data + value representation length
> + * @return NULL if value encoding is incorrect
> + *	    dec  otherwise.
> + */
> +decimal *
> +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len);
> +
> +/**
> + * Decode a decimal encoded as msgpack with ext header.
> + *
> + * \post *data = *data + mp_sizeof_decimal(dec)
> + * @return NULL if mp_typeof(**data) != MP_EXT
> + *		   or value encoding is incorrect
> + *	   dec  otherwise.
> + */
> +decimal *
> +mp_decode_decimal(const char **data, decimal *dec);
> +

Mixing mp_ and decimal_ methods in the same file looks ugly, because
msgpack is just one format - we might add other formats in future and it
doesn't sound like a good idea to implement their decoders in decimal.c.
IMO decimal.h should only contain methods to pack/unpack decimals in
some format, say

  decimal_pack_len/decimal_pack/decimal_unpack

or

  decimal_encode/decimal_decode

mp_ methods should live in some other file (mp_ext, mp_ext_decimal,
mp_decimal.[hc]?)

And I think it's better add and test those generic methods in patch 2,
along with the decimal infrastructure while this patch should be about
msgpack ext only.

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

* [tarantool-patches] Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool
  2019-06-05 12:19   ` Vladimir Davydov
@ 2019-06-11 16:14     ` Serge Petrenko
  0 siblings, 0 replies; 12+ messages in thread
From: Serge Petrenko @ 2019-06-11 16:14 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: Georgy Kirichenko, tarantool-patches, kostja

Hi! Thank you for review.
I addressed your comments and sent v4.

> 5 июня 2019 г., в 15:19, Vladimir Davydov <vdavydov.dev@gmail.com> написал(а):
> 
> On Tue, Jun 04, 2019 at 07:04:17PM +0300, Serge Petrenko wrote:
>> Add fixed-point decimal type to tarantool core.
>> Adapt decNumber floating-point decimal library for the purpose, write a
>> small wrapper and add unit tests.
>> 
>> A new decimal type is an alias for decNumber numbers from the decNumber
>> library.
>> Arithmetic operations (+, -, *, /) and some mathematic functions
>> (ln, log10, exp, pow, sqrt) are available.
>> 
>> We introduce a single context for all the arithmetic operations
>> on decimals, which enforces both number precision and scale to be
>> in range [0, 38].
>> 
>> Every mathematic function has precision as one of its parameters, and
>> the results are rounded to fit the desired precision.
>> 
>> Part of #692
> 
> Are NaN and inf supported? I assume not. Please mention it in
> the commit log.
Done.

> 
>> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
>> new file mode 100644
>> index 000000000..12250da95
>> --- /dev/null
>> +++ b/src/lib/core/decimal.c
>> @@ -0,0 +1,293 @@
> 
>> +#include "decimal.h"
>> +#include "third_party/decNumber/decContext.h"
>> +#include <stdlib.h>
> 
> Do you really need stdlib.h? Isn't stddef.h enough?
> 
> Also, you use intN_t hence you should include stdint.h.
> I'm not sure you really need to use fixed size ints in
> this module though.
I need fixed size ints since decNumber library uses them
in conversions: decNumberToInt32 (decNumberToInt64)

> 
>> +#include <assert.h>
>> +
>> +#define MAX(a, b) ((a) > (b) ? (a) : (b))
>> +#define MIN(a, b) ((a) > (b) ? (b) : (a))
> 
> Why not include trivial/util.h instead?
Indeed, done.

> 
>> +
>> +/** A single context for all the arithmetic operations. */
>> +static decContext decimal_context = {
>> +	/* Maximum precision during operations. */
>> +	TARANTOOL_MAX_DECIMAL_DIGITS,
>> +	/*
>> +	 * Maximum decimal lagarithm of the number.
>> +	 * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS
>> +	 */
>> +	TARANTOOL_MAX_DECIMAL_DIGITS - 1,
>> +	/*
>> +	 * Minimal adjusted exponent. The smallest absolute value will be
>> +	 * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) =
>> +	 * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale =
>> +	 * TARANTOOL_MAX_DECIMAL_DIGITS
>> +	 */
>> +	-1,
>> +	/* Rounding mode: .5 rounds away from 0. */
>> +	DEC_ROUND_HALF_UP,
>> +	/* Turn off signalling for failed operations. */
>> +	0,
>> +	/* Status holding occured events. Initially empty. */
>> +	0,
>> +	/* Turn off exponent clamping. */
>> +	0
>> +};
>> +
>> +/**
>> + * Return operation status and clear it for future checks.
>> + *
>> + * @return 0 if ok, bitwise or of decNumber errors, if any.
>> + */
>> +static inline uint32_t
>> +decimal_get_op_status(decContext *context)
>> +{
>> +	uint32_t status = decContextGetStatus(context);
>> +	decContextZeroStatus(&decimal_context);
> 
> Should be 'context', not decimal_context?

You’re right, fixed.

> 
>> +	/*
>> +	 * Clear warnings. Every value less than 0.1 is
>> +	 * subnormal, DEC_Inexact and DEC_Rounded result
>> +	 * from rounding. DEC_Inexact with DEC_Subnormal
>> +	 * together result in DEC_Underflow. DEC_Clamped
>> +	 * happens after underflow if rounding to zero.
>> +	 */
>> +	return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow |
>> +				    DEC_Subnormal | DEC_Clamped);
> 
> I'd fold this function in decimal_finalize as it's short and you don't
> use it anywhere else.
> 
Done.

>> +}
>> +
>> +/**
>> + * A finalizer for all the operations.
>> + * Check the operation context status and empty it.
>> + *
>> + * @return NULL if finalization failed.
>> + *         result pointer otherwise.
>> + */
>> +static inline decimal *
>> +decimal_finalize(decimal *res, decContext *context)
>> +{
>> +	uint32_t status = decimal_get_op_status(context);
>> +	if (status || ! decNumberIsFinite(res)) {
> 
> Do we really need decNumberIsFinite here? According to the context,
> you don't allow inf.

Fixed.

> 
>> +		return NULL;
>> +	}
>> +	return res;
>> +}
>> +
>> +uint8_t decimal_precision(const decimal *dec) {
>> +	return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) :
>> +				    dec->digits + dec->exponent;
>> +}
>> +
>> +uint8_t decimal_scale(const decimal *dec) {
>> +	return dec->exponent < 0 ? -dec->exponent : 0;
>> +}
>> +
>> +decimal *
>> +decimal_zero(decimal *dec)
>> +{
>> +	decNumberZero(dec);
>> +	return dec;
>> +}
>> +
>> +decimal *
>> +decimal_from_string(decimal *dec, const char *str)
>> +{
>> +	decNumberFromString(dec, str, &decimal_context);
>> +	return decimal_finalize(dec, &decimal_context);
>> +}
> 
> It's unsafe to use a global context in all these functions as they might
> be used from different threads while the context is used for propagating
> errors. I think you should copy it to stack.

Added `__thread` specifier.

> 
>> +int
>> +decimal_compare(const decimal *lhs, const decimal *rhs)
>> +{
>> +	decNumber res;
>> +	decNumberCompare(&res, lhs, rhs, &decimal_context);
>> +	return decNumberToInt32(&res, &decimal_context);
>> +}
> 
> I'd add an assertion here checking the context status if this function
> aren't supposed to fail. The same is fair to all other functions that
> must not fail.

Ok, done.

> 
>> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
>> new file mode 100644
>> index 000000000..f2812f500
>> --- /dev/null
>> +++ b/src/lib/core/decimal.h
>> @@ -0,0 +1,159 @@
> 
>> +#define TARANTOOL_MAX_DECIMAL_DIGITS 38
> 
> This macro definition must be accompanied by a comment. Also, I don't
> see any point in adding TARANTOOL_ prefix - you don't use the prefix
> with the object type so using it with one constant looks inconsistent.
> Let's rename it to DECIMAL_MAX_DIGITS?

Done.

> 
>> +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS
> 
>> +#define DECSTRING_NO_EXPONENT
> 
> AFAICS this macro is defined by default so there's no need to define it
> here.
> 
>> +#include "third_party/decNumber/decNumber.h"
>> +
>> +typedef decNumber decimal;
> 
> We typically add _t suffix to opaque types.

Done.

> 
>> +
>> +/**
>> + * @return decimal precision,
>> + * i.e. the amount of decimal digits in
>> + * its representation.
>> + */
>> +uint8_t
>> +decimal_precision(const decimal *dec);
> 
> We prefer using int unless we really need fixed size ints
> for some reason.

decimal_precision() and decimal_scale() return int from now on.
I left fixed size ints in some other places where they seem appropriate.

> 
>> +
>> +/**
>> + * @return decimal scale,
>> + * i.e. the number of decimal digits after
>> + * the decimal separator.
>> + */
>> +uint8_t
>> +decimal_scale(const decimal *dec);
>> +
>> +/**
>> + * Initialize a zero decimal number.
>> + */
>> +decimal *
>> +decimal_zero(decimal *dec);
>> +
>> +/**
>> + * Initialize a decimal with a value from the string.
>> + *
>> + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS,
>> + * but has excess digits in fractional part, it will be rounded.
>> + *
>> + * @return NULL if string is invalid or
>> + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS)
>> + */
>> +decimal *
>> +decimal_from_string(decimal *dec, const char *str);
> 
> AFAIK in SQL one can set column type to DECIMAL(precision,scale), i.e.
> specify the precision/scale explicitly. An attempt to insert a value
> that doesn't fit results in an error. That said, we might need to allow
> specifying precision/scale here. Not sure about this. Please consult
> SQL guys.

I guess this check can be done after a decimal is constructed.
Precision and scale meet the ones of the string for now. 

> 
>> +
>> +/**
>> + * Initialize a decimal with an integer value.
>> + *
>> + * @return NULL if precicion is insufficient to hold
>> + * the value or precision/scale are out of bounds.
>> +*/
>> +decimal *
>> +decimal_from_int(decimal *dec, int32_t num);
>> +
>> +/** @copydoc decimal_from_int */
>> +decimal *
>> +decimal_from_uint(decimal *dec, uint32_t num);
> 
> Why int32_t? Why not int64_t? Anyway, why do you need this operations
> at all? Are you going to try to compare decimals with numbers when
> the indexed field is scalar? That is, are you going to make decimal
> a part of MP_CLASS_NUMBER? If so, you need _from_float() helpers, too.
> Or are planning to arrange decimals before any simple number?

Added to/from_double and from int64_4 helpers. Yep, we’re going to compare decimals
with doubles, and ints.
> 
>> +
>> +/**
>> + * Write the decimal to a string.
>> + * A string has to be at least dec->digits + 3 bytes in size.
>> + */
>> +char *
>> +decimal_to_string(const decimal *dec, char *buf);
> 
> This is kinda inconsistent with others formatting routines, e.g.
> take a look at vclock_to_string:
> 
> - there should be decimal_snprint which is an snprintf like function
>   that dumps decimal to the provided buffer.
> - there should also be decimal_to_string wrapper which formats a
>   decimal on a static buffer (tt_static_buf).

Fixed according to a verbal discussion.

> 
>> +
>> +/**
>> + * Cast decimal to an integer value. The number will be rounded
>> + * if it has a fractional part.
>> + */
>> +int32_t
>> +decimal_to_int(const decimal *dec);
>> +
>> +/** @copydoc decimal_to_int */
>> +uint32_t
>> +decimal_to_uint(const decimal *dec);
> 
> These functions can easily fail, but the comment says nothing about it.
> Looks like there's no way to figure that out. Anyway, why do we need
> these functions at all?

Removed the to_uint and to_int helpers.

> 
>> +
>> +/**
>> + * Compare 2 decimal values.
>> + * @return -1, lhs < rhs,
>> + *	    0, lhs = rhs,
>> + *	    1, lhs > rhs
>> + */
>> +int
>> +decimal_compare(const decimal *lhs, const decimal *rhs);
>> +
>> +/**
>> + * res is set to the absolute value of dec
>> + * decimal_abs(&a, &a) is allowed.
>> + */
>> +decimal *
>> +decimal_abs(decimal *res, const decimal *dec);
> 
> What about unary minus?

Implemented.

> 
>> +
>> +/*
>> + * Arithmetic ops: add, subtract, multiply and divide.
>> + * Return result pointer on success, NULL on an error (overflow).
>> + */
>> +
>> +decimal *
>> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +decimal *
>> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +decimal *
>> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +decimal *
>> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs);
>> +
>> +/*
>> + * log10, ln, pow, exp, sqrt.
>> + * Calculate the appropriate function and then round the result
>> + * to the desired precision.
>> + * Return result pointer on success, NULL on an error (overflow).
>> + */
>> +decimal *
>> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision);
>> +
>> +decimal *
>> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision);
> 
> I'd expect to see decimal_log. Yeah, I know that
> 
>  log(a, b) = log(c, b) / log(c, a)
> 
> but a helper function would come in handy.

The helper will perform exactly the same division.
This can be done in a lua wrapper, I guess.

> 
>> +
>> +decimal *
>> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision);
>> +
>> +decimal *
>> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision);
>> +
>> +decimal *
>> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision);
> 
> What's the point in specifying precision for these functions? Wouldn't
> having decimal_round be better? Why doesn't decimal_div have this option
> then?

Added decimal_round(), removed precision from math functions.

> 
>> diff --git a/test/unit/decimal.c b/test/unit/decimal.c
>> new file mode 100644
>> index 000000000..adfbead91
>> --- /dev/null
>> +++ b/test/unit/decimal.c
>> @@ -0,0 +1,168 @@
> 
>> +	decimal_from_int(&s, 4);
>> +	ret = decimal_mul(&s, &s, &d);
>> +	isnt(ret, NULL, "Simple multiplication");
> 
> Please also test all possible overflow/underflow cases.

Reworked the test.

--
Serge Petrenko
sergepetrenko@tarantool.org

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

end of thread, other threads:[~2019-06-11 16:14 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko
2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko
2019-06-05  8:04   ` Георгий Кириченко
2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko
2019-06-05  8:11   ` Георгий Кириченко
2019-06-05 10:03     ` Serge Petrenko
2019-06-05 12:19   ` Vladimir Davydov
2019-06-11 16:14     ` [tarantool-patches] " Serge Petrenko
2019-06-04 16:04 ` [PATCH v3 3/4] lib: update msgpuck library Serge Petrenko
2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko
2019-06-05  8:20   ` Георгий Кириченко
2019-06-05 12:40   ` Vladimir Davydov

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