Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v2 0/2] Introduce decimal type to tarantool core.
@ 2019-05-21 13:12 Serge Petrenko
  2019-05-21 13:12 ` [tarantool-patches] [PATCH v2 1/2] third-party: add decNumber library Serge Petrenko
  2019-05-21 13:12 ` [tarantool-patches] [PATCH v2 2/2] lib/core: introduce decimal type to tarantool Serge Petrenko
  0 siblings, 2 replies; 9+ messages in thread
From: Serge Petrenko @ 2019-05-21 13:12 UTC (permalink / raw)
  To: tarantool-patches; +Cc: georgy, kostja, Serge Petrenko

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

This patchset adds a new type, decimal fixed-point, to tarantool.

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

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 (2):
  third-party: add decNumber library
  lib/core: introduce decimal type to tarantool

 .gitmodules                 |   3 +
 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 +++++++
 third_party/decNumber       |   1 +
 11 files changed, 700 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
 create mode 160000 third_party/decNumber

-- 
2.20.1 (Apple Git-117)

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

* [tarantool-patches] [PATCH v2 1/2] third-party: add decNumber library
  2019-05-21 13:12 [tarantool-patches] [PATCH v2 0/2] Introduce decimal type to tarantool core Serge Petrenko
@ 2019-05-21 13:12 ` Serge Petrenko
  2019-05-21 13:12 ` [tarantool-patches] [PATCH v2 2/2] lib/core: introduce decimal type to tarantool Serge Petrenko
  1 sibling, 0 replies; 9+ messages in thread
From: Serge Petrenko @ 2019-05-21 13:12 UTC (permalink / raw)
  To: tarantool-patches; +Cc: georgy, 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] 9+ messages in thread

* [tarantool-patches] [PATCH v2 2/2] lib/core: introduce decimal type to tarantool
  2019-05-21 13:12 [tarantool-patches] [PATCH v2 0/2] Introduce decimal type to tarantool core Serge Petrenko
  2019-05-21 13:12 ` [tarantool-patches] [PATCH v2 1/2] third-party: add decNumber library Serge Petrenko
@ 2019-05-21 13:12 ` Serge Petrenko
  2019-05-21 14:17   ` [tarantool-patches] " Konstantin Osipov
  1 sibling, 1 reply; 9+ messages in thread
From: Serge Petrenko @ 2019-05-21 13:12 UTC (permalink / raw)
  To: tarantool-patches; +Cc: georgy, 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 9a842acfd..cea71880c 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] 9+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/2] lib/core: introduce decimal type to tarantool
  2019-05-21 13:12 ` [tarantool-patches] [PATCH v2 2/2] lib/core: introduce decimal type to tarantool Serge Petrenko
@ 2019-05-21 14:17   ` Konstantin Osipov
  2019-05-21 14:52     ` Serge Petrenko
  0 siblings, 1 reply; 9+ messages in thread
From: Konstantin Osipov @ 2019-05-21 14:17 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: tarantool-patches, georgy

* Serge Petrenko <sergepetrenko@tarantool.org> [19/05/21 17:04]:

How does this co-exist with the bindings for the same library
made by the community?


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 2/2] lib/core: introduce decimal type to tarantool
  2019-05-21 14:17   ` [tarantool-patches] " Konstantin Osipov
@ 2019-05-21 14:52     ` Serge Petrenko
  2019-05-21 14:59       ` Konstantin Osipov
  0 siblings, 1 reply; 9+ messages in thread
From: Serge Petrenko @ 2019-05-21 14:52 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches, Georgy Kirichenko


> 21 мая 2019 г., в 17:17, Konstantin Osipov <kostja@tarantool.org> написал(а):
> 
> * Serge Petrenko <sergepetrenko@tarantool.org> [19/05/21 17:04]:
> 
> How does this co-exist with the bindings for the same library
> made by the community?
> 

What do you mean? I’m not sure I understand.
Are you talking about conversions between our decimal and ldecnumber decimal?

> -- 
> Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 2/2] lib/core: introduce decimal type to tarantool
  2019-05-21 14:52     ` Serge Petrenko
@ 2019-05-21 14:59       ` Konstantin Osipov
  2019-05-23  7:15         ` Георгий Кириченко
  0 siblings, 1 reply; 9+ messages in thread
From: Konstantin Osipov @ 2019-05-21 14:59 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: tarantool-patches, Georgy Kirichenko

* Serge Petrenko <sergepetrenko@tarantool.org> [19/05/21 17:56]:
> 
> > 21 мая 2019 г., в 17:17, Konstantin Osipov <kostja@tarantool.org> написал(а):
> > 
> > * Serge Petrenko <sergepetrenko@tarantool.org> [19/05/21 17:04]:
> > 
> > How does this co-exist with the bindings for the same library
> > made by the community?
> > 
> 
> What do you mean? I’m not sure I understand.
> Are you talking about conversions between our decimal and ldecnumber decimal?

I mean the symbol clash when people are trying to use the
standalone .so 

-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 2/2] lib/core: introduce decimal type to tarantool
  2019-05-21 14:59       ` Konstantin Osipov
@ 2019-05-23  7:15         ` Георгий Кириченко
  2019-05-23 11:44           ` Konstantin Osipov
  0 siblings, 1 reply; 9+ messages in thread
From: Георгий Кириченко @ 2019-05-23  7:15 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: Serge Petrenko, tarantool-patches

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

On Tuesday, May 21, 2019 5:59:20 PM MSK Konstantin Osipov wrote:
> * Serge Petrenko <sergepetrenko@tarantool.org> [19/05/21 17:56]:
> > > 21 мая 2019 г., в 17:17, Konstantin Osipov <kostja@tarantool.org>
> > > написал(а):
> > > 
> > > * Serge Petrenko <sergepetrenko@tarantool.org> [19/05/21 17:04]:
> > > 
> > > How does this co-exist with the bindings for the same library
> > > made by the community?
> > 
> > What do you mean? I’m not sure I understand.
> > Are you talking about conversions between our decimal and ldecnumber
> > decimal?
> I mean the symbol clash when people are trying to use the
> standalone .so
I'm not sure there is a symbol clash because Serge used a static library and 
non-marked symbols are stripped. If would use a shared one then users code 
won't do anything and reuse the linked library.

At least the situation is the same as with libz/ssl and other libraries we 
link with.

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

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

* [tarantool-patches] Re: [PATCH v2 2/2] lib/core: introduce decimal type to tarantool
  2019-05-23  7:15         ` Георгий Кириченко
@ 2019-05-23 11:44           ` Konstantin Osipov
  2019-05-23 14:45             ` Serge Petrenko
  0 siblings, 1 reply; 9+ messages in thread
From: Konstantin Osipov @ 2019-05-23 11:44 UTC (permalink / raw)
  To: Георгий
	Кириченко
  Cc: Serge Petrenko, tarantool-patches

* Георгий Кириченко <georgy@tarantool.org> [19/05/23 11:47]:
> > > > How does this co-exist with the bindings for the same library
> > > > made by the community?
> > > 
> > > What do you mean? I’m not sure I understand.
> > > Are you talking about conversions between our decimal and ldecnumber
> > > decimal?
> > I mean the symbol clash when people are trying to use the
> > standalone .so
> I'm not sure there is a symbol clash because Serge used a static library and 
> non-marked symbols are stripped. If would use a shared one then users code 
> won't do anything and reuse the linked library.

this would be nice, could you test this is the case indeed?


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 2/2] lib/core: introduce decimal type to tarantool
  2019-05-23 11:44           ` Konstantin Osipov
@ 2019-05-23 14:45             ` Serge Petrenko
  0 siblings, 0 replies; 9+ messages in thread
From: Serge Petrenko @ 2019-05-23 14:45 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: Georgy Kirichenko, tarantool-patches



> 23 мая 2019 г., в 14:44, Konstantin Osipov <kostja@tarantool.org> написал(а):
> 
> * Георгий Кириченко <georgy@tarantool.org> [19/05/23 11:47]:
>>>>> How does this co-exist with the bindings for the same library
>>>>> made by the community?
>>>> 
>>>> What do you mean? I’m not sure I understand.
>>>> Are you talking about conversions between our decimal and ldecnumber
>>>> decimal?
>>> I mean the symbol clash when people are trying to use the
>>> standalone .so
>> I'm not sure there is a symbol clash because Serge used a static library and 
>> non-marked symbols are stripped. If would use a shared one then users code 
>> won't do anything and reuse the linked library.
> 
> this would be nice, could you test this is the case indeed?
> 

I checked: there is no symbol clash with current implementation.
I’ll also try out the shared library approach and let you know.

> 
> -- 
> Konstantin Osipov, Moscow, Russia
> 

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

end of thread, other threads:[~2019-05-23 14:45 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-05-21 13:12 [tarantool-patches] [PATCH v2 0/2] Introduce decimal type to tarantool core Serge Petrenko
2019-05-21 13:12 ` [tarantool-patches] [PATCH v2 1/2] third-party: add decNumber library Serge Petrenko
2019-05-21 13:12 ` [tarantool-patches] [PATCH v2 2/2] lib/core: introduce decimal type to tarantool Serge Petrenko
2019-05-21 14:17   ` [tarantool-patches] " Konstantin Osipov
2019-05-21 14:52     ` Serge Petrenko
2019-05-21 14:59       ` Konstantin Osipov
2019-05-23  7:15         ` Георгий Кириченко
2019-05-23 11:44           ` Konstantin Osipov
2019-05-23 14:45             ` Serge Petrenko

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