From: Serge Petrenko <sergepetrenko@tarantool.org> To: georgy@tarantool.org Cc: tarantool-patches@freelists.org, Serge Petrenko <sergepetrenko@tarantool.org> Subject: [tarantool-patches] [PATCH 2/2] lib/core: introduce decimal type to tarantool Date: Thu, 18 Apr 2019 20:16:02 +0300 [thread overview] Message-ID: <90af54aec2c80094aaff6029f8c241bfa5aee8b3.1555607573.git.sergepetrenko@tarantool.org> (raw) In-Reply-To: <cover.1555607573.git.sergepetrenko@tarantool.org> 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)
next prev parent reply other threads:[~2019-04-18 17:16 UTC|newest] Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top 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 [this message] 2019-04-18 17:20 ` [tarantool-patches] Re: [PATCH 0/2] Introduce decimal type to tarantool core Serge Petrenko
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=90af54aec2c80094aaff6029f8c241bfa5aee8b3.1555607573.git.sergepetrenko@tarantool.org \ --to=sergepetrenko@tarantool.org \ --cc=georgy@tarantool.org \ --cc=tarantool-patches@freelists.org \ --subject='Re: [tarantool-patches] [PATCH 2/2] lib/core: introduce decimal type to tarantool' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox