* [tarantool-patches] [PATCH 2/2] lib/core: introduce decimal type to tarantool
2019-04-18 17:16 [tarantool-patches] [PATCH 0/2] Introduce decimal type to tarantool core Serge Petrenko
2019-04-18 17:16 ` [tarantool-patches] [PATCH 1/2] third-party: add decNumber library Serge Petrenko
@ 2019-04-18 17:16 ` Serge Petrenko
2019-04-18 17:20 ` [tarantool-patches] Re: [PATCH 0/2] Introduce decimal type to tarantool core Serge Petrenko
2 siblings, 0 replies; 4+ messages in thread
From: Serge Petrenko @ 2019-04-18 17:16 UTC (permalink / raw)
To: georgy; +Cc: tarantool-patches, 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.
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 | 392 ++++++++++++++++++++++++++++++++++++
src/lib/core/decimal.h | 101 ++++++++++
test/unit/CMakeLists.txt | 2 +
test/unit/decimal.c | 173 ++++++++++++++++
test/unit/decimal.result | 53 +++++
9 files changed, 744 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 7c2395517..2cfbcd800 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..5eafa388e
--- /dev/null
+++ b/src/lib/core/decimal.c
@@ -0,0 +1,392 @@
+/*
+ * 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 decimal operations. */
+static decContext decimal_context = {
+ /* Maximum precision during operations. */
+ TARANTOOL_MAX_DECIMAL_DIGITS,
+ /* Maximum decimal lagarithm of the number. */
+ TARANTOOL_MAX_DECIMAL_DIGITS - 1,
+ /* Minimum decimal logarithm of the number. */
+ -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,
+ /*
+ * Clamp exponenents when they get too big.
+ * Doesn't really happen since they are shifted
+ * on each operation.
+ */
+ 1
+};
+
+/**
+ * Check whether there were errors during the operation
+ * and clear the status for future checks.
+ *
+ * @return 0 if ok, bitwise or of decNumber errors, if any.
+ */
+static inline uint32_t
+decimal_check_op_status()
+{
+ uint32_t status = decContextGetStatus(&decimal_context);
+ decContextZeroStatus(&decimal_context);
+ assert(!(status & DEC_Clamped));
+ /*
+ * Clear warnings. Rounding is ok, subnormal values will get rounded in
+ * the folowwing decimal_finalize() code.
+ */
+ return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Subnormal);
+}
+
+/**
+ * A finalizer to make sure every operation ends with a valid fixed-point
+ * decimal. Set exponent to a correct scale and check boundaries. Also check
+ * for errors during operation and raise an error.
+ *
+ * @return NULL if finalization failed.
+ * finalized number pointer otherwise.
+ */
+static inline struct decimal *
+decimal_finalize(struct decimal *res, uint8_t precision, uint8_t scale)
+{
+ uint32_t status = decimal_check_op_status();
+ if (status) {
+ return NULL;
+ }
+ res->precision = precision;
+ res->scale = scale;
+ decNumber exponent;
+ decNumberFromInt32(&exponent, -scale);
+ decNumberRescale(&res->number, &res->number, &exponent, &decimal_context);
+ status = decimal_check_op_status();
+ if (res->number.digits > precision || status) {
+ return NULL;
+ }
+ return res;
+}
+
+/**
+ * A common method for all the initializers. Check precision and
+ * scale boundaries, and set them.
+ *
+ * @return NULL on error, number pointer otherwise.
+ */
+static inline struct decimal *
+decimal_set_prec_scale(struct decimal *dec, uint8_t precision, uint8_t scale)
+{
+ if (precision < scale || precision > TARANTOOL_MAX_DECIMAL_DIGITS)
+ return NULL;
+ dec->precision = precision;
+ dec->scale = scale;
+ return dec;
+}
+
+/**
+ * Initialize a zero-value decimal with given precision and scale.
+ *
+ * @return NULL if precision and scale are out of bounds.
+ */
+struct decimal *
+decimal_zero(struct decimal *dec, uint8_t precision, uint8_t scale)
+{
+ if (decimal_set_prec_scale(dec, precision, scale) == NULL)
+ return NULL;
+ decNumberZero(&dec->number);
+ return dec;
+}
+
+/**
+ * Initialize a decimal with a value from the string.
+ *
+ * @return NULL if precision is insufficient to hold
+ * the value or precision/scale are out of bounds.
+ */
+struct decimal *
+decimal_from_string(struct decimal *dec, const char *str, uint8_t precision,
+ uint8_t scale)
+{
+ if(decimal_set_prec_scale(dec, precision, scale) == NULL)
+ return NULL;
+ decNumberFromString(&dec->number, str, &decimal_context);
+ return decimal_finalize(dec, precision, scale);
+}
+
+/**
+ * Initialize a decimal with an integer value.
+ *
+ * @return NULL if precicion is insufficient to hold
+ * the value or precision/scale are out of bounds.
+ */
+struct decimal *
+decimal_from_int(struct decimal *dec, int32_t num, uint8_t precision,
+ uint8_t scale)
+{
+ if (decimal_set_prec_scale(dec, precision, scale) == NULL)
+ return NULL;
+ decNumberFromInt32(&dec->number, num);
+ return decimal_finalize(dec, precision, scale);
+}
+
+/** @copydoc decimal_from_int */
+struct decimal *
+decimal_from_uint(struct decimal *dec, uint32_t num, uint8_t precision, uint8_t scale)
+{
+ if (decimal_set_prec_scale(dec, precision, scale) == NULL)
+ return NULL;
+ decNumberFromUInt32(&dec->number, num);
+ return decimal_finalize(dec, precision, scale);
+}
+
+/**
+ * Write the decimal to a string.
+ * A string has to be at least dec->precision + 3 bytes in size.
+ */
+char *
+decimal_to_string(const struct decimal *dec, char *buf)
+{
+ return decNumberToString(&dec->number, buf);
+}
+
+/**
+ * Cast decimal to an integer value. The number will be rounded
+ * if it has a fractional part.
+ */
+int32_t
+decimal_to_int(const struct decimal *dec)
+{
+ decNumber res;
+ decNumberToIntegralValue(&res, &dec->number, &decimal_context);
+ return decNumberToInt32(&res, &decimal_context);
+}
+
+/** @copydoc decimal_to_int */
+uint32_t
+decimal_to_uint(const struct decimal *dec)
+{
+ decNumber res;
+ decNumberToIntegralValue(&res, &dec->number, &decimal_context);
+ return decNumberToUInt32(&dec->number, &decimal_context);
+}
+
+/**
+ * Compare 2 decimal values.
+ * @return -1, lhs < rhs,
+ * 0, lhs = rhs,
+ * 1, lhs > rhs
+ */
+int
+decimal_compare(const struct decimal *lhs, const struct decimal *rhs)
+{
+ decNumber res;
+ decNumberCompare(&res, &lhs->number, &rhs->number, &decimal_context);
+ return decNumberToInt32(&res, &decimal_context);
+}
+
+/**
+ * res is set to the absolute value of dec
+ * decimal_abs(&a, &a) is allowed.
+ */
+struct decimal *
+decimal_abs(struct decimal *res, const struct decimal *dec)
+{
+ decNumberAbs(&res->number, &dec->number, &decimal_context);
+ return res;
+}
+
+/**
+ * Calculate the number of decimal digits needed to hold the
+ * result of adding or subtracting lhs and rhs.
+ */
+static inline uint8_t addsub_precision(const struct decimal *lhs,
+ const struct decimal *rhs)
+{
+ uint8_t precision = MAX(lhs->scale, rhs->scale) + 1;
+ precision += MAX(lhs->precision - lhs->scale,
+ rhs->precision - rhs->scale);
+ return MIN(TARANTOOL_MAX_DECIMAL_DIGITS, precision);
+}
+
+/**
+ * Calculate the number of digits after the decimal point for the result
+ * of adding or subtracting lhs and rhs.
+ */
+static inline uint8_t addsub_scale(uint8_t precision, const struct decimal *lhs,
+ const struct decimal *rhs)
+{
+ uint8_t scale = MAX(lhs->scale, rhs->scale);
+ if (precision - scale < MAX(lhs->precision - lhs->scale,
+ rhs->precision - rhs->scale)) {
+ /*
+ * Not enough digits to store integral part.
+ * Try to round by decreasing scale.
+ */
+ scale = precision - MAX(lhs->precision - lhs->scale,
+ rhs->precision - rhs->scale);
+ }
+ return scale;
+}
+
+struct decimal *
+decimal_add(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs)
+{
+ decNumberAdd(&res->number, &lhs->number, &rhs->number, &decimal_context);
+
+ uint8_t precision = addsub_precision(lhs, rhs);
+ uint8_t scale = addsub_scale(precision, lhs, rhs);
+ return decimal_finalize(res, precision, scale);
+}
+
+struct decimal *
+decimal_sub(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs)
+{
+ decNumberSubtract(&res->number, &lhs->number, &rhs->number, &decimal_context);
+
+ uint8_t precision = addsub_precision(lhs, rhs);
+ uint8_t scale = addsub_scale(precision, lhs, rhs);
+ return decimal_finalize(res, precision, scale);
+}
+
+/** @copydoc addsub_precision */
+static inline uint8_t mul_precision(const struct decimal *lhs,
+ const struct decimal *rhs)
+{
+ return lhs->precision + rhs->precision;
+}
+
+/** @copydoc addsub_scale */
+static inline uint8_t mul_scale(uint8_t precision, const struct decimal *lhs,
+ const struct decimal *rhs)
+{
+ uint8_t scale = lhs->scale + rhs->scale;
+ if (precision - scale > 38)
+ return 0;
+ return MIN(scale, 38 - (precision - scale));
+}
+
+struct decimal *
+decimal_mul(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs)
+{
+ decNumberMultiply(&res->number, &lhs->number, &rhs->number, &decimal_context);
+
+ uint8_t precision = mul_precision(lhs, rhs);
+ uint8_t scale = mul_scale(precision, lhs, rhs);
+ /*
+ * Need to clamp precision, it is used unbounded in scale
+ * calculations. Scale is already clamped.
+ */
+ precision = MIN(TARANTOOL_MAX_DECIMAL_DIGITS, precision);
+ return decimal_finalize(res, precision, scale);
+}
+
+/** @copydoc addsub_precision */
+static inline uint8_t div_precision(const struct decimal *lhs,
+ const struct decimal *rhs)
+{
+ return lhs->precision + rhs->precision + 1;
+}
+
+/** @copydoc addsub_scale */
+static inline uint8_t div_scale(uint8_t precision, const struct decimal *lhs,
+ const struct decimal *rhs)
+{
+ uint8_t scale = lhs->scale + rhs->precision + 1;
+ if (precision - scale > 38)
+ return 0;
+ return MIN(scale, 38 - (precision - scale));
+}
+
+struct decimal *
+decimal_div(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs)
+{
+ decNumberDivide(&res->number, &lhs->number, &rhs->number, &decimal_context);
+
+ uint8_t precision = div_precision(lhs, rhs);
+ uint8_t scale = div_scale(precision, lhs, rhs);
+ /*
+ * Need to clamp precision, it is used unbounded in scale
+ * calculations. Scale is already clamped.
+ */
+ precision = MIN(TARANTOOL_MAX_DECIMAL_DIGITS, precision);
+ return decimal_finalize(res, precision, scale);
+}
+
+/** log10, ln, pow, exp, sqrt.
+ * For these operations the scale and precision are taken from the
+ * res parameter, e.g.:
+ * decimal_zero(&res, 10, 5);
+ * decimal_log10(&res, &some_value) -> decimal(10,5)
+ */
+struct decimal *
+decimal_log10(struct decimal *res, const struct decimal *lhs)
+{
+ decNumberLog10(&res->number, &lhs->number, &decimal_context);
+ return decimal_finalize(res, res->precision, res->scale);
+}
+
+struct decimal *
+decimal_ln(struct decimal *res, const struct decimal *lhs)
+{
+ decNumberLn(&res->number, &lhs->number, &decimal_context);
+ return decimal_finalize(res, res->precision, res->scale);
+}
+
+struct decimal *
+decimal_pow(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs)
+{
+ decNumberPower(&res->number, &lhs->number, &rhs->number, &decimal_context);
+ return decimal_finalize(res, res->precision, res->scale);
+}
+
+struct decimal *
+decimal_exp(struct decimal *res, const struct decimal *lhs)
+{
+ decNumberExp(&res->number, &lhs->number, &decimal_context);
+ return decimal_finalize(res, res->precision, res->scale);
+}
+
+struct decimal *
+decimal_sqrt(struct decimal *res, const struct decimal *lhs)
+{
+ decNumberSquareRoot(&res->number, &lhs->number, &decimal_context);
+ return decimal_finalize(res, res->precision, res->scale);
+}
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
new file mode 100644
index 000000000..e5bdabf77
--- /dev/null
+++ b/src/lib/core/decimal.h
@@ -0,0 +1,101 @@
+#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"
+
+struct decimal {
+ uint8_t precision;
+ uint8_t scale;
+ decNumber number;
+};
+
+struct decimal *
+decimal_zero(struct decimal *dec, uint8_t precision, uint8_t scale);
+
+struct decimal *
+decimal_from_string(struct decimal *dec, const char *str, uint8_t precision,
+ uint8_t scale);
+
+struct decimal *
+decimal_from_int(struct decimal *dec, int32_t num, uint8_t precision,
+ uint8_t scale);
+
+struct decimal *
+decimal_from_uint(struct decimal *dec, uint32_t numb, uint8_t precision,
+ uint8_t scale);
+
+char *
+decimal_to_string(const struct decimal *dec, char *buf);
+
+int32_t
+decimal_to_int(const struct decimal *dec);
+
+uint32_t
+decimal_to_uint(const struct decimal *dec);
+
+int
+decimal_compare(const struct decimal *lhs, const struct decimal *rhs);
+
+struct decimal *
+decimal_abs(struct decimal *res, const struct decimal *dec);
+
+struct decimal *
+decimal_add(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs);
+
+struct decimal *
+decimal_sub(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs);
+
+struct decimal *
+decimal_mul(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs);
+
+struct decimal *
+decimal_div(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs);
+
+struct decimal *
+decimal_log10(struct decimal *res, const struct decimal *lhs);
+
+struct decimal *
+decimal_ln(struct decimal *res, const struct decimal *lhs);
+
+struct decimal *
+decimal_pow(struct decimal *res, const struct decimal *lhs, const struct decimal *rhs);
+
+struct decimal *
+decimal_exp(struct decimal *res, const struct decimal *lhs);
+
+struct decimal *
+decimal_sqrt(struct decimal *res, const struct decimal *lhs);
+
+#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index bb88b9c9b..6ced85ad3 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..b14da1467
--- /dev/null
+++ b/test/unit/decimal.c
@@ -0,0 +1,173 @@
+#include "unit.h"
+#include "decimal.h"
+#include <limits.h>
+#include <string.h>
+
+int
+main(void)
+{
+ plan(52);
+
+ char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
+ char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3];
+ char *b = "2.718281828";
+
+ struct decimal s;
+ struct decimal *ret;
+
+ ret = decimal_from_string(&s, b, 10, 9);
+ 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, 10, 0);
+ 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, 10, 0);
+ 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, 2, 1);
+ is(decimal_to_int(&s), 3, ".5 Rounds up");
+ decimal_from_string(&s, down1, 3, 2);
+ is(decimal_to_int(&s), 2, ".49 Rounds down");
+
+ ret = decimal_from_string(&s, b, 9, 9);
+ is(ret, NULL, "Construction with insufficient precision fails.");
+ ret = decimal_from_string(&s, b, 20, 8);
+ isnt(ret, NULL, "Construction with insufficient scale - rounding happens.");
+ ret = decimal_zero(&s, 17, 13);
+ ok(s.precision == 17 && s.scale == 13 , "Construction is correct.");
+
+ ret = decimal_zero(&s, 5, 6);
+ is(ret, NULL, "Construction with scale > precision fails.");
+ ret = decimal_zero(&s, TARANTOOL_MAX_DECIMAL_DIGITS + 1, TARANTOOL_MAX_DECIMAL_DIGITS);
+ is(ret, NULL, "Construction with precision > TARANTOOL_MAX_DECIMAL_DIGITS fails.");
+
+ /* 38 digits. */
+ char *long_str = "0.0000000000000000000000000000000000001";
+ ret = decimal_from_string(&s, long_str, 38, 37);
+ 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";
+ struct decimal max, min;
+ decimal_from_string(&max, max_str, 3, 2);
+ decimal_from_string(&min, min_str, 5, 4);
+ 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", 4, 3);
+ 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. */
+ struct decimal d, check;
+ ret = decimal_from_string(&s, b, 10, 9);
+ ret = decimal_from_string(&d, "1.25", 3, 2);
+ 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", 3, 2);
+ is(decimal_compare(&d, &check), 0, "Simple subtraction is correct");
+
+ decimal_from_int(&s, 4, 1, 0);
+ ret = decimal_mul(&s, &s, &d);
+ isnt(ret, NULL, "Simple multiplication");
+ decimal_from_string(&check, "5.0", 2, 1);
+ 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", 2, 1);
+ is(decimal_compare(&s, &check), 0, "Simple division is correct");
+
+ /* Math. */
+ ret = decimal_from_string(&s, "40.96", 4, 2);
+ ret = decimal_from_string(&check, "6.4", 2, 1);
+ ret = decimal_sqrt(&s, &s);
+ isnt(ret, NULL, "sqrt");
+ is(decimal_compare(&s, &check), 0, "sqrt is correct");
+
+ ret = decimal_from_string(&s, "40.96", 4, 2);
+ ret = decimal_from_string(&d, "0.5", 2, 1);
+ ret = decimal_pow(&s, &s, &d);
+ isnt(ret, NULL, "pow");
+ is(decimal_compare(&s, &check), 0, "pow is correct");
+
+ ret = decimal_from_int(&s, 2, 1, 0);
+ ret = decimal_exp(&d, &s);
+ isnt(ret, NULL, "exp");
+ /*
+ * precision and scale are taken from the operand to store result in.
+ * d is decimal(2, 1) (from the last test) Additionally checks that
+ * rounding works.
+ */
+ ret = decimal_from_string(&check, "7.4", 2, 1);
+ is(decimal_compare(&d, &check), 0, "exp is correct")
+
+ ret = decimal_ln(&d, &d);
+ isnt(ret, NULL, "ln");
+ is(decimal_compare(&d, &s), 0, "ln is correct");
+
+ /* 10^3.5 */
+ ret = decimal_from_string(&s, "3162.27766", 9, 5);
+ /* d still is decimal(2, 1) */
+ ret = decimal_log10(&d, &s);
+ isnt(ret, NULL, "log10");
+ ret = decimal_from_string(&check, "3.5", 2, 1);
+ 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, 38, 36);
+ ret = decimal_from_int(&d, 4, 1, 0);
+ ret = decimal_mul(&s, &s, &d);
+ isnt(ret, NULL, "Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits");
+ ret = decimal_from_string(&check, test, 38, 35);
+ is(decimal_compare(&s, &check), 0, "Rounding is correct");
+ is(s.precision, 38, "Correct precision");
+ is(s.scale, 35, "Correct scale");
+
+ char *small = "0.00000000000000000001";
+ ret = decimal_from_string(&s, small, 21, 21);
+ ret = decimal_mul(&s, &s, &s);
+ isnt(ret, NULL, "Rounding too small number to zero");
+ ret = decimal_from_int(&check, 0, 1, 0);
+ is(decimal_compare(&s, &check), 0, "Rounding is correct");
+ is(s.precision, 38, "Correct precision");
+ is(s.scale, 38, "Correct scale");
+
+ decimal_from_string(&s, small, 21, 21);
+ decimal_from_string(&d, "10000000000000000000", 20, 0);
+ 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(s.precision, 38, "Correct precision");
+ is(s.scale, 38, "Correct scale");
+
+ check_plan();
+}
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
new file mode 100644
index 000000000..db15e08b1
--- /dev/null
+++ b/test/unit/decimal.result
@@ -0,0 +1,53 @@
+1..52
+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 - Construction with insufficient precision fails.
+ok 10 - Construction with insufficient scale - rounding happens.
+ok 11 - Construction is correct.
+ok 12 - Construction with scale > precision fails.
+ok 13 - Construction with precision > TARANTOOL_MAX_DECIMAL_DIGITS fails.
+ok 14 - Construncting the smallest possible number from string
+ok 15 - Correct representation of smallest possible number
+ok 16 - max > min
+ok 17 - min < max
+ok 18 - max == max
+ok 19 - Construction from negative numbers
+ok 20 - Correct construction for negatives
+ok 21 - Abs
+ok 22 - Correct abs
+ok 23 - Simple addition
+ok 24 - Simple addition is correct
+ok 25 - Simple subtraction
+ok 26 - Simple subtraction is correct
+ok 27 - Simple multiplication
+ok 28 - Simple multiplication is correct
+ok 29 - Simple division
+ok 30 - Simple division is correct
+ok 31 - sqrt
+ok 32 - sqrt is correct
+ok 33 - pow
+ok 34 - pow is correct
+ok 35 - exp
+ok 36 - exp is correct
+ok 37 - ln
+ok 38 - ln is correct
+ok 39 - log10
+ok 40 - log10 is correct
+ok 41 - Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits
+ok 42 - Rounding is correct
+ok 43 - Correct precision
+ok 44 - Correct scale
+ok 45 - Rounding too small number to zero
+ok 46 - Rounding is correct
+ok 47 - Correct precision
+ok 48 - Correct scale
+ok 49 - Rounding too small number to zero
+ok 50 - Rounding is correct
+ok 51 - Correct precision
+ok 52 - Correct scale
--
2.20.1 (Apple Git-117)
^ permalink raw reply [flat|nested] 4+ messages in thread