* [PATCH v3 0/4] Introduce decimal type to tarantool core. @ 2019-06-04 16:04 Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko ` (3 more replies) 0 siblings, 4 replies; 12+ messages in thread From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw) To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko https://github.com/tarantool/tarantool/issues/692 https://github.com/tarantool/tarantool/tree/sp/gh-692-mp-encode-decimal This patchset adds a new type, decimal fixed-point, to tarantool and allows to encode/decode decimals in msgpack. The first patch adds decNumber library as a submodule. The second patch adds a small wrapper to the library to make it work with fixed-point decimal values, and adds a unit test. To evaluate number precision and scale from exponent and the amount of significant digits the following formulas are applied: If exponent < 0 then precision = MAX(digits, -exponent) and scale = -exponent If exponent > 0 then precision = digits + exponent, and scale = 0 The third patch updates msgpuck library to add extension types, which are used to encode decimals. The fourth patch adds methods to encode/decode decimals, adds the first messagepack ext type, MP_DECIMAL, and tests for encoding/decoding decimals. Changes in v3: - add patches 3 and 4 to encode/decode decimals as msgpack. Changes in v2: - get rid of explicit precision and scale, evaluate them from decNumber digits and exponent. - decimal is now an alias for decNumber - ln, log10, exp, sqrt, pow now accept precision to which the result should be rounded. Serge Petrenko (4): third-party: add decNumber library lib/core: introduce decimal type to tarantool lib: update msgpuck library decimal: add MessagePack encoding/decoding support .gitmodules | 3 + CMakeLists.txt | 7 + cmake/BuildDecNumber.cmake | 14 + src/CMakeLists.txt | 1 + src/lib/core/CMakeLists.txt | 3 +- src/lib/core/decimal.c | 357 ++++++++++++++++++++++++++ src/lib/core/decimal.h | 197 ++++++++++++++ src/lib/core/mp_user_types.h | 38 +++ src/lib/msgpuck | 2 +- test/unit/CMakeLists.txt | 2 + test/unit/decimal.c | 239 +++++++++++++++++ test/unit/decimal.result | 253 ++++++++++++++++++ test/unit/msgpack.result | 480 ++++++++++++++++++++++++++++++++++- third_party/decNumber | 1 + 14 files changed, 1582 insertions(+), 15 deletions(-) create mode 100644 cmake/BuildDecNumber.cmake create mode 100644 src/lib/core/decimal.c create mode 100644 src/lib/core/decimal.h create mode 100644 src/lib/core/mp_user_types.h create mode 100644 test/unit/decimal.c create mode 100644 test/unit/decimal.result create mode 160000 third_party/decNumber -- 2.20.1 (Apple Git-117) ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 1/4] third-party: add decNumber library 2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko @ 2019-06-04 16:04 ` Serge Petrenko 2019-06-05 8:04 ` Георгий Кириченко 2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko ` (2 subsequent siblings) 3 siblings, 1 reply; 12+ messages in thread From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw) To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko We will use it for decimal implementation in tarantool Part of #692 --- .gitmodules | 3 +++ third_party/decNumber | 1 + 2 files changed, 4 insertions(+) create mode 160000 third_party/decNumber diff --git a/.gitmodules b/.gitmodules index e3aa7d79e..9f52c3fc1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ path = third_party/luarocks url = https://github.com/tarantool/luarocks.git branch = tarantool-1.7 +[submodule "third_party/decNumber"] + path = third_party/decNumber + url = https://github.com/tarantool/decNumber.git diff --git a/third_party/decNumber b/third_party/decNumber new file mode 160000 index 000000000..04e31ac65 --- /dev/null +++ b/third_party/decNumber @@ -0,0 +1 @@ +Subproject commit 04e31ac65efbfb426c7fc299e736ea4e591b41a9 -- 2.20.1 (Apple Git-117) ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 1/4] third-party: add decNumber library 2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko @ 2019-06-05 8:04 ` Георгий Кириченко 0 siblings, 0 replies; 12+ messages in thread From: Георгий Кириченко @ 2019-06-05 8:04 UTC (permalink / raw) To: Serge Petrenko; +Cc: vdavydov.dev, tarantool-patches, kostja [-- Attachment #1: Type: text/plain, Size: 907 bytes --] LGTM On Tuesday, June 4, 2019 7:04:16 PM MSK Serge Petrenko wrote: > We will use it for decimal implementation in tarantool > > Part of #692 > --- > .gitmodules | 3 +++ > third_party/decNumber | 1 + > 2 files changed, 4 insertions(+) > create mode 160000 third_party/decNumber > > diff --git a/.gitmodules b/.gitmodules > index e3aa7d79e..9f52c3fc1 100644 > --- a/.gitmodules > +++ b/.gitmodules > @@ -31,3 +31,6 @@ > path = third_party/luarocks > url = https://github.com/tarantool/luarocks.git > branch = tarantool-1.7 > +[submodule "third_party/decNumber"] > + path = third_party/decNumber > + url = https://github.com/tarantool/decNumber.git > diff --git a/third_party/decNumber b/third_party/decNumber > new file mode 160000 > index 000000000..04e31ac65 > --- /dev/null > +++ b/third_party/decNumber > @@ -0,0 +1 @@ > +Subproject commit 04e31ac65efbfb426c7fc299e736ea4e591b41a9 [-- Attachment #2: This is a digitally signed message part. --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 2/4] lib/core: introduce decimal type to tarantool 2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko @ 2019-06-04 16:04 ` Serge Petrenko 2019-06-05 8:11 ` Георгий Кириченко 2019-06-05 12:19 ` Vladimir Davydov 2019-06-04 16:04 ` [PATCH v3 3/4] lib: update msgpuck library Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko 3 siblings, 2 replies; 12+ messages in thread From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw) To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko Add fixed-point decimal type to tarantool core. Adapt decNumber floating-point decimal library for the purpose, write a small wrapper and add unit tests. A new decimal type is an alias for decNumber numbers from the decNumber library. Arithmetic operations (+, -, *, /) and some mathematic functions (ln, log10, exp, pow, sqrt) are available. We introduce a single context for all the arithmetic operations on decimals, which enforces both number precision and scale to be in range [0, 38]. Every mathematic function has precision as one of its parameters, and the results are rounded to fit the desired precision. Part of #692 --- CMakeLists.txt | 7 + cmake/BuildDecNumber.cmake | 13 ++ src/CMakeLists.txt | 1 + src/lib/core/CMakeLists.txt | 3 +- src/lib/core/decimal.c | 293 ++++++++++++++++++++++++++++++++++++ src/lib/core/decimal.h | 159 +++++++++++++++++++ test/unit/CMakeLists.txt | 2 + test/unit/decimal.c | 168 +++++++++++++++++++++ test/unit/decimal.result | 51 +++++++ 9 files changed, 696 insertions(+), 1 deletion(-) create mode 100644 cmake/BuildDecNumber.cmake create mode 100644 src/lib/core/decimal.c create mode 100644 src/lib/core/decimal.h create mode 100644 test/unit/decimal.c create mode 100644 test/unit/decimal.result diff --git a/CMakeLists.txt b/CMakeLists.txt index 7658fc6c9..bfb15effb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -431,6 +431,13 @@ else() find_package(MsgPuck) endif() +# +# decNumber +# + +include(BuildDecNumber) +decnumber_build() + # # LibYAML # diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake new file mode 100644 index 000000000..80942fe05 --- /dev/null +++ b/cmake/BuildDecNumber.cmake @@ -0,0 +1,13 @@ +# +# A macro to build the bundled decNumber lisbrary. +macro(decnumber_build) + set(decnumber_src + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c + ) + + add_library(decNumber STATIC ${decnumber_src}) + + set(DECNUMBER_INCLUDE_DIR ${PROJECT_BINARY_DIR}/third_party/decNumber) + unset(decnumber_src) +endmacro(decnumber_build) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 183cc04ef..9d196755f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ include_directories(${MSGPUCK_INCLUDE_DIRS}) include_directories(${CURL_INCLUDE_DIRS}) include_directories(${ICU_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) +include_directories(${DECNUMBER_INCLUDE_DIR}) set(LIBUTIL_FREEBSD_SRC ${CMAKE_SOURCE_DIR}/third_party/libutil_freebsd) include_directories(${LIBUTIL_FREEBSD_SRC}) diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index eb10b11c3..66e430a25 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -26,6 +26,7 @@ set(core_sources trigger.cc mpstream.c port.c + decimal.c ) if (TARGET_OS_NETBSD) @@ -37,7 +38,7 @@ endif() add_library(core STATIC ${core_sources}) -target_link_libraries(core salad small uri ${LIBEV_LIBRARIES} +target_link_libraries(core salad small uri decNumber ${LIBEV_LIBRARIES} ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES} ${MSGPUCK_LIBRARIES}) diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c new file mode 100644 index 000000000..12250da95 --- /dev/null +++ b/src/lib/core/decimal.c @@ -0,0 +1,293 @@ +/* + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "decimal.h" +#include "third_party/decNumber/decContext.h" +#include <stdlib.h> +#include <assert.h> + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +/** A single context for all the arithmetic operations. */ +static decContext decimal_context = { + /* Maximum precision during operations. */ + TARANTOOL_MAX_DECIMAL_DIGITS, + /* + * Maximum decimal lagarithm of the number. + * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS + */ + TARANTOOL_MAX_DECIMAL_DIGITS - 1, + /* + * Minimal adjusted exponent. The smallest absolute value will be + * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) = + * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale = + * TARANTOOL_MAX_DECIMAL_DIGITS + */ + -1, + /* Rounding mode: .5 rounds away from 0. */ + DEC_ROUND_HALF_UP, + /* Turn off signalling for failed operations. */ + 0, + /* Status holding occured events. Initially empty. */ + 0, + /* Turn off exponent clamping. */ + 0 +}; + +/** + * Return operation status and clear it for future checks. + * + * @return 0 if ok, bitwise or of decNumber errors, if any. + */ +static inline uint32_t +decimal_get_op_status(decContext *context) +{ + uint32_t status = decContextGetStatus(context); + decContextZeroStatus(&decimal_context); + /* + * Clear warnings. Every value less than 0.1 is + * subnormal, DEC_Inexact and DEC_Rounded result + * from rounding. DEC_Inexact with DEC_Subnormal + * together result in DEC_Underflow. DEC_Clamped + * happens after underflow if rounding to zero. + */ + return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow | + DEC_Subnormal | DEC_Clamped); +} + +/** + * A finalizer for all the operations. + * Check the operation context status and empty it. + * + * @return NULL if finalization failed. + * result pointer otherwise. + */ +static inline decimal * +decimal_finalize(decimal *res, decContext *context) +{ + uint32_t status = decimal_get_op_status(context); + if (status || ! decNumberIsFinite(res)) { + return NULL; + } + return res; +} + +uint8_t decimal_precision(const decimal *dec) { + return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) : + dec->digits + dec->exponent; +} + +uint8_t decimal_scale(const decimal *dec) { + return dec->exponent < 0 ? -dec->exponent : 0; +} + +decimal * +decimal_zero(decimal *dec) +{ + decNumberZero(dec); + return dec; +} + +decimal * +decimal_from_string(decimal *dec, const char *str) +{ + decNumberFromString(dec, str, &decimal_context); + return decimal_finalize(dec, &decimal_context); +} + +decimal * +decimal_from_int(decimal *dec, int32_t num) +{ + decNumberFromInt32(dec, num); + return decimal_finalize(dec, &decimal_context); +} + +decimal * +decimal_from_uint(decimal *dec, uint32_t num) +{ + decNumberFromUInt32(dec, num); + return decimal_finalize(dec, &decimal_context); +} + +char * +decimal_to_string(const decimal *dec, char *buf) +{ + return decNumberToString(dec, buf); +} + +int32_t +decimal_to_int(const decimal *dec) +{ + decNumber res; + decNumberToIntegralValue(&res, dec, &decimal_context); + return decNumberToInt32(&res, &decimal_context); +} + +uint32_t +decimal_to_uint(const decimal *dec) +{ + decNumber res; + decNumberToIntegralValue(&res, dec, &decimal_context); + return decNumberToUInt32(&res, &decimal_context); +} + +int +decimal_compare(const decimal *lhs, const decimal *rhs) +{ + decNumber res; + decNumberCompare(&res, lhs, rhs, &decimal_context); + return decNumberToInt32(&res, &decimal_context); +} + +decimal * +decimal_abs(decimal *res, const decimal *dec) +{ + decNumberAbs(res, dec, &decimal_context); + return res; +} + +decimal * +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs) +{ + decNumberAdd(res, lhs, rhs, &decimal_context); + + return decimal_finalize(res, &decimal_context); +} + +decimal * +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs) +{ + decNumberSubtract(res, lhs, rhs, &decimal_context); + + return decimal_finalize(res, &decimal_context); +} + +decimal * +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs) +{ + decNumberMultiply(res, lhs, rhs, &decimal_context); + + return decimal_finalize(res, &decimal_context); +} + +decimal * +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs) +{ + decNumberDivide(res, lhs, rhs, &decimal_context); + + return decimal_finalize(res, &decimal_context); +} + +decimal * +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision) +{ + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) + return NULL; + + /* A separate context to enforce desired precision. */ + static decContext op_context; + op_context.digits = precision; + op_context.emax = op_context.digits - 1; + op_context.emin = -1; + + decNumberLog10(res, lhs, &op_context); + + return decimal_finalize(res, &op_context); +} + +decimal * +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision) +{ + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) + return NULL; + + /* A separate context to enforce desired precision. */ + static decContext op_context; + op_context.digits = precision; + op_context.emax = op_context.digits - 1; + op_context.emin = -1; + + decNumberLn(res, lhs, &op_context); + + return decimal_finalize(res, &op_context); +} + +decimal * +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision) +{ + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) + return NULL; + + /* A separate context to enforce desired precision. */ + static decContext op_context; + op_context.digits = precision; + op_context.emax = op_context.digits - 1; + op_context.emin = -1; + + decNumberPower(res, lhs, rhs, &op_context); + + return decimal_finalize(res, &op_context); +} + +decimal * +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision) +{ + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) + return NULL; + + /* A separate context to enforce desired precision. */ + static decContext op_context; + op_context.digits = precision; + op_context.emax = op_context.digits - 1; + op_context.emin = -1; + + decNumberExp(res, lhs, &op_context); + + return decimal_finalize(res, &op_context); +} + +decimal * +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision) +{ + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) + return NULL; + + /* A separate context to enforce desired precision. */ + static decContext op_context; + op_context.digits = precision; + op_context.emax = op_context.digits - 1; + op_context.emin = -1; + + decNumberSquareRoot(res, lhs, &op_context); + + return decimal_finalize(res, &op_context); +} diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h new file mode 100644 index 000000000..f2812f500 --- /dev/null +++ b/src/lib/core/decimal.h @@ -0,0 +1,159 @@ +#ifndef TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED +#define TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED +/* + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#define TARANTOOL_MAX_DECIMAL_DIGITS 38 +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS +#define DECSTRING_NO_EXPONENT +#include "third_party/decNumber/decNumber.h" + +typedef decNumber decimal; + +/** + * @return decimal precision, + * i.e. the amount of decimal digits in + * its representation. + */ +uint8_t +decimal_precision(const decimal *dec); + +/** + * @return decimal scale, + * i.e. the number of decimal digits after + * the decimal separator. + */ +uint8_t +decimal_scale(const decimal *dec); + +/** + * Initialize a zero decimal number. + */ +decimal * +decimal_zero(decimal *dec); + +/** + * Initialize a decimal with a value from the string. + * + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS, + * but has excess digits in fractional part, it will be rounded. + * + * @return NULL if string is invalid or + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS) + */ +decimal * +decimal_from_string(decimal *dec, const char *str); + +/** + * Initialize a decimal with an integer value. + * + * @return NULL if precicion is insufficient to hold + * the value or precision/scale are out of bounds. +*/ +decimal * +decimal_from_int(decimal *dec, int32_t num); + +/** @copydoc decimal_from_int */ +decimal * +decimal_from_uint(decimal *dec, uint32_t num); + +/** + * Write the decimal to a string. + * A string has to be at least dec->digits + 3 bytes in size. + */ +char * +decimal_to_string(const decimal *dec, char *buf); + +/** + * Cast decimal to an integer value. The number will be rounded + * if it has a fractional part. + */ +int32_t +decimal_to_int(const decimal *dec); + +/** @copydoc decimal_to_int */ +uint32_t +decimal_to_uint(const decimal *dec); + +/** + * Compare 2 decimal values. + * @return -1, lhs < rhs, + * 0, lhs = rhs, + * 1, lhs > rhs + */ +int +decimal_compare(const decimal *lhs, const decimal *rhs); + +/** + * res is set to the absolute value of dec + * decimal_abs(&a, &a) is allowed. + */ +decimal * +decimal_abs(decimal *res, const decimal *dec); + +/* + * Arithmetic ops: add, subtract, multiply and divide. + * Return result pointer on success, NULL on an error (overflow). + */ + +decimal * +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs); + +decimal * +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs); + +decimal * +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs); + +decimal * +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs); + +/* + * log10, ln, pow, exp, sqrt. + * Calculate the appropriate function and then round the result + * to the desired precision. + * Return result pointer on success, NULL on an error (overflow). + */ +decimal * +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision); + +decimal * +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision); + +decimal * +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision); + +decimal * +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision); + +decimal * +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); + +#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 70be6366c..e3de34b16 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -68,6 +68,8 @@ add_executable(vclock.test vclock.cc) target_link_libraries(vclock.test vclock unit) add_executable(xrow.test xrow.cc) target_link_libraries(xrow.test xrow unit) +add_executable(decimal.test decimal.c) +target_link_libraries(decimal.test core unit) add_executable(fiber.test fiber.cc) set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0) diff --git a/test/unit/decimal.c b/test/unit/decimal.c new file mode 100644 index 000000000..adfbead91 --- /dev/null +++ b/test/unit/decimal.c @@ -0,0 +1,168 @@ +#include "unit.h" +#include "decimal.h" +#include <limits.h> +#include <string.h> + +int +main(void) +{ + plan(50); + + char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; + char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; + char *b = "2.718281828"; + + decimal s; + decimal *ret; + + ret = decimal_from_string(&s, b); + isnt(ret, NULL, "Basic construction from string."); + decimal_to_string(&s, buf); + is(strcmp(buf, b), 0, "Correct construction and to_string conversion."); + + ret = decimal_from_int(&s, INT_MAX); + decimal_to_string(&s, buf); + sprintf(buf2, "%d", INT_MAX); + is(strcmp(buf, buf2), 0, "Correct construction from INT_MAX."); + is(decimal_to_int(&s), INT_MAX, "Simple conversion back to int INT_MAX"); + + ret = decimal_from_int(&s, INT_MIN); + decimal_to_string(&s, buf); + sprintf(buf2, "%d", INT_MIN); + is(strcmp(buf, buf2), 0, "Correct construction from INT_MIN."); + is(decimal_to_int(&s), INT_MIN, "Simple conversion bact to int INT_MIN"); + + char *up1 = "2.5"; + char *down1 = "2.49"; + decimal_from_string(&s, up1); + is(decimal_to_int(&s), 3, ".5 Rounds up"); + decimal_from_string(&s, down1); + is(decimal_to_int(&s), 2, ".49 Rounds down"); + + char *l = "1.23456789123456789123456789123456789123"; + ret = decimal_from_string(&s, l); + isnt(ret, NULL, "Precision too high. Rounding happens."); + ok(decimal_precision(&s) == 38 && decimal_scale(&s) == 37 , "Construction is correct."); + char *ll = "123456789123456789123456789123456789123"; + ret = decimal_from_string(&s, ll); + is(ret, NULL, "Precision too high and scale = 0. Cannot round"); + + /* 38 digits. */ + char *long_str = "0.0000000000000000000000000000000000001"; + ret = decimal_from_string(&s, long_str); + isnt(ret, NULL, "Construncting the smallest possible number from string"); + decimal_to_string(&s, buf); + is(strcmp(buf, long_str), 0, "Correct representation of smallest possible number"); + + /* Comparsions. */ + char *max_str = "3.11", *min_str = "3.0999"; + decimal max, min; + decimal_from_string(&max, max_str); + decimal_from_string(&min, min_str); + is(decimal_compare(&max, &min), 1, "max > min"); + is(decimal_compare(&min, &max), -1, "min < max"); + is(decimal_compare(&max, &max), 0, "max == max"); + + ret = decimal_from_string(&s, "-3.456"); + isnt(ret, NULL, "Construction from negative numbers"); + decimal_to_string(&s, buf); + is(strcmp(buf, "-3.456"), 0, "Correct construction for negatives"); + ret = decimal_abs(&s, &s); + isnt(ret, NULL, "Abs"); + decimal_to_string(&s, buf); + is(strcmp(buf, "3.456"), 0, "Correct abs"); + + /* Arithmetic ops. */ + decimal d, check; + ret = decimal_from_string(&s, b); + ret = decimal_from_string(&d, "1.25"); + sprintf(buf2, "%.9f", 1.25 + 2.718281828); + ret = decimal_add(&d, &d, &s); + isnt(ret, NULL, "Simple addition"); + decimal_to_string(&d, buf); + is(strcmp(buf, buf2), 0, "Simple addition is correct"); + + ret = decimal_sub(&d, &d, &s); + isnt(ret, NULL, "Simple subtraction"); + decimal_from_string(&check, "1.25"); + is(decimal_compare(&d, &check), 0, "Simple subtraction is correct"); + + decimal_from_int(&s, 4); + ret = decimal_mul(&s, &s, &d); + isnt(ret, NULL, "Simple multiplication"); + decimal_from_string(&check, "5.0"); + is(decimal_compare(&s, &check), 0 , "Simple multiplication is correct"); + + ret = decimal_div(&s, &s, &d); + isnt(ret, NULL, "Simple division"); + decimal_from_string(&check, "4.0"); + is(decimal_compare(&s, &check), 0, "Simple division is correct"); + + /* Math. */ + ret = decimal_from_string(&s, "40.96"); + ret = decimal_from_string(&check, "6.4"); + ret = decimal_sqrt(&s, &s, 2); + isnt(ret, NULL, "sqrt"); + is(decimal_compare(&s, &check), 0, "sqrt is correct"); + + ret = decimal_from_string(&s, "40.96"); + ret = decimal_from_string(&d, "0.5"); + ret = decimal_pow(&s, &s, &d, 2); + isnt(ret, NULL, "pow"); + is(decimal_compare(&s, &check), 0, "pow is correct"); + + ret = decimal_from_string(&s, "3.000"); + ret = decimal_exp(&d, &s, 4); + isnt(ret, NULL, "exp"); + + ret = decimal_from_string(&check, "20.09"); + is(decimal_compare(&d, &check), 0, "exp is correct") + ret = decimal_ln(&d, &d, 4); + isnt(ret, NULL, "ln"); + /* + * ln is wrong by 1 unit in last position. + * (3.001 instead of 3.000, 3.0001 instead of 3.0000 and so on) + */ + decimal_from_string(&s, "3.001"); + is(decimal_compare(&d, &s), 0, "ln is correct"); + + /* 10^3.5 */ + ret = decimal_from_string(&s, "3162.27766"); + ret = decimal_log10(&d, &s, 2); + isnt(ret, NULL, "log10"); + ret = decimal_from_string(&check, "3.5"); + is(decimal_compare(&d, &check), 0, "log10 is correct"); + + /* Advanced test. */ + /* 38 digits. */ + char *bignum = "33.333333333333333333333333333333333333"; + char *test = "133.33333333333333333333333333333333333"; + ret = decimal_from_string(&s, bignum); + ret = decimal_from_int(&d, 4); + ret = decimal_mul(&s, &s, &d); + isnt(ret, NULL, "Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits"); + ret = decimal_from_string(&check, test); + is(decimal_compare(&s, &check), 0, "Rounding is correct"); + is(decimal_precision(&s), 38, "Correct precision"); + is(decimal_scale(&s), 35, "Correct scale"); + + char *small = "0.000000000000000000000000001"; + ret = decimal_from_string(&s, small); + ret = decimal_mul(&s, &s, &s); + isnt(ret, NULL, "Rounding too small number to zero"); + ret = decimal_from_int(&check, 0); + is(decimal_compare(&s, &check), 0, "Rounding is correct"); + is(decimal_precision(&s), 38, "Correct precision"); + is(decimal_scale(&s), 38, "Correct scale"); + + decimal_from_string(&s, small); + decimal_from_string(&d, "10000000000000000000"); + ret = decimal_div(&s, &s, &d); + isnt(ret, NULL, "Rounding too small number to zero"); + is(decimal_compare(&s, &check), 0, "Rounding is correct"); + decimal_to_string(&s, buf); + is(decimal_precision(&s), 38, "Correct precision"); + is(decimal_scale(&s), 38, "Correct scale"); + + check_plan(); +} diff --git a/test/unit/decimal.result b/test/unit/decimal.result new file mode 100644 index 000000000..02bee118a --- /dev/null +++ b/test/unit/decimal.result @@ -0,0 +1,51 @@ +1..50 +ok 1 - Basic construction from string. +ok 2 - Correct construction and to_string conversion. +ok 3 - Correct construction from INT_MAX. +ok 4 - Simple conversion back to int INT_MAX +ok 5 - Correct construction from INT_MIN. +ok 6 - Simple conversion bact to int INT_MIN +ok 7 - .5 Rounds up +ok 8 - .49 Rounds down +ok 9 - Precision too high. Rounding happens. +ok 10 - Construction is correct. +ok 11 - Precision too high and scale = 0. Cannot round +ok 12 - Construncting the smallest possible number from string +ok 13 - Correct representation of smallest possible number +ok 14 - max > min +ok 15 - min < max +ok 16 - max == max +ok 17 - Construction from negative numbers +ok 18 - Correct construction for negatives +ok 19 - Abs +ok 20 - Correct abs +ok 21 - Simple addition +ok 22 - Simple addition is correct +ok 23 - Simple subtraction +ok 24 - Simple subtraction is correct +ok 25 - Simple multiplication +ok 26 - Simple multiplication is correct +ok 27 - Simple division +ok 28 - Simple division is correct +ok 29 - sqrt +ok 30 - sqrt is correct +ok 31 - pow +ok 32 - pow is correct +ok 33 - exp +ok 34 - exp is correct +ok 35 - ln +ok 36 - ln is correct +ok 37 - log10 +ok 38 - log10 is correct +ok 39 - Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits +ok 40 - Rounding is correct +ok 41 - Correct precision +ok 42 - Correct scale +ok 43 - Rounding too small number to zero +ok 44 - Rounding is correct +ok 45 - Correct precision +ok 46 - Correct scale +ok 47 - Rounding too small number to zero +ok 48 - Rounding is correct +ok 49 - Correct precision +ok 50 - Correct scale -- 2.20.1 (Apple Git-117) ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool 2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko @ 2019-06-05 8:11 ` Георгий Кириченко 2019-06-05 10:03 ` Serge Petrenko 2019-06-05 12:19 ` Vladimir Davydov 1 sibling, 1 reply; 12+ messages in thread From: Георгий Кириченко @ 2019-06-05 8:11 UTC (permalink / raw) To: Serge Petrenko; +Cc: vdavydov.dev, tarantool-patches, kostja [-- Attachment #1: Type: text/plain, Size: 27549 bytes --] Overall it looks great but please check travis has an error: /tarantool/src/lib/core/decimal.c:319:8: error: unused variable ‘tmp’ [- Werror=unused-variable] char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec); Perhaps, it was caused by assert elimination because of the Release-type build. On Tuesday, June 4, 2019 7:04:17 PM MSK Serge Petrenko wrote: > Add fixed-point decimal type to tarantool core. > Adapt decNumber floating-point decimal library for the purpose, write a > small wrapper and add unit tests. > > A new decimal type is an alias for decNumber numbers from the decNumber > library. > Arithmetic operations (+, -, *, /) and some mathematic functions > (ln, log10, exp, pow, sqrt) are available. > > We introduce a single context for all the arithmetic operations > on decimals, which enforces both number precision and scale to be > in range [0, 38]. > > Every mathematic function has precision as one of its parameters, and > the results are rounded to fit the desired precision. > > Part of #692 > --- > CMakeLists.txt | 7 + > cmake/BuildDecNumber.cmake | 13 ++ > src/CMakeLists.txt | 1 + > src/lib/core/CMakeLists.txt | 3 +- > src/lib/core/decimal.c | 293 ++++++++++++++++++++++++++++++++++++ > src/lib/core/decimal.h | 159 +++++++++++++++++++ > test/unit/CMakeLists.txt | 2 + > test/unit/decimal.c | 168 +++++++++++++++++++++ > test/unit/decimal.result | 51 +++++++ > 9 files changed, 696 insertions(+), 1 deletion(-) > create mode 100644 cmake/BuildDecNumber.cmake > create mode 100644 src/lib/core/decimal.c > create mode 100644 src/lib/core/decimal.h > create mode 100644 test/unit/decimal.c > create mode 100644 test/unit/decimal.result > > diff --git a/CMakeLists.txt b/CMakeLists.txt > index 7658fc6c9..bfb15effb 100644 > --- a/CMakeLists.txt > +++ b/CMakeLists.txt > @@ -431,6 +431,13 @@ else() > find_package(MsgPuck) > endif() > > +# > +# decNumber > +# > + > +include(BuildDecNumber) > +decnumber_build() > + > # > # LibYAML > # > diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake > new file mode 100644 > index 000000000..80942fe05 > --- /dev/null > +++ b/cmake/BuildDecNumber.cmake > @@ -0,0 +1,13 @@ > +# > +# A macro to build the bundled decNumber lisbrary. > +macro(decnumber_build) > + set(decnumber_src > + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c > + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c > + ) > + > + add_library(decNumber STATIC ${decnumber_src}) > + > + set(DECNUMBER_INCLUDE_DIR ${PROJECT_BINARY_DIR}/third_party/decNumber) > + unset(decnumber_src) > +endmacro(decnumber_build) > diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt > index 183cc04ef..9d196755f 100644 > --- a/src/CMakeLists.txt > +++ b/src/CMakeLists.txt > @@ -13,6 +13,7 @@ include_directories(${MSGPUCK_INCLUDE_DIRS}) > include_directories(${CURL_INCLUDE_DIRS}) > include_directories(${ICU_INCLUDE_DIRS}) > include_directories(${ICONV_INCLUDE_DIRS}) > +include_directories(${DECNUMBER_INCLUDE_DIR}) > > set(LIBUTIL_FREEBSD_SRC ${CMAKE_SOURCE_DIR}/third_party/libutil_freebsd) > include_directories(${LIBUTIL_FREEBSD_SRC}) > diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt > index eb10b11c3..66e430a25 100644 > --- a/src/lib/core/CMakeLists.txt > +++ b/src/lib/core/CMakeLists.txt > @@ -26,6 +26,7 @@ set(core_sources > trigger.cc > mpstream.c > port.c > + decimal.c > ) > > if (TARGET_OS_NETBSD) > @@ -37,7 +38,7 @@ endif() > > add_library(core STATIC ${core_sources}) > > -target_link_libraries(core salad small uri ${LIBEV_LIBRARIES} > +target_link_libraries(core salad small uri decNumber ${LIBEV_LIBRARIES} > ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES} > ${MSGPUCK_LIBRARIES}) > > diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c > new file mode 100644 > index 000000000..12250da95 > --- /dev/null > +++ b/src/lib/core/decimal.c > @@ -0,0 +1,293 @@ > +/* > + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. > + * > + * Redistribution and use in source and binary forms, with or > + * without modification, are permitted provided that the following > + * conditions are met: > + * > + * 1. Redistributions of source code must retain the above > + * copyright notice, this list of conditions and the > + * following disclaimer. > + * > + * 2. Redistributions in binary form must reproduce the above > + * copyright notice, this list of conditions and the following > + * disclaimer in the documentation and/or other materials > + * provided with the distribution. > + * > + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL > + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, > + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF > + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF > + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF > + * SUCH DAMAGE. > + */ > + > +#include "decimal.h" > +#include "third_party/decNumber/decContext.h" > +#include <stdlib.h> > +#include <assert.h> > + > +#define MAX(a, b) ((a) > (b) ? (a) : (b)) > +#define MIN(a, b) ((a) > (b) ? (b) : (a)) > + > +/** A single context for all the arithmetic operations. */ > +static decContext decimal_context = { > + /* Maximum precision during operations. */ > + TARANTOOL_MAX_DECIMAL_DIGITS, > + /* > + * Maximum decimal lagarithm of the number. > + * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS > + */ > + TARANTOOL_MAX_DECIMAL_DIGITS - 1, > + /* > + * Minimal adjusted exponent. The smallest absolute value will be > + * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) = > + * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale = > + * TARANTOOL_MAX_DECIMAL_DIGITS > + */ > + -1, > + /* Rounding mode: .5 rounds away from 0. */ > + DEC_ROUND_HALF_UP, > + /* Turn off signalling for failed operations. */ > + 0, > + /* Status holding occured events. Initially empty. */ > + 0, > + /* Turn off exponent clamping. */ > + 0 > +}; > + > +/** > + * Return operation status and clear it for future checks. > + * > + * @return 0 if ok, bitwise or of decNumber errors, if any. > + */ > +static inline uint32_t > +decimal_get_op_status(decContext *context) > +{ > + uint32_t status = decContextGetStatus(context); > + decContextZeroStatus(&decimal_context); > + /* > + * Clear warnings. Every value less than 0.1 is > + * subnormal, DEC_Inexact and DEC_Rounded result > + * from rounding. DEC_Inexact with DEC_Subnormal > + * together result in DEC_Underflow. DEC_Clamped > + * happens after underflow if rounding to zero. > + */ > + return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow | > + DEC_Subnormal | DEC_Clamped); > +} > + > +/** > + * A finalizer for all the operations. > + * Check the operation context status and empty it. > + * > + * @return NULL if finalization failed. > + * result pointer otherwise. > + */ > +static inline decimal * > +decimal_finalize(decimal *res, decContext *context) > +{ > + uint32_t status = decimal_get_op_status(context); > + if (status || ! decNumberIsFinite(res)) { > + return NULL; > + } > + return res; > +} > + > +uint8_t decimal_precision(const decimal *dec) { > + return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) : > + dec->digits + dec->exponent; > +} > + > +uint8_t decimal_scale(const decimal *dec) { > + return dec->exponent < 0 ? -dec->exponent : 0; > +} > + > +decimal * > +decimal_zero(decimal *dec) > +{ > + decNumberZero(dec); > + return dec; > +} > + > +decimal * > +decimal_from_string(decimal *dec, const char *str) > +{ > + decNumberFromString(dec, str, &decimal_context); > + return decimal_finalize(dec, &decimal_context); > +} > + > +decimal * > +decimal_from_int(decimal *dec, int32_t num) > +{ > + decNumberFromInt32(dec, num); > + return decimal_finalize(dec, &decimal_context); > +} > + > +decimal * > +decimal_from_uint(decimal *dec, uint32_t num) > +{ > + decNumberFromUInt32(dec, num); > + return decimal_finalize(dec, &decimal_context); > +} > + > +char * > +decimal_to_string(const decimal *dec, char *buf) > +{ > + return decNumberToString(dec, buf); > +} > + > +int32_t > +decimal_to_int(const decimal *dec) > +{ > + decNumber res; > + decNumberToIntegralValue(&res, dec, &decimal_context); > + return decNumberToInt32(&res, &decimal_context); > +} > + > +uint32_t > +decimal_to_uint(const decimal *dec) > +{ > + decNumber res; > + decNumberToIntegralValue(&res, dec, &decimal_context); > + return decNumberToUInt32(&res, &decimal_context); > +} > + > +int > +decimal_compare(const decimal *lhs, const decimal *rhs) > +{ > + decNumber res; > + decNumberCompare(&res, lhs, rhs, &decimal_context); > + return decNumberToInt32(&res, &decimal_context); > +} > + > +decimal * > +decimal_abs(decimal *res, const decimal *dec) > +{ > + decNumberAbs(res, dec, &decimal_context); > + return res; > +} > + > +decimal * > +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs) > +{ > + decNumberAdd(res, lhs, rhs, &decimal_context); > + > + return decimal_finalize(res, &decimal_context); > +} > + > +decimal * > +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs) > +{ > + decNumberSubtract(res, lhs, rhs, &decimal_context); > + > + return decimal_finalize(res, &decimal_context); > +} > + > +decimal * > +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs) > +{ > + decNumberMultiply(res, lhs, rhs, &decimal_context); > + > + return decimal_finalize(res, &decimal_context); > +} > + > +decimal * > +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs) > +{ > + decNumberDivide(res, lhs, rhs, &decimal_context); > + > + return decimal_finalize(res, &decimal_context); > +} > + > +decimal * > +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision) > +{ > + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) > + return NULL; > + > + /* A separate context to enforce desired precision. */ > + static decContext op_context; > + op_context.digits = precision; > + op_context.emax = op_context.digits - 1; > + op_context.emin = -1; > + > + decNumberLog10(res, lhs, &op_context); > + > + return decimal_finalize(res, &op_context); > +} > + > +decimal * > +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision) > +{ > + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) > + return NULL; > + > + /* A separate context to enforce desired precision. */ > + static decContext op_context; > + op_context.digits = precision; > + op_context.emax = op_context.digits - 1; > + op_context.emin = -1; > + > + decNumberLn(res, lhs, &op_context); > + > + return decimal_finalize(res, &op_context); > +} > + > +decimal * > +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t > precision) +{ > + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) > + return NULL; > + > + /* A separate context to enforce desired precision. */ > + static decContext op_context; > + op_context.digits = precision; > + op_context.emax = op_context.digits - 1; > + op_context.emin = -1; > + > + decNumberPower(res, lhs, rhs, &op_context); > + > + return decimal_finalize(res, &op_context); > +} > + > +decimal * > +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision) > +{ > + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) > + return NULL; > + > + /* A separate context to enforce desired precision. */ > + static decContext op_context; > + op_context.digits = precision; > + op_context.emax = op_context.digits - 1; > + op_context.emin = -1; > + > + decNumberExp(res, lhs, &op_context); > + > + return decimal_finalize(res, &op_context); > +} > + > +decimal * > +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision) > +{ > + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) > + return NULL; > + > + /* A separate context to enforce desired precision. */ > + static decContext op_context; > + op_context.digits = precision; > + op_context.emax = op_context.digits - 1; > + op_context.emin = -1; > + > + decNumberSquareRoot(res, lhs, &op_context); > + > + return decimal_finalize(res, &op_context); > +} > diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h > new file mode 100644 > index 000000000..f2812f500 > --- /dev/null > +++ b/src/lib/core/decimal.h > @@ -0,0 +1,159 @@ > +#ifndef TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED > +#define TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED > +/* > + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. > + * > + * Redistribution and use in source and binary forms, with or > + * without modification, are permitted provided that the following > + * conditions are met: > + * > + * 1. Redistributions of source code must retain the above > + * copyright notice, this list of conditions and the > + * following disclaimer. > + * > + * 2. Redistributions in binary form must reproduce the above > + * copyright notice, this list of conditions and the following > + * disclaimer in the documentation and/or other materials > + * provided with the distribution. > + * > + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL > + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, > + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF > + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF > + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF > + * SUCH DAMAGE. > + */ > +#define TARANTOOL_MAX_DECIMAL_DIGITS 38 > +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS > +#define DECSTRING_NO_EXPONENT > +#include "third_party/decNumber/decNumber.h" > + > +typedef decNumber decimal; > + > +/** > + * @return decimal precision, > + * i.e. the amount of decimal digits in > + * its representation. > + */ > +uint8_t > +decimal_precision(const decimal *dec); > + > +/** > + * @return decimal scale, > + * i.e. the number of decimal digits after > + * the decimal separator. > + */ > +uint8_t > +decimal_scale(const decimal *dec); > + > +/** > + * Initialize a zero decimal number. > + */ > +decimal * > +decimal_zero(decimal *dec); > + > +/** > + * Initialize a decimal with a value from the string. > + * > + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS, > + * but has excess digits in fractional part, it will be rounded. > + * > + * @return NULL if string is invalid or > + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS) > + */ > +decimal * > +decimal_from_string(decimal *dec, const char *str); > + > +/** > + * Initialize a decimal with an integer value. > + * > + * @return NULL if precicion is insufficient to hold > + * the value or precision/scale are out of bounds. > +*/ > +decimal * > +decimal_from_int(decimal *dec, int32_t num); > + > +/** @copydoc decimal_from_int */ > +decimal * > +decimal_from_uint(decimal *dec, uint32_t num); > + > +/** > + * Write the decimal to a string. > + * A string has to be at least dec->digits + 3 bytes in size. > + */ > +char * > +decimal_to_string(const decimal *dec, char *buf); > + > +/** > + * Cast decimal to an integer value. The number will be rounded > + * if it has a fractional part. > + */ > +int32_t > +decimal_to_int(const decimal *dec); > + > +/** @copydoc decimal_to_int */ > +uint32_t > +decimal_to_uint(const decimal *dec); > + > +/** > + * Compare 2 decimal values. > + * @return -1, lhs < rhs, > + * 0, lhs = rhs, > + * 1, lhs > rhs > + */ > +int > +decimal_compare(const decimal *lhs, const decimal *rhs); > + > +/** > + * res is set to the absolute value of dec > + * decimal_abs(&a, &a) is allowed. > + */ > +decimal * > +decimal_abs(decimal *res, const decimal *dec); > + > +/* > + * Arithmetic ops: add, subtract, multiply and divide. > + * Return result pointer on success, NULL on an error (overflow). > + */ > + > +decimal * > +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs); > + > +decimal * > +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs); > + > +decimal * > +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs); > + > +decimal * > +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs); > + > +/* > + * log10, ln, pow, exp, sqrt. > + * Calculate the appropriate function and then round the result > + * to the desired precision. > + * Return result pointer on success, NULL on an error (overflow). > + */ > +decimal * > +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision); > + > +decimal * > +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision); > + > +decimal * > +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t > precision); + > +decimal * > +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision); > + > +decimal * > +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); > + > +#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */ > diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt > index 70be6366c..e3de34b16 100644 > --- a/test/unit/CMakeLists.txt > +++ b/test/unit/CMakeLists.txt > @@ -68,6 +68,8 @@ add_executable(vclock.test vclock.cc) > target_link_libraries(vclock.test vclock unit) > add_executable(xrow.test xrow.cc) > target_link_libraries(xrow.test xrow unit) > +add_executable(decimal.test decimal.c) > +target_link_libraries(decimal.test core unit) > > add_executable(fiber.test fiber.cc) > set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0) > diff --git a/test/unit/decimal.c b/test/unit/decimal.c > new file mode 100644 > index 000000000..adfbead91 > --- /dev/null > +++ b/test/unit/decimal.c > @@ -0,0 +1,168 @@ > +#include "unit.h" > +#include "decimal.h" > +#include <limits.h> > +#include <string.h> > + > +int > +main(void) > +{ > + plan(50); > + > + char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; > + char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; > + char *b = "2.718281828"; > + > + decimal s; > + decimal *ret; > + > + ret = decimal_from_string(&s, b); > + isnt(ret, NULL, "Basic construction from string."); > + decimal_to_string(&s, buf); > + is(strcmp(buf, b), 0, "Correct construction and to_string conversion."); > + > + ret = decimal_from_int(&s, INT_MAX); > + decimal_to_string(&s, buf); > + sprintf(buf2, "%d", INT_MAX); > + is(strcmp(buf, buf2), 0, "Correct construction from INT_MAX."); > + is(decimal_to_int(&s), INT_MAX, "Simple conversion back to int INT_MAX"); > + > + ret = decimal_from_int(&s, INT_MIN); > + decimal_to_string(&s, buf); > + sprintf(buf2, "%d", INT_MIN); > + is(strcmp(buf, buf2), 0, "Correct construction from INT_MIN."); > + is(decimal_to_int(&s), INT_MIN, "Simple conversion bact to int INT_MIN"); > + > + char *up1 = "2.5"; > + char *down1 = "2.49"; > + decimal_from_string(&s, up1); > + is(decimal_to_int(&s), 3, ".5 Rounds up"); > + decimal_from_string(&s, down1); > + is(decimal_to_int(&s), 2, ".49 Rounds down"); > + > + char *l = "1.23456789123456789123456789123456789123"; > + ret = decimal_from_string(&s, l); > + isnt(ret, NULL, "Precision too high. Rounding happens."); > + ok(decimal_precision(&s) == 38 && decimal_scale(&s) == 37 , "Construction > is correct."); + char *ll = "123456789123456789123456789123456789123"; > + ret = decimal_from_string(&s, ll); > + is(ret, NULL, "Precision too high and scale = 0. Cannot round"); > + > + /* 38 digits. */ > + char *long_str = "0.0000000000000000000000000000000000001"; > + ret = decimal_from_string(&s, long_str); > + isnt(ret, NULL, "Construncting the smallest possible number from string"); > + decimal_to_string(&s, buf); > + is(strcmp(buf, long_str), 0, "Correct representation of smallest possible > number"); + > + /* Comparsions. */ > + char *max_str = "3.11", *min_str = "3.0999"; > + decimal max, min; > + decimal_from_string(&max, max_str); > + decimal_from_string(&min, min_str); > + is(decimal_compare(&max, &min), 1, "max > min"); > + is(decimal_compare(&min, &max), -1, "min < max"); > + is(decimal_compare(&max, &max), 0, "max == max"); > + > + ret = decimal_from_string(&s, "-3.456"); > + isnt(ret, NULL, "Construction from negative numbers"); > + decimal_to_string(&s, buf); > + is(strcmp(buf, "-3.456"), 0, "Correct construction for negatives"); > + ret = decimal_abs(&s, &s); > + isnt(ret, NULL, "Abs"); > + decimal_to_string(&s, buf); > + is(strcmp(buf, "3.456"), 0, "Correct abs"); > + > + /* Arithmetic ops. */ > + decimal d, check; > + ret = decimal_from_string(&s, b); > + ret = decimal_from_string(&d, "1.25"); > + sprintf(buf2, "%.9f", 1.25 + 2.718281828); > + ret = decimal_add(&d, &d, &s); > + isnt(ret, NULL, "Simple addition"); > + decimal_to_string(&d, buf); > + is(strcmp(buf, buf2), 0, "Simple addition is correct"); > + > + ret = decimal_sub(&d, &d, &s); > + isnt(ret, NULL, "Simple subtraction"); > + decimal_from_string(&check, "1.25"); > + is(decimal_compare(&d, &check), 0, "Simple subtraction is correct"); > + > + decimal_from_int(&s, 4); > + ret = decimal_mul(&s, &s, &d); > + isnt(ret, NULL, "Simple multiplication"); > + decimal_from_string(&check, "5.0"); > + is(decimal_compare(&s, &check), 0 , "Simple multiplication is correct"); > + > + ret = decimal_div(&s, &s, &d); > + isnt(ret, NULL, "Simple division"); > + decimal_from_string(&check, "4.0"); > + is(decimal_compare(&s, &check), 0, "Simple division is correct"); > + > + /* Math. */ > + ret = decimal_from_string(&s, "40.96"); > + ret = decimal_from_string(&check, "6.4"); > + ret = decimal_sqrt(&s, &s, 2); > + isnt(ret, NULL, "sqrt"); > + is(decimal_compare(&s, &check), 0, "sqrt is correct"); > + > + ret = decimal_from_string(&s, "40.96"); > + ret = decimal_from_string(&d, "0.5"); > + ret = decimal_pow(&s, &s, &d, 2); > + isnt(ret, NULL, "pow"); > + is(decimal_compare(&s, &check), 0, "pow is correct"); > + > + ret = decimal_from_string(&s, "3.000"); > + ret = decimal_exp(&d, &s, 4); > + isnt(ret, NULL, "exp"); > + > + ret = decimal_from_string(&check, "20.09"); > + is(decimal_compare(&d, &check), 0, "exp is correct") > + ret = decimal_ln(&d, &d, 4); > + isnt(ret, NULL, "ln"); > + /* > + * ln is wrong by 1 unit in last position. > + * (3.001 instead of 3.000, 3.0001 instead of 3.0000 and so on) > + */ > + decimal_from_string(&s, "3.001"); > + is(decimal_compare(&d, &s), 0, "ln is correct"); > + > + /* 10^3.5 */ > + ret = decimal_from_string(&s, "3162.27766"); > + ret = decimal_log10(&d, &s, 2); > + isnt(ret, NULL, "log10"); > + ret = decimal_from_string(&check, "3.5"); > + is(decimal_compare(&d, &check), 0, "log10 is correct"); > + > + /* Advanced test. */ > + /* 38 digits. */ > + char *bignum = "33.333333333333333333333333333333333333"; > + char *test = "133.33333333333333333333333333333333333"; > + ret = decimal_from_string(&s, bignum); > + ret = decimal_from_int(&d, 4); > + ret = decimal_mul(&s, &s, &d); > + isnt(ret, NULL, "Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS > digits"); + ret = decimal_from_string(&check, test); > + is(decimal_compare(&s, &check), 0, "Rounding is correct"); > + is(decimal_precision(&s), 38, "Correct precision"); > + is(decimal_scale(&s), 35, "Correct scale"); > + > + char *small = "0.000000000000000000000000001"; > + ret = decimal_from_string(&s, small); > + ret = decimal_mul(&s, &s, &s); > + isnt(ret, NULL, "Rounding too small number to zero"); > + ret = decimal_from_int(&check, 0); > + is(decimal_compare(&s, &check), 0, "Rounding is correct"); > + is(decimal_precision(&s), 38, "Correct precision"); > + is(decimal_scale(&s), 38, "Correct scale"); > + > + decimal_from_string(&s, small); > + decimal_from_string(&d, "10000000000000000000"); > + ret = decimal_div(&s, &s, &d); > + isnt(ret, NULL, "Rounding too small number to zero"); > + is(decimal_compare(&s, &check), 0, "Rounding is correct"); > + decimal_to_string(&s, buf); > + is(decimal_precision(&s), 38, "Correct precision"); > + is(decimal_scale(&s), 38, "Correct scale"); > + > + check_plan(); > +} > diff --git a/test/unit/decimal.result b/test/unit/decimal.result > new file mode 100644 > index 000000000..02bee118a > --- /dev/null > +++ b/test/unit/decimal.result > @@ -0,0 +1,51 @@ > +1..50 > +ok 1 - Basic construction from string. > +ok 2 - Correct construction and to_string conversion. > +ok 3 - Correct construction from INT_MAX. > +ok 4 - Simple conversion back to int INT_MAX > +ok 5 - Correct construction from INT_MIN. > +ok 6 - Simple conversion bact to int INT_MIN > +ok 7 - .5 Rounds up > +ok 8 - .49 Rounds down > +ok 9 - Precision too high. Rounding happens. > +ok 10 - Construction is correct. > +ok 11 - Precision too high and scale = 0. Cannot round > +ok 12 - Construncting the smallest possible number from string > +ok 13 - Correct representation of smallest possible number > +ok 14 - max > min > +ok 15 - min < max > +ok 16 - max == max > +ok 17 - Construction from negative numbers > +ok 18 - Correct construction for negatives > +ok 19 - Abs > +ok 20 - Correct abs > +ok 21 - Simple addition > +ok 22 - Simple addition is correct > +ok 23 - Simple subtraction > +ok 24 - Simple subtraction is correct > +ok 25 - Simple multiplication > +ok 26 - Simple multiplication is correct > +ok 27 - Simple division > +ok 28 - Simple division is correct > +ok 29 - sqrt > +ok 30 - sqrt is correct > +ok 31 - pow > +ok 32 - pow is correct > +ok 33 - exp > +ok 34 - exp is correct > +ok 35 - ln > +ok 36 - ln is correct > +ok 37 - log10 > +ok 38 - log10 is correct > +ok 39 - Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits > +ok 40 - Rounding is correct > +ok 41 - Correct precision > +ok 42 - Correct scale > +ok 43 - Rounding too small number to zero > +ok 44 - Rounding is correct > +ok 45 - Correct precision > +ok 46 - Correct scale > +ok 47 - Rounding too small number to zero > +ok 48 - Rounding is correct > +ok 49 - Correct precision > +ok 50 - Correct scale [-- Attachment #2: This is a digitally signed message part. --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool 2019-06-05 8:11 ` Георгий Кириченко @ 2019-06-05 10:03 ` Serge Petrenko 0 siblings, 0 replies; 12+ messages in thread From: Serge Petrenko @ 2019-06-05 10:03 UTC (permalink / raw) To: Georgy Kirichenko; +Cc: Vladimir Davydov, tarantool-patches, kostja > 5 июня 2019 г., в 11:11, Георгий Кириченко <georgy@tarantool.org> написал(а): > > Overall it looks great but please check travis has an error: > /tarantool/src/lib/core/decimal.c:319:8: error: unused variable ‘tmp’ [- > Werror=unused-variable] > char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec); > > Perhaps, it was caused by assert elimination because of the Release-type > build. Hi! Thank you for review. Fixed. mp_encode_decimal() is actually from the fourth patch. diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c index 7b3b1d8c9..fab3eb866 100644 --- a/src/lib/core/decimal.c +++ b/src/lib/core/decimal.c @@ -319,6 +319,7 @@ mp_encode_decimal(char *data, decimal *dec) char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec); assert(tmp == data); assert(scale == (int32_t)decimal_scale(dec)); + (void)tmp; data += len; return data; } > > On Tuesday, June 4, 2019 7:04:17 PM MSK Serge Petrenko wrote: >> Add fixed-point decimal type to tarantool core. >> Adapt decNumber floating-point decimal library for the purpose, write a >> small wrapper and add unit tests. >> >> A new decimal type is an alias for decNumber numbers from the decNumber >> library. >> Arithmetic operations (+, -, *, /) and some mathematic functions >> (ln, log10, exp, pow, sqrt) are available. >> >> We introduce a single context for all the arithmetic operations >> on decimals, which enforces both number precision and scale to be >> in range [0, 38]. >> >> Every mathematic function has precision as one of its parameters, and >> the results are rounded to fit the desired precision. >> >> Part of #692 >> --- >> CMakeLists.txt | 7 + >> cmake/BuildDecNumber.cmake | 13 ++ >> src/CMakeLists.txt | 1 + >> src/lib/core/CMakeLists.txt | 3 +- >> src/lib/core/decimal.c | 293 ++++++++++++++++++++++++++++++++++++ >> src/lib/core/decimal.h | 159 +++++++++++++++++++ >> test/unit/CMakeLists.txt | 2 + >> test/unit/decimal.c | 168 +++++++++++++++++++++ >> test/unit/decimal.result | 51 +++++++ >> 9 files changed, 696 insertions(+), 1 deletion(-) >> create mode 100644 cmake/BuildDecNumber.cmake >> create mode 100644 src/lib/core/decimal.c >> create mode 100644 src/lib/core/decimal.h >> create mode 100644 test/unit/decimal.c >> create mode 100644 test/unit/decimal.result >> >> diff --git a/CMakeLists.txt b/CMakeLists.txt >> index 7658fc6c9..bfb15effb 100644 >> --- a/CMakeLists.txt >> +++ b/CMakeLists.txt >> @@ -431,6 +431,13 @@ else() >> find_package(MsgPuck) >> endif() >> >> +# >> +# decNumber >> +# >> + >> +include(BuildDecNumber) >> +decnumber_build() >> + >> # >> # LibYAML >> # >> diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake >> new file mode 100644 >> index 000000000..80942fe05 >> --- /dev/null >> +++ b/cmake/BuildDecNumber.cmake >> @@ -0,0 +1,13 @@ >> +# >> +# A macro to build the bundled decNumber lisbrary. >> +macro(decnumber_build) >> + set(decnumber_src >> + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c >> + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c >> + ) >> + >> + add_library(decNumber STATIC ${decnumber_src}) >> + >> + set(DECNUMBER_INCLUDE_DIR ${PROJECT_BINARY_DIR}/third_party/decNumber) >> + unset(decnumber_src) >> +endmacro(decnumber_build) >> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt >> index 183cc04ef..9d196755f 100644 >> --- a/src/CMakeLists.txt >> +++ b/src/CMakeLists.txt >> @@ -13,6 +13,7 @@ include_directories(${MSGPUCK_INCLUDE_DIRS}) >> include_directories(${CURL_INCLUDE_DIRS}) >> include_directories(${ICU_INCLUDE_DIRS}) >> include_directories(${ICONV_INCLUDE_DIRS}) >> +include_directories(${DECNUMBER_INCLUDE_DIR}) >> >> set(LIBUTIL_FREEBSD_SRC ${CMAKE_SOURCE_DIR}/third_party/libutil_freebsd) >> include_directories(${LIBUTIL_FREEBSD_SRC}) >> diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt >> index eb10b11c3..66e430a25 100644 >> --- a/src/lib/core/CMakeLists.txt >> +++ b/src/lib/core/CMakeLists.txt >> @@ -26,6 +26,7 @@ set(core_sources >> trigger.cc >> mpstream.c >> port.c >> + decimal.c >> ) >> >> if (TARGET_OS_NETBSD) >> @@ -37,7 +38,7 @@ endif() >> >> add_library(core STATIC ${core_sources}) >> >> -target_link_libraries(core salad small uri ${LIBEV_LIBRARIES} >> +target_link_libraries(core salad small uri decNumber ${LIBEV_LIBRARIES} >> ${LIBEIO_LIBRARIES} ${LIBCORO_LIBRARIES} >> ${MSGPUCK_LIBRARIES}) >> >> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c >> new file mode 100644 >> index 000000000..12250da95 >> --- /dev/null >> +++ b/src/lib/core/decimal.c >> @@ -0,0 +1,293 @@ >> +/* >> + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. >> + * >> + * Redistribution and use in source and binary forms, with or >> + * without modification, are permitted provided that the following >> + * conditions are met: >> + * >> + * 1. Redistributions of source code must retain the above >> + * copyright notice, this list of conditions and the >> + * following disclaimer. >> + * >> + * 2. Redistributions in binary form must reproduce the above >> + * copyright notice, this list of conditions and the following >> + * disclaimer in the documentation and/or other materials >> + * provided with the distribution. >> + * >> + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND >> + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED >> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL >> + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, >> + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL >> + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR >> + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF >> + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF >> + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF >> + * SUCH DAMAGE. >> + */ >> + >> +#include "decimal.h" >> +#include "third_party/decNumber/decContext.h" >> +#include <stdlib.h> >> +#include <assert.h> >> + >> +#define MAX(a, b) ((a) > (b) ? (a) : (b)) >> +#define MIN(a, b) ((a) > (b) ? (b) : (a)) >> + >> +/** A single context for all the arithmetic operations. */ >> +static decContext decimal_context = { >> + /* Maximum precision during operations. */ >> + TARANTOOL_MAX_DECIMAL_DIGITS, >> + /* >> + * Maximum decimal lagarithm of the number. >> + * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS >> + */ >> + TARANTOOL_MAX_DECIMAL_DIGITS - 1, >> + /* >> + * Minimal adjusted exponent. The smallest absolute value will be >> + * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) = >> + * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale = >> + * TARANTOOL_MAX_DECIMAL_DIGITS >> + */ >> + -1, >> + /* Rounding mode: .5 rounds away from 0. */ >> + DEC_ROUND_HALF_UP, >> + /* Turn off signalling for failed operations. */ >> + 0, >> + /* Status holding occured events. Initially empty. */ >> + 0, >> + /* Turn off exponent clamping. */ >> + 0 >> +}; >> + >> +/** >> + * Return operation status and clear it for future checks. >> + * >> + * @return 0 if ok, bitwise or of decNumber errors, if any. >> + */ >> +static inline uint32_t >> +decimal_get_op_status(decContext *context) >> +{ >> + uint32_t status = decContextGetStatus(context); >> + decContextZeroStatus(&decimal_context); >> + /* >> + * Clear warnings. Every value less than 0.1 is >> + * subnormal, DEC_Inexact and DEC_Rounded result >> + * from rounding. DEC_Inexact with DEC_Subnormal >> + * together result in DEC_Underflow. DEC_Clamped >> + * happens after underflow if rounding to zero. >> + */ >> + return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | > DEC_Underflow | >> + DEC_Subnormal | DEC_Clamped); >> +} >> + >> +/** >> + * A finalizer for all the operations. >> + * Check the operation context status and empty it. >> + * >> + * @return NULL if finalization failed. >> + * result pointer otherwise. >> + */ >> +static inline decimal * >> +decimal_finalize(decimal *res, decContext *context) >> +{ >> + uint32_t status = decimal_get_op_status(context); >> + if (status || ! decNumberIsFinite(res)) { >> + return NULL; >> + } >> + return res; >> +} >> + >> +uint8_t decimal_precision(const decimal *dec) { >> + return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) : >> + dec->digits + dec->exponent; >> +} >> + >> +uint8_t decimal_scale(const decimal *dec) { >> + return dec->exponent < 0 ? -dec->exponent : 0; >> +} >> + >> +decimal * >> +decimal_zero(decimal *dec) >> +{ >> + decNumberZero(dec); >> + return dec; >> +} >> + >> +decimal * >> +decimal_from_string(decimal *dec, const char *str) >> +{ >> + decNumberFromString(dec, str, &decimal_context); >> + return decimal_finalize(dec, &decimal_context); >> +} >> + >> +decimal * >> +decimal_from_int(decimal *dec, int32_t num) >> +{ >> + decNumberFromInt32(dec, num); >> + return decimal_finalize(dec, &decimal_context); >> +} >> + >> +decimal * >> +decimal_from_uint(decimal *dec, uint32_t num) >> +{ >> + decNumberFromUInt32(dec, num); >> + return decimal_finalize(dec, &decimal_context); >> +} >> + >> +char * >> +decimal_to_string(const decimal *dec, char *buf) >> +{ >> + return decNumberToString(dec, buf); >> +} >> + >> +int32_t >> +decimal_to_int(const decimal *dec) >> +{ >> + decNumber res; >> + decNumberToIntegralValue(&res, dec, &decimal_context); >> + return decNumberToInt32(&res, &decimal_context); >> +} >> + >> +uint32_t >> +decimal_to_uint(const decimal *dec) >> +{ >> + decNumber res; >> + decNumberToIntegralValue(&res, dec, &decimal_context); >> + return decNumberToUInt32(&res, &decimal_context); >> +} >> + >> +int >> +decimal_compare(const decimal *lhs, const decimal *rhs) >> +{ >> + decNumber res; >> + decNumberCompare(&res, lhs, rhs, &decimal_context); >> + return decNumberToInt32(&res, &decimal_context); >> +} >> + >> +decimal * >> +decimal_abs(decimal *res, const decimal *dec) >> +{ >> + decNumberAbs(res, dec, &decimal_context); >> + return res; >> +} >> + >> +decimal * >> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs) >> +{ >> + decNumberAdd(res, lhs, rhs, &decimal_context); >> + >> + return decimal_finalize(res, &decimal_context); >> +} >> + >> +decimal * >> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs) >> +{ >> + decNumberSubtract(res, lhs, rhs, &decimal_context); >> + >> + return decimal_finalize(res, &decimal_context); >> +} >> + >> +decimal * >> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs) >> +{ >> + decNumberMultiply(res, lhs, rhs, &decimal_context); >> + >> + return decimal_finalize(res, &decimal_context); >> +} >> + >> +decimal * >> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs) >> +{ >> + decNumberDivide(res, lhs, rhs, &decimal_context); >> + >> + return decimal_finalize(res, &decimal_context); >> +} >> + >> +decimal * >> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision) >> +{ >> + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits = precision; >> + op_context.emax = op_context.digits - 1; >> + op_context.emin = -1; >> + >> + decNumberLog10(res, lhs, &op_context); >> + >> + return decimal_finalize(res, &op_context); >> +} >> + >> +decimal * >> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision) >> +{ >> + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits = precision; >> + op_context.emax = op_context.digits - 1; >> + op_context.emin = -1; >> + >> + decNumberLn(res, lhs, &op_context); >> + >> + return decimal_finalize(res, &op_context); >> +} >> + >> +decimal * >> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t >> precision) +{ >> + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits = precision; >> + op_context.emax = op_context.digits - 1; >> + op_context.emin = -1; >> + >> + decNumberPower(res, lhs, rhs, &op_context); >> + >> + return decimal_finalize(res, &op_context); >> +} >> + >> +decimal * >> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision) >> +{ >> + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits = precision; >> + op_context.emax = op_context.digits - 1; >> + op_context.emin = -1; >> + >> + decNumberExp(res, lhs, &op_context); >> + >> + return decimal_finalize(res, &op_context); >> +} >> + >> +decimal * >> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision) >> +{ >> + if (precision >= TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits = precision; >> + op_context.emax = op_context.digits - 1; >> + op_context.emin = -1; >> + >> + decNumberSquareRoot(res, lhs, &op_context); >> + >> + return decimal_finalize(res, &op_context); >> +} >> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h >> new file mode 100644 >> index 000000000..f2812f500 >> --- /dev/null >> +++ b/src/lib/core/decimal.h >> @@ -0,0 +1,159 @@ >> +#ifndef TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED >> +#define TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED >> +/* >> + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. >> + * >> + * Redistribution and use in source and binary forms, with or >> + * without modification, are permitted provided that the following >> + * conditions are met: >> + * >> + * 1. Redistributions of source code must retain the above >> + * copyright notice, this list of conditions and the >> + * following disclaimer. >> + * >> + * 2. Redistributions in binary form must reproduce the above >> + * copyright notice, this list of conditions and the following >> + * disclaimer in the documentation and/or other materials >> + * provided with the distribution. >> + * >> + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND >> + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED >> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL >> + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, >> + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL >> + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR >> + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF >> + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF >> + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF >> + * SUCH DAMAGE. >> + */ >> +#define TARANTOOL_MAX_DECIMAL_DIGITS 38 >> +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS >> +#define DECSTRING_NO_EXPONENT >> +#include "third_party/decNumber/decNumber.h" >> + >> +typedef decNumber decimal; >> + >> +/** >> + * @return decimal precision, >> + * i.e. the amount of decimal digits in >> + * its representation. >> + */ >> +uint8_t >> +decimal_precision(const decimal *dec); >> + >> +/** >> + * @return decimal scale, >> + * i.e. the number of decimal digits after >> + * the decimal separator. >> + */ >> +uint8_t >> +decimal_scale(const decimal *dec); >> + >> +/** >> + * Initialize a zero decimal number. >> + */ >> +decimal * >> +decimal_zero(decimal *dec); >> + >> +/** >> + * Initialize a decimal with a value from the string. >> + * >> + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS, >> + * but has excess digits in fractional part, it will be rounded. >> + * >> + * @return NULL if string is invalid or >> + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS) >> + */ >> +decimal * >> +decimal_from_string(decimal *dec, const char *str); >> + >> +/** >> + * Initialize a decimal with an integer value. >> + * >> + * @return NULL if precicion is insufficient to hold >> + * the value or precision/scale are out of bounds. >> +*/ >> +decimal * >> +decimal_from_int(decimal *dec, int32_t num); >> + >> +/** @copydoc decimal_from_int */ >> +decimal * >> +decimal_from_uint(decimal *dec, uint32_t num); >> + >> +/** >> + * Write the decimal to a string. >> + * A string has to be at least dec->digits + 3 bytes in size. >> + */ >> +char * >> +decimal_to_string(const decimal *dec, char *buf); >> + >> +/** >> + * Cast decimal to an integer value. The number will be rounded >> + * if it has a fractional part. >> + */ >> +int32_t >> +decimal_to_int(const decimal *dec); >> + >> +/** @copydoc decimal_to_int */ >> +uint32_t >> +decimal_to_uint(const decimal *dec); >> + >> +/** >> + * Compare 2 decimal values. >> + * @return -1, lhs < rhs, >> + * 0, lhs = rhs, >> + * 1, lhs > rhs >> + */ >> +int >> +decimal_compare(const decimal *lhs, const decimal *rhs); >> + >> +/** >> + * res is set to the absolute value of dec >> + * decimal_abs(&a, &a) is allowed. >> + */ >> +decimal * >> +decimal_abs(decimal *res, const decimal *dec); >> + >> +/* >> + * Arithmetic ops: add, subtract, multiply and divide. >> + * Return result pointer on success, NULL on an error (overflow). >> + */ >> + >> +decimal * >> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +decimal * >> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +decimal * >> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +decimal * >> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +/* >> + * log10, ln, pow, exp, sqrt. >> + * Calculate the appropriate function and then round the result >> + * to the desired precision. >> + * Return result pointer on success, NULL on an error (overflow). >> + */ >> +decimal * >> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision); >> + >> +decimal * >> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision); >> + >> +decimal * >> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t >> precision); + >> +decimal * >> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision); >> + >> +decimal * >> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); >> + >> +#endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */ >> diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt >> index 70be6366c..e3de34b16 100644 >> --- a/test/unit/CMakeLists.txt >> +++ b/test/unit/CMakeLists.txt >> @@ -68,6 +68,8 @@ add_executable(vclock.test vclock.cc) >> target_link_libraries(vclock.test vclock unit) >> add_executable(xrow.test xrow.cc) >> target_link_libraries(xrow.test xrow unit) >> +add_executable(decimal.test decimal.c) >> +target_link_libraries(decimal.test core unit) >> >> add_executable(fiber.test fiber.cc) >> set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0) >> diff --git a/test/unit/decimal.c b/test/unit/decimal.c >> new file mode 100644 >> index 000000000..adfbead91 >> --- /dev/null >> +++ b/test/unit/decimal.c >> @@ -0,0 +1,168 @@ >> +#include "unit.h" >> +#include "decimal.h" >> +#include <limits.h> >> +#include <string.h> >> + >> +int >> +main(void) >> +{ >> + plan(50); >> + >> + char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; >> + char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; >> + char *b = "2.718281828"; >> + >> + decimal s; >> + decimal *ret; >> + >> + ret = decimal_from_string(&s, b); >> + isnt(ret, NULL, "Basic construction from string."); >> + decimal_to_string(&s, buf); >> + is(strcmp(buf, b), 0, "Correct construction and to_string > conversion."); >> + >> + ret = decimal_from_int(&s, INT_MAX); >> + decimal_to_string(&s, buf); >> + sprintf(buf2, "%d", INT_MAX); >> + is(strcmp(buf, buf2), 0, "Correct construction from INT_MAX."); >> + is(decimal_to_int(&s), INT_MAX, "Simple conversion back to int > INT_MAX"); >> + >> + ret = decimal_from_int(&s, INT_MIN); >> + decimal_to_string(&s, buf); >> + sprintf(buf2, "%d", INT_MIN); >> + is(strcmp(buf, buf2), 0, "Correct construction from INT_MIN."); >> + is(decimal_to_int(&s), INT_MIN, "Simple conversion bact to int > INT_MIN"); >> + >> + char *up1 = "2.5"; >> + char *down1 = "2.49"; >> + decimal_from_string(&s, up1); >> + is(decimal_to_int(&s), 3, ".5 Rounds up"); >> + decimal_from_string(&s, down1); >> + is(decimal_to_int(&s), 2, ".49 Rounds down"); >> + >> + char *l = "1.23456789123456789123456789123456789123"; >> + ret = decimal_from_string(&s, l); >> + isnt(ret, NULL, "Precision too high. Rounding happens."); >> + ok(decimal_precision(&s) == 38 && decimal_scale(&s) == 37 , > "Construction >> is correct."); + char *ll = > "123456789123456789123456789123456789123"; >> + ret = decimal_from_string(&s, ll); >> + is(ret, NULL, "Precision too high and scale = 0. Cannot round"); >> + >> + /* 38 digits. */ >> + char *long_str = "0.0000000000000000000000000000000000001"; >> + ret = decimal_from_string(&s, long_str); >> + isnt(ret, NULL, "Construncting the smallest possible number from > string"); >> + decimal_to_string(&s, buf); >> + is(strcmp(buf, long_str), 0, "Correct representation of smallest > possible >> number"); + >> + /* Comparsions. */ >> + char *max_str = "3.11", *min_str = "3.0999"; >> + decimal max, min; >> + decimal_from_string(&max, max_str); >> + decimal_from_string(&min, min_str); >> + is(decimal_compare(&max, &min), 1, "max > min"); >> + is(decimal_compare(&min, &max), -1, "min < max"); >> + is(decimal_compare(&max, &max), 0, "max == max"); >> + >> + ret = decimal_from_string(&s, "-3.456"); >> + isnt(ret, NULL, "Construction from negative numbers"); >> + decimal_to_string(&s, buf); >> + is(strcmp(buf, "-3.456"), 0, "Correct construction for negatives"); >> + ret = decimal_abs(&s, &s); >> + isnt(ret, NULL, "Abs"); >> + decimal_to_string(&s, buf); >> + is(strcmp(buf, "3.456"), 0, "Correct abs"); >> + >> + /* Arithmetic ops. */ >> + decimal d, check; >> + ret = decimal_from_string(&s, b); >> + ret = decimal_from_string(&d, "1.25"); >> + sprintf(buf2, "%.9f", 1.25 + 2.718281828); >> + ret = decimal_add(&d, &d, &s); >> + isnt(ret, NULL, "Simple addition"); >> + decimal_to_string(&d, buf); >> + is(strcmp(buf, buf2), 0, "Simple addition is correct"); >> + >> + ret = decimal_sub(&d, &d, &s); >> + isnt(ret, NULL, "Simple subtraction"); >> + decimal_from_string(&check, "1.25"); >> + is(decimal_compare(&d, &check), 0, "Simple subtraction is > correct"); >> + >> + decimal_from_int(&s, 4); >> + ret = decimal_mul(&s, &s, &d); >> + isnt(ret, NULL, "Simple multiplication"); >> + decimal_from_string(&check, "5.0"); >> + is(decimal_compare(&s, &check), 0 , "Simple multiplication is > correct"); >> + >> + ret = decimal_div(&s, &s, &d); >> + isnt(ret, NULL, "Simple division"); >> + decimal_from_string(&check, "4.0"); >> + is(decimal_compare(&s, &check), 0, "Simple division is correct"); >> + >> + /* Math. */ >> + ret = decimal_from_string(&s, "40.96"); >> + ret = decimal_from_string(&check, "6.4"); >> + ret = decimal_sqrt(&s, &s, 2); >> + isnt(ret, NULL, "sqrt"); >> + is(decimal_compare(&s, &check), 0, "sqrt is correct"); >> + >> + ret = decimal_from_string(&s, "40.96"); >> + ret = decimal_from_string(&d, "0.5"); >> + ret = decimal_pow(&s, &s, &d, 2); >> + isnt(ret, NULL, "pow"); >> + is(decimal_compare(&s, &check), 0, "pow is correct"); >> + >> + ret = decimal_from_string(&s, "3.000"); >> + ret = decimal_exp(&d, &s, 4); >> + isnt(ret, NULL, "exp"); >> + >> + ret = decimal_from_string(&check, "20.09"); >> + is(decimal_compare(&d, &check), 0, "exp is correct") >> + ret = decimal_ln(&d, &d, 4); >> + isnt(ret, NULL, "ln"); >> + /* >> + * ln is wrong by 1 unit in last position. >> + * (3.001 instead of 3.000, 3.0001 instead of 3.0000 and so on) >> + */ >> + decimal_from_string(&s, "3.001"); >> + is(decimal_compare(&d, &s), 0, "ln is correct"); >> + >> + /* 10^3.5 */ >> + ret = decimal_from_string(&s, "3162.27766"); >> + ret = decimal_log10(&d, &s, 2); >> + isnt(ret, NULL, "log10"); >> + ret = decimal_from_string(&check, "3.5"); >> + is(decimal_compare(&d, &check), 0, "log10 is correct"); >> + >> + /* Advanced test. */ >> + /* 38 digits. */ >> + char *bignum = "33.333333333333333333333333333333333333"; >> + char *test = "133.33333333333333333333333333333333333"; >> + ret = decimal_from_string(&s, bignum); >> + ret = decimal_from_int(&d, 4); >> + ret = decimal_mul(&s, &s, &d); >> + isnt(ret, NULL, "Rounding when more than > TARANTOOL_MAX_DECIMAL_DIGITS >> digits"); + ret = decimal_from_string(&check, test); >> + is(decimal_compare(&s, &check), 0, "Rounding is correct"); >> + is(decimal_precision(&s), 38, "Correct precision"); >> + is(decimal_scale(&s), 35, "Correct scale"); >> + >> + char *small = "0.000000000000000000000000001"; >> + ret = decimal_from_string(&s, small); >> + ret = decimal_mul(&s, &s, &s); >> + isnt(ret, NULL, "Rounding too small number to zero"); >> + ret = decimal_from_int(&check, 0); >> + is(decimal_compare(&s, &check), 0, "Rounding is correct"); >> + is(decimal_precision(&s), 38, "Correct precision"); >> + is(decimal_scale(&s), 38, "Correct scale"); >> + >> + decimal_from_string(&s, small); >> + decimal_from_string(&d, "10000000000000000000"); >> + ret = decimal_div(&s, &s, &d); >> + isnt(ret, NULL, "Rounding too small number to zero"); >> + is(decimal_compare(&s, &check), 0, "Rounding is correct"); >> + decimal_to_string(&s, buf); >> + is(decimal_precision(&s), 38, "Correct precision"); >> + is(decimal_scale(&s), 38, "Correct scale"); >> + >> + check_plan(); >> +} >> diff --git a/test/unit/decimal.result b/test/unit/decimal.result >> new file mode 100644 >> index 000000000..02bee118a >> --- /dev/null >> +++ b/test/unit/decimal.result >> @@ -0,0 +1,51 @@ >> +1..50 >> +ok 1 - Basic construction from string. >> +ok 2 - Correct construction and to_string conversion. >> +ok 3 - Correct construction from INT_MAX. >> +ok 4 - Simple conversion back to int INT_MAX >> +ok 5 - Correct construction from INT_MIN. >> +ok 6 - Simple conversion bact to int INT_MIN >> +ok 7 - .5 Rounds up >> +ok 8 - .49 Rounds down >> +ok 9 - Precision too high. Rounding happens. >> +ok 10 - Construction is correct. >> +ok 11 - Precision too high and scale = 0. Cannot round >> +ok 12 - Construncting the smallest possible number from string >> +ok 13 - Correct representation of smallest possible number >> +ok 14 - max > min >> +ok 15 - min < max >> +ok 16 - max == max >> +ok 17 - Construction from negative numbers >> +ok 18 - Correct construction for negatives >> +ok 19 - Abs >> +ok 20 - Correct abs >> +ok 21 - Simple addition >> +ok 22 - Simple addition is correct >> +ok 23 - Simple subtraction >> +ok 24 - Simple subtraction is correct >> +ok 25 - Simple multiplication >> +ok 26 - Simple multiplication is correct >> +ok 27 - Simple division >> +ok 28 - Simple division is correct >> +ok 29 - sqrt >> +ok 30 - sqrt is correct >> +ok 31 - pow >> +ok 32 - pow is correct >> +ok 33 - exp >> +ok 34 - exp is correct >> +ok 35 - ln >> +ok 36 - ln is correct >> +ok 37 - log10 >> +ok 38 - log10 is correct >> +ok 39 - Rounding when more than TARANTOOL_MAX_DECIMAL_DIGITS digits >> +ok 40 - Rounding is correct >> +ok 41 - Correct precision >> +ok 42 - Correct scale >> +ok 43 - Rounding too small number to zero >> +ok 44 - Rounding is correct >> +ok 45 - Correct precision >> +ok 46 - Correct scale >> +ok 47 - Rounding too small number to zero >> +ok 48 - Rounding is correct >> +ok 49 - Correct precision >> +ok 50 - Correct scale > ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool 2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko 2019-06-05 8:11 ` Георгий Кириченко @ 2019-06-05 12:19 ` Vladimir Davydov 2019-06-11 16:14 ` [tarantool-patches] " Serge Petrenko 1 sibling, 1 reply; 12+ messages in thread From: Vladimir Davydov @ 2019-06-05 12:19 UTC (permalink / raw) To: Serge Petrenko; +Cc: georgy, tarantool-patches, kostja On Tue, Jun 04, 2019 at 07:04:17PM +0300, Serge Petrenko wrote: > Add fixed-point decimal type to tarantool core. > Adapt decNumber floating-point decimal library for the purpose, write a > small wrapper and add unit tests. > > A new decimal type is an alias for decNumber numbers from the decNumber > library. > Arithmetic operations (+, -, *, /) and some mathematic functions > (ln, log10, exp, pow, sqrt) are available. > > We introduce a single context for all the arithmetic operations > on decimals, which enforces both number precision and scale to be > in range [0, 38]. > > Every mathematic function has precision as one of its parameters, and > the results are rounded to fit the desired precision. > > Part of #692 Are NaN and inf supported? I assume not. Please mention it in the commit log. > diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c > new file mode 100644 > index 000000000..12250da95 > --- /dev/null > +++ b/src/lib/core/decimal.c > @@ -0,0 +1,293 @@ > +#include "decimal.h" > +#include "third_party/decNumber/decContext.h" > +#include <stdlib.h> Do you really need stdlib.h? Isn't stddef.h enough? Also, you use intN_t hence you should include stdint.h. I'm not sure you really need to use fixed size ints in this module though. > +#include <assert.h> > + > +#define MAX(a, b) ((a) > (b) ? (a) : (b)) > +#define MIN(a, b) ((a) > (b) ? (b) : (a)) Why not include trivial/util.h instead? > + > +/** A single context for all the arithmetic operations. */ > +static decContext decimal_context = { > + /* Maximum precision during operations. */ > + TARANTOOL_MAX_DECIMAL_DIGITS, > + /* > + * Maximum decimal lagarithm of the number. > + * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS > + */ > + TARANTOOL_MAX_DECIMAL_DIGITS - 1, > + /* > + * Minimal adjusted exponent. The smallest absolute value will be > + * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) = > + * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale = > + * TARANTOOL_MAX_DECIMAL_DIGITS > + */ > + -1, > + /* Rounding mode: .5 rounds away from 0. */ > + DEC_ROUND_HALF_UP, > + /* Turn off signalling for failed operations. */ > + 0, > + /* Status holding occured events. Initially empty. */ > + 0, > + /* Turn off exponent clamping. */ > + 0 > +}; > + > +/** > + * Return operation status and clear it for future checks. > + * > + * @return 0 if ok, bitwise or of decNumber errors, if any. > + */ > +static inline uint32_t > +decimal_get_op_status(decContext *context) > +{ > + uint32_t status = decContextGetStatus(context); > + decContextZeroStatus(&decimal_context); Should be 'context', not decimal_context? > + /* > + * Clear warnings. Every value less than 0.1 is > + * subnormal, DEC_Inexact and DEC_Rounded result > + * from rounding. DEC_Inexact with DEC_Subnormal > + * together result in DEC_Underflow. DEC_Clamped > + * happens after underflow if rounding to zero. > + */ > + return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow | > + DEC_Subnormal | DEC_Clamped); I'd fold this function in decimal_finalize as it's short and you don't use it anywhere else. > +} > + > +/** > + * A finalizer for all the operations. > + * Check the operation context status and empty it. > + * > + * @return NULL if finalization failed. > + * result pointer otherwise. > + */ > +static inline decimal * > +decimal_finalize(decimal *res, decContext *context) > +{ > + uint32_t status = decimal_get_op_status(context); > + if (status || ! decNumberIsFinite(res)) { Do we really need decNumberIsFinite here? According to the context, you don't allow inf. > + return NULL; > + } > + return res; > +} > + > +uint8_t decimal_precision(const decimal *dec) { > + return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) : > + dec->digits + dec->exponent; > +} > + > +uint8_t decimal_scale(const decimal *dec) { > + return dec->exponent < 0 ? -dec->exponent : 0; > +} > + > +decimal * > +decimal_zero(decimal *dec) > +{ > + decNumberZero(dec); > + return dec; > +} > + > +decimal * > +decimal_from_string(decimal *dec, const char *str) > +{ > + decNumberFromString(dec, str, &decimal_context); > + return decimal_finalize(dec, &decimal_context); > +} It's unsafe to use a global context in all these functions as they might be used from different threads while the context is used for propagating errors. I think you should copy it to stack. > +int > +decimal_compare(const decimal *lhs, const decimal *rhs) > +{ > + decNumber res; > + decNumberCompare(&res, lhs, rhs, &decimal_context); > + return decNumberToInt32(&res, &decimal_context); > +} I'd add an assertion here checking the context status if this function aren't supposed to fail. The same is fair to all other functions that must not fail. > diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h > new file mode 100644 > index 000000000..f2812f500 > --- /dev/null > +++ b/src/lib/core/decimal.h > @@ -0,0 +1,159 @@ > +#define TARANTOOL_MAX_DECIMAL_DIGITS 38 This macro definition must be accompanied by a comment. Also, I don't see any point in adding TARANTOOL_ prefix - you don't use the prefix with the object type so using it with one constant looks inconsistent. Let's rename it to DECIMAL_MAX_DIGITS? > +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS > +#define DECSTRING_NO_EXPONENT AFAICS this macro is defined by default so there's no need to define it here. > +#include "third_party/decNumber/decNumber.h" > + > +typedef decNumber decimal; We typically add _t suffix to opaque types. > + > +/** > + * @return decimal precision, > + * i.e. the amount of decimal digits in > + * its representation. > + */ > +uint8_t > +decimal_precision(const decimal *dec); We prefer using int unless we really need fixed size ints for some reason. > + > +/** > + * @return decimal scale, > + * i.e. the number of decimal digits after > + * the decimal separator. > + */ > +uint8_t > +decimal_scale(const decimal *dec); > + > +/** > + * Initialize a zero decimal number. > + */ > +decimal * > +decimal_zero(decimal *dec); > + > +/** > + * Initialize a decimal with a value from the string. > + * > + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS, > + * but has excess digits in fractional part, it will be rounded. > + * > + * @return NULL if string is invalid or > + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS) > + */ > +decimal * > +decimal_from_string(decimal *dec, const char *str); AFAIK in SQL one can set column type to DECIMAL(precision,scale), i.e. specify the precision/scale explicitly. An attempt to insert a value that doesn't fit results in an error. That said, we might need to allow specifying precision/scale here. Not sure about this. Please consult SQL guys. > + > +/** > + * Initialize a decimal with an integer value. > + * > + * @return NULL if precicion is insufficient to hold > + * the value or precision/scale are out of bounds. > +*/ > +decimal * > +decimal_from_int(decimal *dec, int32_t num); > + > +/** @copydoc decimal_from_int */ > +decimal * > +decimal_from_uint(decimal *dec, uint32_t num); Why int32_t? Why not int64_t? Anyway, why do you need this operations at all? Are you going to try to compare decimals with numbers when the indexed field is scalar? That is, are you going to make decimal a part of MP_CLASS_NUMBER? If so, you need _from_float() helpers, too. Or are planning to arrange decimals before any simple number? > + > +/** > + * Write the decimal to a string. > + * A string has to be at least dec->digits + 3 bytes in size. > + */ > +char * > +decimal_to_string(const decimal *dec, char *buf); This is kinda inconsistent with others formatting routines, e.g. take a look at vclock_to_string: - there should be decimal_snprint which is an snprintf like function that dumps decimal to the provided buffer. - there should also be decimal_to_string wrapper which formats a decimal on a static buffer (tt_static_buf). > + > +/** > + * Cast decimal to an integer value. The number will be rounded > + * if it has a fractional part. > + */ > +int32_t > +decimal_to_int(const decimal *dec); > + > +/** @copydoc decimal_to_int */ > +uint32_t > +decimal_to_uint(const decimal *dec); These functions can easily fail, but the comment says nothing about it. Looks like there's no way to figure that out. Anyway, why do we need these functions at all? > + > +/** > + * Compare 2 decimal values. > + * @return -1, lhs < rhs, > + * 0, lhs = rhs, > + * 1, lhs > rhs > + */ > +int > +decimal_compare(const decimal *lhs, const decimal *rhs); > + > +/** > + * res is set to the absolute value of dec > + * decimal_abs(&a, &a) is allowed. > + */ > +decimal * > +decimal_abs(decimal *res, const decimal *dec); What about unary minus? > + > +/* > + * Arithmetic ops: add, subtract, multiply and divide. > + * Return result pointer on success, NULL on an error (overflow). > + */ > + > +decimal * > +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs); > + > +decimal * > +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs); > + > +decimal * > +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs); > + > +decimal * > +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs); > + > +/* > + * log10, ln, pow, exp, sqrt. > + * Calculate the appropriate function and then round the result > + * to the desired precision. > + * Return result pointer on success, NULL on an error (overflow). > + */ > +decimal * > +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision); > + > +decimal * > +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision); I'd expect to see decimal_log. Yeah, I know that log(a, b) = log(c, b) / log(c, a) but a helper function would come in handy. > + > +decimal * > +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision); > + > +decimal * > +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision); > + > +decimal * > +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); What's the point in specifying precision for these functions? Wouldn't having decimal_round be better? Why doesn't decimal_div have this option then? > diff --git a/test/unit/decimal.c b/test/unit/decimal.c > new file mode 100644 > index 000000000..adfbead91 > --- /dev/null > +++ b/test/unit/decimal.c > @@ -0,0 +1,168 @@ > + decimal_from_int(&s, 4); > + ret = decimal_mul(&s, &s, &d); > + isnt(ret, NULL, "Simple multiplication"); Please also test all possible overflow/underflow cases. ^ permalink raw reply [flat|nested] 12+ messages in thread
* [tarantool-patches] Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool 2019-06-05 12:19 ` Vladimir Davydov @ 2019-06-11 16:14 ` Serge Petrenko 0 siblings, 0 replies; 12+ messages in thread From: Serge Petrenko @ 2019-06-11 16:14 UTC (permalink / raw) To: Vladimir Davydov; +Cc: Georgy Kirichenko, tarantool-patches, kostja Hi! Thank you for review. I addressed your comments and sent v4. > 5 июня 2019 г., в 15:19, Vladimir Davydov <vdavydov.dev@gmail.com> написал(а): > > On Tue, Jun 04, 2019 at 07:04:17PM +0300, Serge Petrenko wrote: >> Add fixed-point decimal type to tarantool core. >> Adapt decNumber floating-point decimal library for the purpose, write a >> small wrapper and add unit tests. >> >> A new decimal type is an alias for decNumber numbers from the decNumber >> library. >> Arithmetic operations (+, -, *, /) and some mathematic functions >> (ln, log10, exp, pow, sqrt) are available. >> >> We introduce a single context for all the arithmetic operations >> on decimals, which enforces both number precision and scale to be >> in range [0, 38]. >> >> Every mathematic function has precision as one of its parameters, and >> the results are rounded to fit the desired precision. >> >> Part of #692 > > Are NaN and inf supported? I assume not. Please mention it in > the commit log. Done. > >> diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c >> new file mode 100644 >> index 000000000..12250da95 >> --- /dev/null >> +++ b/src/lib/core/decimal.c >> @@ -0,0 +1,293 @@ > >> +#include "decimal.h" >> +#include "third_party/decNumber/decContext.h" >> +#include <stdlib.h> > > Do you really need stdlib.h? Isn't stddef.h enough? > > Also, you use intN_t hence you should include stdint.h. > I'm not sure you really need to use fixed size ints in > this module though. I need fixed size ints since decNumber library uses them in conversions: decNumberToInt32 (decNumberToInt64) > >> +#include <assert.h> >> + >> +#define MAX(a, b) ((a) > (b) ? (a) : (b)) >> +#define MIN(a, b) ((a) > (b) ? (b) : (a)) > > Why not include trivial/util.h instead? Indeed, done. > >> + >> +/** A single context for all the arithmetic operations. */ >> +static decContext decimal_context = { >> + /* Maximum precision during operations. */ >> + TARANTOOL_MAX_DECIMAL_DIGITS, >> + /* >> + * Maximum decimal lagarithm of the number. >> + * Allows for precision = TARANTOOL_MAX_DECIMAL_DIGITS >> + */ >> + TARANTOOL_MAX_DECIMAL_DIGITS - 1, >> + /* >> + * Minimal adjusted exponent. The smallest absolute value will be >> + * exp((1 - TARANTOOL_MAX_DECIMAL_DIGITS) - 1) = >> + * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale = >> + * TARANTOOL_MAX_DECIMAL_DIGITS >> + */ >> + -1, >> + /* Rounding mode: .5 rounds away from 0. */ >> + DEC_ROUND_HALF_UP, >> + /* Turn off signalling for failed operations. */ >> + 0, >> + /* Status holding occured events. Initially empty. */ >> + 0, >> + /* Turn off exponent clamping. */ >> + 0 >> +}; >> + >> +/** >> + * Return operation status and clear it for future checks. >> + * >> + * @return 0 if ok, bitwise or of decNumber errors, if any. >> + */ >> +static inline uint32_t >> +decimal_get_op_status(decContext *context) >> +{ >> + uint32_t status = decContextGetStatus(context); >> + decContextZeroStatus(&decimal_context); > > Should be 'context', not decimal_context? You’re right, fixed. > >> + /* >> + * Clear warnings. Every value less than 0.1 is >> + * subnormal, DEC_Inexact and DEC_Rounded result >> + * from rounding. DEC_Inexact with DEC_Subnormal >> + * together result in DEC_Underflow. DEC_Clamped >> + * happens after underflow if rounding to zero. >> + */ >> + return status & ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow | >> + DEC_Subnormal | DEC_Clamped); > > I'd fold this function in decimal_finalize as it's short and you don't > use it anywhere else. > Done. >> +} >> + >> +/** >> + * A finalizer for all the operations. >> + * Check the operation context status and empty it. >> + * >> + * @return NULL if finalization failed. >> + * result pointer otherwise. >> + */ >> +static inline decimal * >> +decimal_finalize(decimal *res, decContext *context) >> +{ >> + uint32_t status = decimal_get_op_status(context); >> + if (status || ! decNumberIsFinite(res)) { > > Do we really need decNumberIsFinite here? According to the context, > you don't allow inf. Fixed. > >> + return NULL; >> + } >> + return res; >> +} >> + >> +uint8_t decimal_precision(const decimal *dec) { >> + return dec->exponent <= 0 ? MAX(dec->digits, -dec->exponent) : >> + dec->digits + dec->exponent; >> +} >> + >> +uint8_t decimal_scale(const decimal *dec) { >> + return dec->exponent < 0 ? -dec->exponent : 0; >> +} >> + >> +decimal * >> +decimal_zero(decimal *dec) >> +{ >> + decNumberZero(dec); >> + return dec; >> +} >> + >> +decimal * >> +decimal_from_string(decimal *dec, const char *str) >> +{ >> + decNumberFromString(dec, str, &decimal_context); >> + return decimal_finalize(dec, &decimal_context); >> +} > > It's unsafe to use a global context in all these functions as they might > be used from different threads while the context is used for propagating > errors. I think you should copy it to stack. Added `__thread` specifier. > >> +int >> +decimal_compare(const decimal *lhs, const decimal *rhs) >> +{ >> + decNumber res; >> + decNumberCompare(&res, lhs, rhs, &decimal_context); >> + return decNumberToInt32(&res, &decimal_context); >> +} > > I'd add an assertion here checking the context status if this function > aren't supposed to fail. The same is fair to all other functions that > must not fail. Ok, done. > >> diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h >> new file mode 100644 >> index 000000000..f2812f500 >> --- /dev/null >> +++ b/src/lib/core/decimal.h >> @@ -0,0 +1,159 @@ > >> +#define TARANTOOL_MAX_DECIMAL_DIGITS 38 > > This macro definition must be accompanied by a comment. Also, I don't > see any point in adding TARANTOOL_ prefix - you don't use the prefix > with the object type so using it with one constant looks inconsistent. > Let's rename it to DECIMAL_MAX_DIGITS? Done. > >> +#define DECNUMDIGITS TARANTOOL_MAX_DECIMAL_DIGITS > >> +#define DECSTRING_NO_EXPONENT > > AFAICS this macro is defined by default so there's no need to define it > here. > >> +#include "third_party/decNumber/decNumber.h" >> + >> +typedef decNumber decimal; > > We typically add _t suffix to opaque types. Done. > >> + >> +/** >> + * @return decimal precision, >> + * i.e. the amount of decimal digits in >> + * its representation. >> + */ >> +uint8_t >> +decimal_precision(const decimal *dec); > > We prefer using int unless we really need fixed size ints > for some reason. decimal_precision() and decimal_scale() return int from now on. I left fixed size ints in some other places where they seem appropriate. > >> + >> +/** >> + * @return decimal scale, >> + * i.e. the number of decimal digits after >> + * the decimal separator. >> + */ >> +uint8_t >> +decimal_scale(const decimal *dec); >> + >> +/** >> + * Initialize a zero decimal number. >> + */ >> +decimal * >> +decimal_zero(decimal *dec); >> + >> +/** >> + * Initialize a decimal with a value from the string. >> + * >> + * If the number is less, than 10^TARANTOOL_MAX_DECIMAL_DIGITS, >> + * but has excess digits in fractional part, it will be rounded. >> + * >> + * @return NULL if string is invalid or >> + * the number is too big (>= 10^TARANTOOL_MAX_DECIMAL_DIGITS) >> + */ >> +decimal * >> +decimal_from_string(decimal *dec, const char *str); > > AFAIK in SQL one can set column type to DECIMAL(precision,scale), i.e. > specify the precision/scale explicitly. An attempt to insert a value > that doesn't fit results in an error. That said, we might need to allow > specifying precision/scale here. Not sure about this. Please consult > SQL guys. I guess this check can be done after a decimal is constructed. Precision and scale meet the ones of the string for now. > >> + >> +/** >> + * Initialize a decimal with an integer value. >> + * >> + * @return NULL if precicion is insufficient to hold >> + * the value or precision/scale are out of bounds. >> +*/ >> +decimal * >> +decimal_from_int(decimal *dec, int32_t num); >> + >> +/** @copydoc decimal_from_int */ >> +decimal * >> +decimal_from_uint(decimal *dec, uint32_t num); > > Why int32_t? Why not int64_t? Anyway, why do you need this operations > at all? Are you going to try to compare decimals with numbers when > the indexed field is scalar? That is, are you going to make decimal > a part of MP_CLASS_NUMBER? If so, you need _from_float() helpers, too. > Or are planning to arrange decimals before any simple number? Added to/from_double and from int64_4 helpers. Yep, we’re going to compare decimals with doubles, and ints. > >> + >> +/** >> + * Write the decimal to a string. >> + * A string has to be at least dec->digits + 3 bytes in size. >> + */ >> +char * >> +decimal_to_string(const decimal *dec, char *buf); > > This is kinda inconsistent with others formatting routines, e.g. > take a look at vclock_to_string: > > - there should be decimal_snprint which is an snprintf like function > that dumps decimal to the provided buffer. > - there should also be decimal_to_string wrapper which formats a > decimal on a static buffer (tt_static_buf). Fixed according to a verbal discussion. > >> + >> +/** >> + * Cast decimal to an integer value. The number will be rounded >> + * if it has a fractional part. >> + */ >> +int32_t >> +decimal_to_int(const decimal *dec); >> + >> +/** @copydoc decimal_to_int */ >> +uint32_t >> +decimal_to_uint(const decimal *dec); > > These functions can easily fail, but the comment says nothing about it. > Looks like there's no way to figure that out. Anyway, why do we need > these functions at all? Removed the to_uint and to_int helpers. > >> + >> +/** >> + * Compare 2 decimal values. >> + * @return -1, lhs < rhs, >> + * 0, lhs = rhs, >> + * 1, lhs > rhs >> + */ >> +int >> +decimal_compare(const decimal *lhs, const decimal *rhs); >> + >> +/** >> + * res is set to the absolute value of dec >> + * decimal_abs(&a, &a) is allowed. >> + */ >> +decimal * >> +decimal_abs(decimal *res, const decimal *dec); > > What about unary minus? Implemented. > >> + >> +/* >> + * Arithmetic ops: add, subtract, multiply and divide. >> + * Return result pointer on success, NULL on an error (overflow). >> + */ >> + >> +decimal * >> +decimal_add(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +decimal * >> +decimal_sub(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +decimal * >> +decimal_mul(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +decimal * >> +decimal_div(decimal *res, const decimal *lhs, const decimal *rhs); >> + >> +/* >> + * log10, ln, pow, exp, sqrt. >> + * Calculate the appropriate function and then round the result >> + * to the desired precision. >> + * Return result pointer on success, NULL on an error (overflow). >> + */ >> +decimal * >> +decimal_log10(decimal *res, const decimal *lhs, uint32_t precision); >> + >> +decimal * >> +decimal_ln(decimal *res, const decimal *lhs, uint32_t precision); > > I'd expect to see decimal_log. Yeah, I know that > > log(a, b) = log(c, b) / log(c, a) > > but a helper function would come in handy. The helper will perform exactly the same division. This can be done in a lua wrapper, I guess. > >> + >> +decimal * >> +decimal_pow(decimal *res, const decimal *lhs, const decimal *rhs, uint32_t precision); >> + >> +decimal * >> +decimal_exp(decimal *res, const decimal *lhs, uint32_t precision); >> + >> +decimal * >> +decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); > > What's the point in specifying precision for these functions? Wouldn't > having decimal_round be better? Why doesn't decimal_div have this option > then? Added decimal_round(), removed precision from math functions. > >> diff --git a/test/unit/decimal.c b/test/unit/decimal.c >> new file mode 100644 >> index 000000000..adfbead91 >> --- /dev/null >> +++ b/test/unit/decimal.c >> @@ -0,0 +1,168 @@ > >> + decimal_from_int(&s, 4); >> + ret = decimal_mul(&s, &s, &d); >> + isnt(ret, NULL, "Simple multiplication"); > > Please also test all possible overflow/underflow cases. Reworked the test. -- Serge Petrenko sergepetrenko@tarantool.org ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 3/4] lib: update msgpuck library 2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko @ 2019-06-04 16:04 ` Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko 3 siblings, 0 replies; 12+ messages in thread From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw) To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko Provide msgpack ext type handling. Needed for #692 --- src/lib/msgpuck | 2 +- test/unit/msgpack.result | 480 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 468 insertions(+), 14 deletions(-) diff --git a/src/lib/msgpuck b/src/lib/msgpuck index 22f3fc901..8ae606a16 160000 --- a/src/lib/msgpuck +++ b/src/lib/msgpuck @@ -1 +1 @@ -Subproject commit 22f3fc901baf13fc9e8563e6fe0242b9f352114b +Subproject commit 8ae606a1636dd89b2d61b154e5a1db03dce91657 diff --git a/test/unit/msgpack.result b/test/unit/msgpack.result index 385e7da0e..02c0b05fd 100644 --- a/test/unit/msgpack.result +++ b/test/unit/msgpack.result @@ -1,4 +1,4 @@ -1..20 +1..22 1..135 # *** test_uints *** # uint 0U @@ -620,6 +620,206 @@ ok 7 - subtests ok 78 - mp_encode(0xffffffffU) == "\xc6\xff\xff\xff\xff" # *** test_binls: done *** ok 8 - subtests + 1..168 + # *** test_extls *** + # extl 0x01U + ok 1 - mp_check_extl(0x01U) == 0 + ok 2 - mp_decode(mp_encode(0x01U)) == 0x01U + ok 3 - len(mp_encode_extl(0x01U) + ok 4 - len(mp_decode_extl(0x01U)) + ok 5 - mp_sizeof_extl(0x01U) + ok 6 - mp_encode(0x01U) == "\xd4\x00" + # extl 0x02U + ok 7 - mp_check_extl(0x02U) == 0 + ok 8 - mp_decode(mp_encode(0x02U)) == 0x02U + ok 9 - len(mp_encode_extl(0x02U) + ok 10 - len(mp_decode_extl(0x02U)) + ok 11 - mp_sizeof_extl(0x02U) + ok 12 - mp_encode(0x02U) == "\xd5\x00" + # extl 0x04U + ok 13 - mp_check_extl(0x04U) == 0 + ok 14 - mp_decode(mp_encode(0x04U)) == 0x04U + ok 15 - len(mp_encode_extl(0x04U) + ok 16 - len(mp_decode_extl(0x04U)) + ok 17 - mp_sizeof_extl(0x04U) + ok 18 - mp_encode(0x04U) == "\xd6\x00" + # extl 0x08U + ok 19 - mp_check_extl(0x08U) == 0 + ok 20 - mp_decode(mp_encode(0x08U)) == 0x08U + ok 21 - len(mp_encode_extl(0x08U) + ok 22 - len(mp_decode_extl(0x08U)) + ok 23 - mp_sizeof_extl(0x08U) + ok 24 - mp_encode(0x08U) == "\xd7\x00" + # extl 0x10U + ok 25 - mp_check_extl(0x10U) == 0 + ok 26 - mp_decode(mp_encode(0x10U)) == 0x10U + ok 27 - len(mp_encode_extl(0x10U) + ok 28 - len(mp_decode_extl(0x10U)) + ok 29 - mp_sizeof_extl(0x10U) + ok 30 - mp_encode(0x10U) == "\xd8\x00" + # extl 0x11U + ok 31 - mp_check_extl(0x11U) == 0 + ok 32 - mp_decode(mp_encode(0x11U)) == 0x11U + ok 33 - len(mp_encode_extl(0x11U) + ok 34 - len(mp_decode_extl(0x11U)) + ok 35 - mp_sizeof_extl(0x11U) + ok 36 - mp_encode(0x11U) == "\xc7\x11\x00" + # extl 0xfeU + ok 37 - mp_check_extl(0xfeU) == 0 + ok 38 - mp_decode(mp_encode(0xfeU)) == 0xfeU + ok 39 - len(mp_encode_extl(0xfeU) + ok 40 - len(mp_decode_extl(0xfeU)) + ok 41 - mp_sizeof_extl(0xfeU) + ok 42 - mp_encode(0xfeU) == "\xc7\xfe\x00" + # extl 0xffU + ok 43 - mp_check_extl(0xffU) == 0 + ok 44 - mp_decode(mp_encode(0xffU)) == 0xffU + ok 45 - len(mp_encode_extl(0xffU) + ok 46 - len(mp_decode_extl(0xffU)) + ok 47 - mp_sizeof_extl(0xffU) + ok 48 - mp_encode(0xffU) == "\xc7\xff\x00" + # extl 0x00U + ok 49 - mp_check_extl(0x00U) == 0 + ok 50 - mp_decode(mp_encode(0x00U)) == 0x00U + ok 51 - len(mp_encode_extl(0x00U) + ok 52 - len(mp_decode_extl(0x00U)) + ok 53 - mp_sizeof_extl(0x00U) + ok 54 - mp_encode(0x00U) == "\xc7\x00\x00" + # extl 0x03U + ok 55 - mp_check_extl(0x03U) == 0 + ok 56 - mp_decode(mp_encode(0x03U)) == 0x03U + ok 57 - len(mp_encode_extl(0x03U) + ok 58 - len(mp_decode_extl(0x03U)) + ok 59 - mp_sizeof_extl(0x03U) + ok 60 - mp_encode(0x03U) == "\xc7\x03\x00" + # extl 0x05U + ok 61 - mp_check_extl(0x05U) == 0 + ok 62 - mp_decode(mp_encode(0x05U)) == 0x05U + ok 63 - len(mp_encode_extl(0x05U) + ok 64 - len(mp_decode_extl(0x05U)) + ok 65 - mp_sizeof_extl(0x05U) + ok 66 - mp_encode(0x05U) == "\xc7\x05\x00" + # extl 0x06U + ok 67 - mp_check_extl(0x06U) == 0 + ok 68 - mp_decode(mp_encode(0x06U)) == 0x06U + ok 69 - len(mp_encode_extl(0x06U) + ok 70 - len(mp_decode_extl(0x06U)) + ok 71 - mp_sizeof_extl(0x06U) + ok 72 - mp_encode(0x06U) == "\xc7\x06\x00" + # extl 0x07U + ok 73 - mp_check_extl(0x07U) == 0 + ok 74 - mp_decode(mp_encode(0x07U)) == 0x07U + ok 75 - len(mp_encode_extl(0x07U) + ok 76 - len(mp_decode_extl(0x07U)) + ok 77 - mp_sizeof_extl(0x07U) + ok 78 - mp_encode(0x07U) == "\xc7\x07\x00" + # extl 0x09U + ok 79 - mp_check_extl(0x09U) == 0 + ok 80 - mp_decode(mp_encode(0x09U)) == 0x09U + ok 81 - len(mp_encode_extl(0x09U) + ok 82 - len(mp_decode_extl(0x09U)) + ok 83 - mp_sizeof_extl(0x09U) + ok 84 - mp_encode(0x09U) == "\xc7\x09\x00" + # extl 0x0aU + ok 85 - mp_check_extl(0x0aU) == 0 + ok 86 - mp_decode(mp_encode(0x0aU)) == 0x0aU + ok 87 - len(mp_encode_extl(0x0aU) + ok 88 - len(mp_decode_extl(0x0aU)) + ok 89 - mp_sizeof_extl(0x0aU) + ok 90 - mp_encode(0x0aU) == "\xc7\x0a\x00" + # extl 0x0bU + ok 91 - mp_check_extl(0x0bU) == 0 + ok 92 - mp_decode(mp_encode(0x0bU)) == 0x0bU + ok 93 - len(mp_encode_extl(0x0bU) + ok 94 - len(mp_decode_extl(0x0bU)) + ok 95 - mp_sizeof_extl(0x0bU) + ok 96 - mp_encode(0x0bU) == "\xc7\x0b\x00" + # extl 0x0cU + ok 97 - mp_check_extl(0x0cU) == 0 + ok 98 - mp_decode(mp_encode(0x0cU)) == 0x0cU + ok 99 - len(mp_encode_extl(0x0cU) + ok 100 - len(mp_decode_extl(0x0cU)) + ok 101 - mp_sizeof_extl(0x0cU) + ok 102 - mp_encode(0x0cU) == "\xc7\x0c\x00" + # extl 0x0dU + ok 103 - mp_check_extl(0x0dU) == 0 + ok 104 - mp_decode(mp_encode(0x0dU)) == 0x0dU + ok 105 - len(mp_encode_extl(0x0dU) + ok 106 - len(mp_decode_extl(0x0dU)) + ok 107 - mp_sizeof_extl(0x0dU) + ok 108 - mp_encode(0x0dU) == "\xc7\x0d\x00" + # extl 0x0eU + ok 109 - mp_check_extl(0x0eU) == 0 + ok 110 - mp_decode(mp_encode(0x0eU)) == 0x0eU + ok 111 - len(mp_encode_extl(0x0eU) + ok 112 - len(mp_decode_extl(0x0eU)) + ok 113 - mp_sizeof_extl(0x0eU) + ok 114 - mp_encode(0x0eU) == "\xc7\x0e\x00" + # extl 0x0fU + ok 115 - mp_check_extl(0x0fU) == 0 + ok 116 - mp_decode(mp_encode(0x0fU)) == 0x0fU + ok 117 - len(mp_encode_extl(0x0fU) + ok 118 - len(mp_decode_extl(0x0fU)) + ok 119 - mp_sizeof_extl(0x0fU) + ok 120 - mp_encode(0x0fU) == "\xc7\x0f\x00" + # extl 0x0100U + ok 121 - mp_check_extl(0x0100U) == 0 + ok 122 - mp_decode(mp_encode(0x0100U)) == 0x0100U + ok 123 - len(mp_encode_extl(0x0100U) + ok 124 - len(mp_decode_extl(0x0100U)) + ok 125 - mp_sizeof_extl(0x0100U) + ok 126 - mp_encode(0x0100U) == "\xc8\x01\x00\x00" + # extl 0x0101U + ok 127 - mp_check_extl(0x0101U) == 0 + ok 128 - mp_decode(mp_encode(0x0101U)) == 0x0101U + ok 129 - len(mp_encode_extl(0x0101U) + ok 130 - len(mp_decode_extl(0x0101U)) + ok 131 - mp_sizeof_extl(0x0101U) + ok 132 - mp_encode(0x0101U) == "\xc8\x01\x01\x00" + # extl 0xfffeU + ok 133 - mp_check_extl(0xfffeU) == 0 + ok 134 - mp_decode(mp_encode(0xfffeU)) == 0xfffeU + ok 135 - len(mp_encode_extl(0xfffeU) + ok 136 - len(mp_decode_extl(0xfffeU)) + ok 137 - mp_sizeof_extl(0xfffeU) + ok 138 - mp_encode(0xfffeU) == "\xc8\xff\xfe\x00" + # extl 0xffffU + ok 139 - mp_check_extl(0xffffU) == 0 + ok 140 - mp_decode(mp_encode(0xffffU)) == 0xffffU + ok 141 - len(mp_encode_extl(0xffffU) + ok 142 - len(mp_decode_extl(0xffffU)) + ok 143 - mp_sizeof_extl(0xffffU) + ok 144 - mp_encode(0xffffU) == "\xc8\xff\xff\x00" + # extl 0x00010000U + ok 145 - mp_check_extl(0x00010000U) == 0 + ok 146 - mp_decode(mp_encode(0x00010000U)) == 0x00010000U + ok 147 - len(mp_encode_extl(0x00010000U) + ok 148 - len(mp_decode_extl(0x00010000U)) + ok 149 - mp_sizeof_extl(0x00010000U) + ok 150 - mp_encode(0x00010000U) == "\xc9\x00\x01\x00\x00\x00" + # extl 0x00010001U + ok 151 - mp_check_extl(0x00010001U) == 0 + ok 152 - mp_decode(mp_encode(0x00010001U)) == 0x00010001U + ok 153 - len(mp_encode_extl(0x00010001U) + ok 154 - len(mp_decode_extl(0x00010001U)) + ok 155 - mp_sizeof_extl(0x00010001U) + ok 156 - mp_encode(0x00010001U) == "\xc9\x00\x01\x00\x01\x00" + # extl 0xfffffffeU + ok 157 - mp_check_extl(0xfffffffeU) == 0 + ok 158 - mp_decode(mp_encode(0xfffffffeU)) == 0xfffffffeU + ok 159 - len(mp_encode_extl(0xfffffffeU) + ok 160 - len(mp_decode_extl(0xfffffffeU)) + ok 161 - mp_sizeof_extl(0xfffffffeU) + ok 162 - mp_encode(0xfffffffeU) == "\xc9\xff\xff\xff\xfe\x00" + # extl 0xffffffffU + ok 163 - mp_check_extl(0xffffffffU) == 0 + ok 164 - mp_decode(mp_encode(0xffffffffU)) == 0xffffffffU + ok 165 - len(mp_encode_extl(0xffffffffU) + ok 166 - len(mp_decode_extl(0xffffffffU)) + ok 167 - mp_sizeof_extl(0xffffffffU) + ok 168 - mp_encode(0xffffffffU) == "\xc9\xff\xff\xff\xff\x00" + # *** test_extls: done *** +ok 9 - subtests 1..96 # *** test_strs *** # str len=0x01 @@ -731,7 +931,7 @@ ok 8 - subtests ok 95 - mp_sizeof_str(0x10001) ok 96 - mp_encode_str(x, 0x10001) == x # *** test_strs: done *** -ok 9 - subtests +ok 10 - subtests 1..96 # *** test_bins *** # bin len=0x01 @@ -843,7 +1043,261 @@ ok 9 - subtests ok 95 - mp_sizeof_bin(0x10001) ok 96 - mp_encode_bin(x, 0x10001) == x # *** test_bins: done *** -ok 10 - subtests +ok 11 - subtests + 1..225 + # *** test_exts *** + # ext len=0x01 + ok 1 - len(mp_decode_ext(x, 1)) + ok 2 - type(mp_decode_ext(x)) + ok 3 - len(mp_decode_strbin(x, 1)) + ok 4 - mp_check_ext(mp_encode_ext(x, 0x01)) + ok 5 - len(mp_decode_ext(x, 0x01) + ok 6 - len(mp_next_ext(x, 0x01) + ok 7 - len(mp_check_ext(x, 0x01) + ok 8 - mp_sizeof_ext(0x01) + ok 9 - mp_encode_ext(x, 0x01) == x + # ext len=0x02 + ok 10 - len(mp_decode_ext(x, 2)) + ok 11 - type(mp_decode_ext(x)) + ok 12 - len(mp_decode_strbin(x, 2)) + ok 13 - mp_check_ext(mp_encode_ext(x, 0x02)) + ok 14 - len(mp_decode_ext(x, 0x02) + ok 15 - len(mp_next_ext(x, 0x02) + ok 16 - len(mp_check_ext(x, 0x02) + ok 17 - mp_sizeof_ext(0x02) + ok 18 - mp_encode_ext(x, 0x02) == x + # ext len=0x03 + ok 19 - len(mp_decode_ext(x, 3)) + ok 20 - type(mp_decode_ext(x)) + ok 21 - len(mp_decode_strbin(x, 3)) + ok 22 - mp_check_ext(mp_encode_ext(x, 0x03)) + ok 23 - len(mp_decode_ext(x, 0x03) + ok 24 - len(mp_next_ext(x, 0x03) + ok 25 - len(mp_check_ext(x, 0x03) + ok 26 - mp_sizeof_ext(0x03) + ok 27 - mp_encode_ext(x, 0x03) == x + # ext len=0x04 + ok 28 - len(mp_decode_ext(x, 4)) + ok 29 - type(mp_decode_ext(x)) + ok 30 - len(mp_decode_strbin(x, 4)) + ok 31 - mp_check_ext(mp_encode_ext(x, 0x04)) + ok 32 - len(mp_decode_ext(x, 0x04) + ok 33 - len(mp_next_ext(x, 0x04) + ok 34 - len(mp_check_ext(x, 0x04) + ok 35 - mp_sizeof_ext(0x04) + ok 36 - mp_encode_ext(x, 0x04) == x + # ext len=0x05 + ok 37 - len(mp_decode_ext(x, 5)) + ok 38 - type(mp_decode_ext(x)) + ok 39 - len(mp_decode_strbin(x, 5)) + ok 40 - mp_check_ext(mp_encode_ext(x, 0x05)) + ok 41 - len(mp_decode_ext(x, 0x05) + ok 42 - len(mp_next_ext(x, 0x05) + ok 43 - len(mp_check_ext(x, 0x05) + ok 44 - mp_sizeof_ext(0x05) + ok 45 - mp_encode_ext(x, 0x05) == x + # ext len=0x06 + ok 46 - len(mp_decode_ext(x, 6)) + ok 47 - type(mp_decode_ext(x)) + ok 48 - len(mp_decode_strbin(x, 6)) + ok 49 - mp_check_ext(mp_encode_ext(x, 0x06)) + ok 50 - len(mp_decode_ext(x, 0x06) + ok 51 - len(mp_next_ext(x, 0x06) + ok 52 - len(mp_check_ext(x, 0x06) + ok 53 - mp_sizeof_ext(0x06) + ok 54 - mp_encode_ext(x, 0x06) == x + # ext len=0x07 + ok 55 - len(mp_decode_ext(x, 7)) + ok 56 - type(mp_decode_ext(x)) + ok 57 - len(mp_decode_strbin(x, 7)) + ok 58 - mp_check_ext(mp_encode_ext(x, 0x07)) + ok 59 - len(mp_decode_ext(x, 0x07) + ok 60 - len(mp_next_ext(x, 0x07) + ok 61 - len(mp_check_ext(x, 0x07) + ok 62 - mp_sizeof_ext(0x07) + ok 63 - mp_encode_ext(x, 0x07) == x + # ext len=0x08 + ok 64 - len(mp_decode_ext(x, 8)) + ok 65 - type(mp_decode_ext(x)) + ok 66 - len(mp_decode_strbin(x, 8)) + ok 67 - mp_check_ext(mp_encode_ext(x, 0x08)) + ok 68 - len(mp_decode_ext(x, 0x08) + ok 69 - len(mp_next_ext(x, 0x08) + ok 70 - len(mp_check_ext(x, 0x08) + ok 71 - mp_sizeof_ext(0x08) + ok 72 - mp_encode_ext(x, 0x08) == x + # ext len=0x09 + ok 73 - len(mp_decode_ext(x, 9)) + ok 74 - type(mp_decode_ext(x)) + ok 75 - len(mp_decode_strbin(x, 9)) + ok 76 - mp_check_ext(mp_encode_ext(x, 0x09)) + ok 77 - len(mp_decode_ext(x, 0x09) + ok 78 - len(mp_next_ext(x, 0x09) + ok 79 - len(mp_check_ext(x, 0x09) + ok 80 - mp_sizeof_ext(0x09) + ok 81 - mp_encode_ext(x, 0x09) == x + # ext len=0x0a + ok 82 - len(mp_decode_ext(x, 10)) + ok 83 - type(mp_decode_ext(x)) + ok 84 - len(mp_decode_strbin(x, 10)) + ok 85 - mp_check_ext(mp_encode_ext(x, 0x0a)) + ok 86 - len(mp_decode_ext(x, 0x0a) + ok 87 - len(mp_next_ext(x, 0x0a) + ok 88 - len(mp_check_ext(x, 0x0a) + ok 89 - mp_sizeof_ext(0x0a) + ok 90 - mp_encode_ext(x, 0x0a) == x + # ext len=0x0b + ok 91 - len(mp_decode_ext(x, 11)) + ok 92 - type(mp_decode_ext(x)) + ok 93 - len(mp_decode_strbin(x, 11)) + ok 94 - mp_check_ext(mp_encode_ext(x, 0x0b)) + ok 95 - len(mp_decode_ext(x, 0x0b) + ok 96 - len(mp_next_ext(x, 0x0b) + ok 97 - len(mp_check_ext(x, 0x0b) + ok 98 - mp_sizeof_ext(0x0b) + ok 99 - mp_encode_ext(x, 0x0b) == x + # ext len=0x0c + ok 100 - len(mp_decode_ext(x, 12)) + ok 101 - type(mp_decode_ext(x)) + ok 102 - len(mp_decode_strbin(x, 12)) + ok 103 - mp_check_ext(mp_encode_ext(x, 0x0c)) + ok 104 - len(mp_decode_ext(x, 0x0c) + ok 105 - len(mp_next_ext(x, 0x0c) + ok 106 - len(mp_check_ext(x, 0x0c) + ok 107 - mp_sizeof_ext(0x0c) + ok 108 - mp_encode_ext(x, 0x0c) == x + # ext len=0x0d + ok 109 - len(mp_decode_ext(x, 13)) + ok 110 - type(mp_decode_ext(x)) + ok 111 - len(mp_decode_strbin(x, 13)) + ok 112 - mp_check_ext(mp_encode_ext(x, 0x0d)) + ok 113 - len(mp_decode_ext(x, 0x0d) + ok 114 - len(mp_next_ext(x, 0x0d) + ok 115 - len(mp_check_ext(x, 0x0d) + ok 116 - mp_sizeof_ext(0x0d) + ok 117 - mp_encode_ext(x, 0x0d) == x + # ext len=0x0e + ok 118 - len(mp_decode_ext(x, 14)) + ok 119 - type(mp_decode_ext(x)) + ok 120 - len(mp_decode_strbin(x, 14)) + ok 121 - mp_check_ext(mp_encode_ext(x, 0x0e)) + ok 122 - len(mp_decode_ext(x, 0x0e) + ok 123 - len(mp_next_ext(x, 0x0e) + ok 124 - len(mp_check_ext(x, 0x0e) + ok 125 - mp_sizeof_ext(0x0e) + ok 126 - mp_encode_ext(x, 0x0e) == x + # ext len=0x0f + ok 127 - len(mp_decode_ext(x, 15)) + ok 128 - type(mp_decode_ext(x)) + ok 129 - len(mp_decode_strbin(x, 15)) + ok 130 - mp_check_ext(mp_encode_ext(x, 0x0f)) + ok 131 - len(mp_decode_ext(x, 0x0f) + ok 132 - len(mp_next_ext(x, 0x0f) + ok 133 - len(mp_check_ext(x, 0x0f) + ok 134 - mp_sizeof_ext(0x0f) + ok 135 - mp_encode_ext(x, 0x0f) == x + # ext len=0x10 + ok 136 - len(mp_decode_ext(x, 16)) + ok 137 - type(mp_decode_ext(x)) + ok 138 - len(mp_decode_strbin(x, 16)) + ok 139 - mp_check_ext(mp_encode_ext(x, 0x10)) + ok 140 - len(mp_decode_ext(x, 0x10) + ok 141 - len(mp_next_ext(x, 0x10) + ok 142 - len(mp_check_ext(x, 0x10) + ok 143 - mp_sizeof_ext(0x10) + ok 144 - mp_encode_ext(x, 0x10) == x + # ext len=0x11 + ok 145 - len(mp_decode_ext(x, 17)) + ok 146 - type(mp_decode_ext(x)) + ok 147 - len(mp_decode_strbin(x, 17)) + ok 148 - mp_check_ext(mp_encode_ext(x, 0x11)) + ok 149 - len(mp_decode_ext(x, 0x11) + ok 150 - len(mp_next_ext(x, 0x11) + ok 151 - len(mp_check_ext(x, 0x11) + ok 152 - mp_sizeof_ext(0x11) + ok 153 - mp_encode_ext(x, 0x11) == x + # ext len=0xfe + ok 154 - len(mp_decode_ext(x, 254)) + ok 155 - type(mp_decode_ext(x)) + ok 156 - len(mp_decode_strbin(x, 254)) + ok 157 - mp_check_ext(mp_encode_ext(x, 0xfe)) + ok 158 - len(mp_decode_ext(x, 0xfe) + ok 159 - len(mp_next_ext(x, 0xfe) + ok 160 - len(mp_check_ext(x, 0xfe) + ok 161 - mp_sizeof_ext(0xfe) + ok 162 - mp_encode_ext(x, 0xfe) == x + # ext len=0xff + ok 163 - len(mp_decode_ext(x, 255)) + ok 164 - type(mp_decode_ext(x)) + ok 165 - len(mp_decode_strbin(x, 255)) + ok 166 - mp_check_ext(mp_encode_ext(x, 0xff)) + ok 167 - len(mp_decode_ext(x, 0xff) + ok 168 - len(mp_next_ext(x, 0xff) + ok 169 - len(mp_check_ext(x, 0xff) + ok 170 - mp_sizeof_ext(0xff) + ok 171 - mp_encode_ext(x, 0xff) == x + # ext len=0x0100 + ok 172 - len(mp_decode_ext(x, 256)) + ok 173 - type(mp_decode_ext(x)) + ok 174 - len(mp_decode_strbin(x, 256)) + ok 175 - mp_check_ext(mp_encode_ext(x, 0x0100)) + ok 176 - len(mp_decode_ext(x, 0x0100) + ok 177 - len(mp_next_ext(x, 0x0100) + ok 178 - len(mp_check_ext(x, 0x0100) + ok 179 - mp_sizeof_ext(0x0100) + ok 180 - mp_encode_ext(x, 0x0100) == x + # ext len=0x0101 + ok 181 - len(mp_decode_ext(x, 257)) + ok 182 - type(mp_decode_ext(x)) + ok 183 - len(mp_decode_strbin(x, 257)) + ok 184 - mp_check_ext(mp_encode_ext(x, 0x0101)) + ok 185 - len(mp_decode_ext(x, 0x0101) + ok 186 - len(mp_next_ext(x, 0x0101) + ok 187 - len(mp_check_ext(x, 0x0101) + ok 188 - mp_sizeof_ext(0x0101) + ok 189 - mp_encode_ext(x, 0x0101) == x + # ext len=0xfffe + ok 190 - len(mp_decode_ext(x, 65534)) + ok 191 - type(mp_decode_ext(x)) + ok 192 - len(mp_decode_strbin(x, 65534)) + ok 193 - mp_check_ext(mp_encode_ext(x, 0xfffe)) + ok 194 - len(mp_decode_ext(x, 0xfffe) + ok 195 - len(mp_next_ext(x, 0xfffe) + ok 196 - len(mp_check_ext(x, 0xfffe) + ok 197 - mp_sizeof_ext(0xfffe) + ok 198 - mp_encode_ext(x, 0xfffe) == x + # ext len=0xffff + ok 199 - len(mp_decode_ext(x, 65535)) + ok 200 - type(mp_decode_ext(x)) + ok 201 - len(mp_decode_strbin(x, 65535)) + ok 202 - mp_check_ext(mp_encode_ext(x, 0xffff)) + ok 203 - len(mp_decode_ext(x, 0xffff) + ok 204 - len(mp_next_ext(x, 0xffff) + ok 205 - len(mp_check_ext(x, 0xffff) + ok 206 - mp_sizeof_ext(0xffff) + ok 207 - mp_encode_ext(x, 0xffff) == x + # ext len=0x00010000 + ok 208 - len(mp_decode_ext(x, 65536)) + ok 209 - type(mp_decode_ext(x)) + ok 210 - len(mp_decode_strbin(x, 65536)) + ok 211 - mp_check_ext(mp_encode_ext(x, 0x00010000)) + ok 212 - len(mp_decode_ext(x, 0x00010000) + ok 213 - len(mp_next_ext(x, 0x00010000) + ok 214 - len(mp_check_ext(x, 0x00010000) + ok 215 - mp_sizeof_ext(0x00010000) + ok 216 - mp_encode_ext(x, 0x00010000) == x + # ext len=0x00010001 + ok 217 - len(mp_decode_ext(x, 65537)) + ok 218 - type(mp_decode_ext(x)) + ok 219 - len(mp_decode_strbin(x, 65537)) + ok 220 - mp_check_ext(mp_encode_ext(x, 0x00010001)) + ok 221 - len(mp_decode_ext(x, 0x00010001) + ok 222 - len(mp_next_ext(x, 0x00010001) + ok 223 - len(mp_check_ext(x, 0x00010001) + ok 224 - mp_sizeof_ext(0x00010001) + ok 225 - mp_encode_ext(x, 0x00010001) == x + # *** test_exts: done *** +ok 12 - subtests 1..54 # *** test_arrays *** # array 0 @@ -910,7 +1364,7 @@ ok 10 - subtests ok 53 - mp_sizeof_array(0xffffffffU) ok 54 - mp_encode(0xffffffffU) == "\xdd\xff\xff\xff\xff" # *** test_arrays: done *** -ok 11 - subtests +ok 13 - subtests 1..54 # *** test_maps *** # map 0 @@ -977,7 +1431,7 @@ ok 11 - subtests ok 53 - mp_sizeof_map(0xffffffffU) ok 54 - mp_encode(0xffffffffU) == "\xdf\xff\xff\xff\xff" # *** test_maps: done *** -ok 12 - subtests +ok 14 - subtests 1..52 # *** test_next_on_arrays *** # next/check on array(0) @@ -1046,7 +1500,7 @@ ok 12 - subtests ok 51 - len(mp_check(array 65537)) == 65542 ok 52 - len(mp_next(array 65537)) == 65542 # *** test_next_on_arrays: done *** -ok 13 - subtests +ok 15 - subtests 1..52 # *** test_next_on_maps *** # next/check on map(0) @@ -1115,7 +1569,7 @@ ok 13 - subtests ok 51 - len(mp_check(map 65537)) == 131079 ok 52 - len(mp_next(map 65537)) == 131079 # *** test_next_on_maps: done *** -ok 14 - subtests +ok 16 - subtests 1..227 # *** test_compare_uints *** ok 1 - mp_compare_uint(0, 0) == 0 @@ -1346,7 +1800,7 @@ ok 14 - subtests ok 226 - mp_compare_uint(18446744073709551615, 18446744073709551614) > 0 ok 227 - mp_compare_uint(18446744073709551615, 18446744073709551615) == 0 # *** test_compare_uints: done *** -ok 15 - subtests +ok 17 - subtests 1..282 # *** test_format *** ok 1 - Test type on step 0 @@ -1632,7 +2086,7 @@ ok 15 - subtests ok 281 - return value on step 70 ok 282 - buffer overflow on step 70 # *** test_format: done *** -ok 16 - subtests +ok 18 - subtests 1..12 # *** test_mp_print *** ok 1 - mp_snprint return value @@ -1648,7 +2102,7 @@ ok 16 - subtests ok 11 - mp_snprint max nesting depth return value ok 12 - mp_snprint max nesting depth result # *** test_mp_print: done *** -ok 17 - subtests +ok 19 - subtests 1..65 # *** test_mp_check *** ok 1 - invalid fixmap 1 @@ -1717,7 +2171,7 @@ ok 17 - subtests ok 64 - invalid map32 2 ok 65 - invalid map32 3 # *** test_mp_check: done *** -ok 18 - subtests +ok 20 - subtests 1..96 # *** test_numbers *** ok 1 - mp_read_int32(mp_encode_uint(123)) check success @@ -1817,7 +2271,7 @@ ok 18 - subtests ok 95 - mp_read_double(mp_encode_strl(100)) check fail ok 96 - mp_read_double(mp_encode_strl(100)) check pos unchanged # *** test_numbers: done *** -ok 19 - subtests +ok 21 - subtests 1..4 # *** test_overflow *** ok 1 - mp_check array overflow @@ -1825,4 +2279,4 @@ ok 19 - subtests ok 3 - mp_check str overflow ok 4 - mp_check bin overflow # *** test_overflow: done *** -ok 20 - subtests +ok 22 - subtests -- 2.20.1 (Apple Git-117) ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support 2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko ` (2 preceding siblings ...) 2019-06-04 16:04 ` [PATCH v3 3/4] lib: update msgpuck library Serge Petrenko @ 2019-06-04 16:04 ` Serge Petrenko 2019-06-05 8:20 ` Георгий Кириченко 2019-06-05 12:40 ` Vladimir Davydov 3 siblings, 2 replies; 12+ messages in thread From: Serge Petrenko @ 2019-06-04 16:04 UTC (permalink / raw) To: georgy; +Cc: vdavydov.dev, tarantool-patches, kostja, Serge Petrenko Add methods to encode/decode decimals as msgpack. Part of #692 --- cmake/BuildDecNumber.cmake | 1 + src/lib/core/decimal.c | 64 +++++++++++ src/lib/core/decimal.h | 38 +++++++ src/lib/core/mp_user_types.h | 38 +++++++ test/unit/decimal.c | 73 ++++++++++++- test/unit/decimal.result | 204 ++++++++++++++++++++++++++++++++++- 6 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 src/lib/core/mp_user_types.h diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake index 80942fe05..abc6c64c4 100644 --- a/cmake/BuildDecNumber.cmake +++ b/cmake/BuildDecNumber.cmake @@ -4,6 +4,7 @@ macro(decnumber_build) set(decnumber_src ${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c ${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decPacked.c ) add_library(decNumber STATIC ${decnumber_src}) diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c index 12250da95..7b3b1d8c9 100644 --- a/src/lib/core/decimal.c +++ b/src/lib/core/decimal.c @@ -30,7 +30,10 @@ */ #include "decimal.h" +#include "mp_user_types.h" +#include "src/lib/msgpuck/msgpuck.h" #include "third_party/decNumber/decContext.h" +#include "third_party/decNumber/decPacked.h" #include <stdlib.h> #include <assert.h> @@ -291,3 +294,64 @@ decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision) return decimal_finalize(res, &op_context); } + +static inline uint32_t +decimal_len(decimal *dec) +{ + /* 1 + ceil((digits + 1) / 2) */ + return 2 + dec->digits / 2; +} + +uint32_t +mp_sizeof_decimal(decimal *dec) { + uint32_t l = decimal_len(dec); + return mp_sizeof_extl(l) + l; +} + +char * +mp_encode_decimal(char *data, decimal *dec) +{ + uint32_t len = decimal_len(dec); + data = mp_encode_extl(data, MP_DECIMAL, len); + data = mp_store_u8(data, decimal_scale(dec)); + len--; + int32_t scale; + char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec); + assert(tmp == data); + assert(scale == (int32_t)decimal_scale(dec)); + data += len; + return data; +} + +decimal * +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len) +{ + int32_t scale = mp_load_u8(data); + len--; + decimal *res = decPackedToNumber((uint8_t *)*data, len, &scale, dec); + if (res) + *data += len; + else + (*data)--; + return res; +} + +decimal * +mp_decode_decimal(const char **data, decimal *dec) +{ + int8_t type; + uint32_t len; + + if (mp_typeof(**data) != MP_EXT) + return NULL; + const char *const svp = *data; + len = mp_decode_extl(data, &type); + + if (type != MP_DECIMAL || len == 0) + return NULL; + decimal *res = mp_decode_decimal_raw(data, dec, len); + if (!res) { + *data = svp; + } + return res; +} diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h index f2812f500..cfcc98073 100644 --- a/src/lib/core/decimal.h +++ b/src/lib/core/decimal.h @@ -156,4 +156,42 @@ decimal_exp(decimal *res, const decimal *lhs, uint32_t precision); decimal * decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); + +/** + * Return length of message pack encoded decimal with extension header. + * Equivalent to mp_sizeof_ext(decimal representation length) + */ +uint32_t +mp_sizeof_decimal(decimal *dec); + +/** + * Encode a decimal value to msgpack. + * + * @return data + mp_sizeof_decimal(dec) + */ +char * +mp_encode_decimal(char *data, decimal *dec); + +/** + * Decode a decimal value as though msgpack + * ext header was already decoded. + * + * \post *data = *data + value representation length + * @return NULL if value encoding is incorrect + * dec otherwise. + */ +decimal * +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len); + +/** + * Decode a decimal encoded as msgpack with ext header. + * + * \post *data = *data + mp_sizeof_decimal(dec) + * @return NULL if mp_typeof(**data) != MP_EXT + * or value encoding is incorrect + * dec otherwise. + */ +decimal * +mp_decode_decimal(const char **data, decimal *dec); + #endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */ diff --git a/src/lib/core/mp_user_types.h b/src/lib/core/mp_user_types.h new file mode 100644 index 000000000..d5694a386 --- /dev/null +++ b/src/lib/core/mp_user_types.h @@ -0,0 +1,38 @@ +#ifndef TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED +#define TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED +/* + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +enum mp_user_type { + MP_DECIMAL = 0 +}; + +#endif /* TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED */ diff --git a/test/unit/decimal.c b/test/unit/decimal.c index adfbead91..1f4c9f385 100644 --- a/test/unit/decimal.c +++ b/test/unit/decimal.c @@ -1,12 +1,81 @@ #include "unit.h" #include "decimal.h" +#include "msgpuck.h" +#include "mp_user_types.h" + #include <limits.h> #include <string.h> +char buf[32]; +char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; + +#define test_mpdec(_str) ({ \ + decimal dec;\ + decimal_from_string(&dec, _str);\ + uint32_t l1 = mp_sizeof_decimal(&dec);\ + ok(l1 <= 25 && l1 >= 4, "mp_sizeof_decimal(%s)", _str);\ + char *b1 = mp_encode_decimal(buf, &dec);\ + is(b1, buf + l1, "mp_sizeof_decimal(%s) == len(mp_encode_decimal(%s)))", _str, _str);\ + const char *b2 = buf;\ + const char *b3 = buf;\ + decimal d2;\ + mp_next(&b3);\ + is(b3, b1, "mp_next(mp_encode(%s))", _str);\ + mp_decode_decimal(&b2, &d2);\ + is(b1, b2, "mp_decode(mp_encode(%s)) len", _str);\ + is(decimal_compare(&dec, &d2), 0, "mp_decode(mp_encode(%s)) value", _str);\ + is(decimal_scale(&dec), decimal_scale(&d2), "mp_decode(mp_encode(%s)) scale", _str);\ + decimal_to_string(&d2, buf2);\ + is(strcmp(buf2, _str), 0, "str(mp_decode(mp_encode())) == %s", _str);\ + b2 = buf;\ + int8_t type;\ + uint32_t l2 = mp_decode_extl(&b2, &type);\ + is(type, MP_DECIMAL, "mp_ext_type is MP_DECIMAL");\ + ok(&d2 == mp_decode_decimal_raw(&b2, &d2, l2), "mp_decode_decimal_raw()");\ + is(decimal_compare(&dec, &d2), 0, "mp_decode_decimal_raw() is correct");\ + is(b2, buf + l1, "mp_decode_extl + mp_decode_decimal_raw len");\ +}) + +static int +test_encode_decode(void) +{ + plan(200); + test_mpdec("0"); + test_mpdec("-0"); + test_mpdec("1"); + test_mpdec("-1"); + test_mpdec("0.1"); + test_mpdec("-0.1"); + test_mpdec("2.718281828459045"); + test_mpdec("-2.718281828459045"); + test_mpdec("3.141592653589793"); + test_mpdec("-3.141592653589793"); + test_mpdec("1234567891234567890.0987654321987654321"); + test_mpdec("-1234567891234567890.0987654321987654321"); + test_mpdec("0.0000000000000000000000000000000000001"); + test_mpdec("-0.0000000000000000000000000000000000001"); + test_mpdec("0.00000000000000000000000000000000000001"); + test_mpdec("-0.00000000000000000000000000000000000001"); + test_mpdec("99999999999999999999999999999999999999"); + test_mpdec("-99999999999999999999999999999999999999"); + + /* Encode an invalid decimal. */ + char *b = mp_encode_extl(buf, MP_DECIMAL, 2); + b = mp_store_u8(b, 1); + *b++ = '\xab'; + *b++ = '\xcd'; + const char *bb = buf; + decimal dec; + is(mp_decode_decimal(&bb, &dec), NULL, "decode malformed decimal fails"); + is (bb, buf, "decode malformed decimal saves buffer position"); + + return check_plan(); +} + int main(void) { - plan(50); + plan(51); char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; @@ -164,5 +233,7 @@ main(void) is(decimal_precision(&s), 38, "Correct precision"); is(decimal_scale(&s), 38, "Correct scale"); + test_encode_decode(); + check_plan(); } diff --git a/test/unit/decimal.result b/test/unit/decimal.result index 02bee118a..da34aa0fd 100644 --- a/test/unit/decimal.result +++ b/test/unit/decimal.result @@ -1,4 +1,4 @@ -1..50 +1..51 ok 1 - Basic construction from string. ok 2 - Correct construction and to_string conversion. ok 3 - Correct construction from INT_MAX. @@ -49,3 +49,205 @@ ok 47 - Rounding too small number to zero ok 48 - Rounding is correct ok 49 - Correct precision ok 50 - Correct scale + 1..200 + ok 1 - mp_sizeof_decimal(0) + ok 2 - mp_sizeof_decimal(0) == len(mp_encode_decimal(0))) + ok 3 - mp_next(mp_encode(0)) + ok 4 - mp_decode(mp_encode(0)) len + ok 5 - mp_decode(mp_encode(0)) value + ok 6 - mp_decode(mp_encode(0)) scale + ok 7 - str(mp_decode(mp_encode())) == 0 + ok 8 - mp_ext_type is MP_DECIMAL + ok 9 - mp_decode_decimal_raw() + ok 10 - mp_decode_decimal_raw() is correct + ok 11 - mp_decode_extl + mp_decode_decimal_raw len + ok 12 - mp_sizeof_decimal(-0) + ok 13 - mp_sizeof_decimal(-0) == len(mp_encode_decimal(-0))) + ok 14 - mp_next(mp_encode(-0)) + ok 15 - mp_decode(mp_encode(-0)) len + ok 16 - mp_decode(mp_encode(-0)) value + ok 17 - mp_decode(mp_encode(-0)) scale + ok 18 - str(mp_decode(mp_encode())) == -0 + ok 19 - mp_ext_type is MP_DECIMAL + ok 20 - mp_decode_decimal_raw() + ok 21 - mp_decode_decimal_raw() is correct + ok 22 - mp_decode_extl + mp_decode_decimal_raw len + ok 23 - mp_sizeof_decimal(1) + ok 24 - mp_sizeof_decimal(1) == len(mp_encode_decimal(1))) + ok 25 - mp_next(mp_encode(1)) + ok 26 - mp_decode(mp_encode(1)) len + ok 27 - mp_decode(mp_encode(1)) value + ok 28 - mp_decode(mp_encode(1)) scale + ok 29 - str(mp_decode(mp_encode())) == 1 + ok 30 - mp_ext_type is MP_DECIMAL + ok 31 - mp_decode_decimal_raw() + ok 32 - mp_decode_decimal_raw() is correct + ok 33 - mp_decode_extl + mp_decode_decimal_raw len + ok 34 - mp_sizeof_decimal(-1) + ok 35 - mp_sizeof_decimal(-1) == len(mp_encode_decimal(-1))) + ok 36 - mp_next(mp_encode(-1)) + ok 37 - mp_decode(mp_encode(-1)) len + ok 38 - mp_decode(mp_encode(-1)) value + ok 39 - mp_decode(mp_encode(-1)) scale + ok 40 - str(mp_decode(mp_encode())) == -1 + ok 41 - mp_ext_type is MP_DECIMAL + ok 42 - mp_decode_decimal_raw() + ok 43 - mp_decode_decimal_raw() is correct + ok 44 - mp_decode_extl + mp_decode_decimal_raw len + ok 45 - mp_sizeof_decimal(0.1) + ok 46 - mp_sizeof_decimal(0.1) == len(mp_encode_decimal(0.1))) + ok 47 - mp_next(mp_encode(0.1)) + ok 48 - mp_decode(mp_encode(0.1)) len + ok 49 - mp_decode(mp_encode(0.1)) value + ok 50 - mp_decode(mp_encode(0.1)) scale + ok 51 - str(mp_decode(mp_encode())) == 0.1 + ok 52 - mp_ext_type is MP_DECIMAL + ok 53 - mp_decode_decimal_raw() + ok 54 - mp_decode_decimal_raw() is correct + ok 55 - mp_decode_extl + mp_decode_decimal_raw len + ok 56 - mp_sizeof_decimal(-0.1) + ok 57 - mp_sizeof_decimal(-0.1) == len(mp_encode_decimal(-0.1))) + ok 58 - mp_next(mp_encode(-0.1)) + ok 59 - mp_decode(mp_encode(-0.1)) len + ok 60 - mp_decode(mp_encode(-0.1)) value + ok 61 - mp_decode(mp_encode(-0.1)) scale + ok 62 - str(mp_decode(mp_encode())) == -0.1 + ok 63 - mp_ext_type is MP_DECIMAL + ok 64 - mp_decode_decimal_raw() + ok 65 - mp_decode_decimal_raw() is correct + ok 66 - mp_decode_extl + mp_decode_decimal_raw len + ok 67 - mp_sizeof_decimal(2.718281828459045) + ok 68 - mp_sizeof_decimal(2.718281828459045) == len(mp_encode_decimal(2.718281828459045))) + ok 69 - mp_next(mp_encode(2.718281828459045)) + ok 70 - mp_decode(mp_encode(2.718281828459045)) len + ok 71 - mp_decode(mp_encode(2.718281828459045)) value + ok 72 - mp_decode(mp_encode(2.718281828459045)) scale + ok 73 - str(mp_decode(mp_encode())) == 2.718281828459045 + ok 74 - mp_ext_type is MP_DECIMAL + ok 75 - mp_decode_decimal_raw() + ok 76 - mp_decode_decimal_raw() is correct + ok 77 - mp_decode_extl + mp_decode_decimal_raw len + ok 78 - mp_sizeof_decimal(-2.718281828459045) + ok 79 - mp_sizeof_decimal(-2.718281828459045) == len(mp_encode_decimal(-2.718281828459045))) + ok 80 - mp_next(mp_encode(-2.718281828459045)) + ok 81 - mp_decode(mp_encode(-2.718281828459045)) len + ok 82 - mp_decode(mp_encode(-2.718281828459045)) value + ok 83 - mp_decode(mp_encode(-2.718281828459045)) scale + ok 84 - str(mp_decode(mp_encode())) == -2.718281828459045 + ok 85 - mp_ext_type is MP_DECIMAL + ok 86 - mp_decode_decimal_raw() + ok 87 - mp_decode_decimal_raw() is correct + ok 88 - mp_decode_extl + mp_decode_decimal_raw len + ok 89 - mp_sizeof_decimal(3.141592653589793) + ok 90 - mp_sizeof_decimal(3.141592653589793) == len(mp_encode_decimal(3.141592653589793))) + ok 91 - mp_next(mp_encode(3.141592653589793)) + ok 92 - mp_decode(mp_encode(3.141592653589793)) len + ok 93 - mp_decode(mp_encode(3.141592653589793)) value + ok 94 - mp_decode(mp_encode(3.141592653589793)) scale + ok 95 - str(mp_decode(mp_encode())) == 3.141592653589793 + ok 96 - mp_ext_type is MP_DECIMAL + ok 97 - mp_decode_decimal_raw() + ok 98 - mp_decode_decimal_raw() is correct + ok 99 - mp_decode_extl + mp_decode_decimal_raw len + ok 100 - mp_sizeof_decimal(-3.141592653589793) + ok 101 - mp_sizeof_decimal(-3.141592653589793) == len(mp_encode_decimal(-3.141592653589793))) + ok 102 - mp_next(mp_encode(-3.141592653589793)) + ok 103 - mp_decode(mp_encode(-3.141592653589793)) len + ok 104 - mp_decode(mp_encode(-3.141592653589793)) value + ok 105 - mp_decode(mp_encode(-3.141592653589793)) scale + ok 106 - str(mp_decode(mp_encode())) == -3.141592653589793 + ok 107 - mp_ext_type is MP_DECIMAL + ok 108 - mp_decode_decimal_raw() + ok 109 - mp_decode_decimal_raw() is correct + ok 110 - mp_decode_extl + mp_decode_decimal_raw len + ok 111 - mp_sizeof_decimal(1234567891234567890.0987654321987654321) + ok 112 - mp_sizeof_decimal(1234567891234567890.0987654321987654321) == len(mp_encode_decimal(1234567891234567890.0987654321987654321))) + ok 113 - mp_next(mp_encode(1234567891234567890.0987654321987654321)) + ok 114 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) len + ok 115 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) value + ok 116 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) scale + ok 117 - str(mp_decode(mp_encode())) == 1234567891234567890.0987654321987654321 + ok 118 - mp_ext_type is MP_DECIMAL + ok 119 - mp_decode_decimal_raw() + ok 120 - mp_decode_decimal_raw() is correct + ok 121 - mp_decode_extl + mp_decode_decimal_raw len + ok 122 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321) + ok 123 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321) == len(mp_encode_decimal(-1234567891234567890.0987654321987654321))) + ok 124 - mp_next(mp_encode(-1234567891234567890.0987654321987654321)) + ok 125 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) len + ok 126 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) value + ok 127 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) scale + ok 128 - str(mp_decode(mp_encode())) == -1234567891234567890.0987654321987654321 + ok 129 - mp_ext_type is MP_DECIMAL + ok 130 - mp_decode_decimal_raw() + ok 131 - mp_decode_decimal_raw() is correct + ok 132 - mp_decode_extl + mp_decode_decimal_raw len + ok 133 - mp_sizeof_decimal(0.0000000000000000000000000000000000001) + ok 134 - mp_sizeof_decimal(0.0000000000000000000000000000000000001) == len(mp_encode_decimal(0.0000000000000000000000000000000000001))) + ok 135 - mp_next(mp_encode(0.0000000000000000000000000000000000001)) + ok 136 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) len + ok 137 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) value + ok 138 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) scale + ok 139 - str(mp_decode(mp_encode())) == 0.0000000000000000000000000000000000001 + ok 140 - mp_ext_type is MP_DECIMAL + ok 141 - mp_decode_decimal_raw() + ok 142 - mp_decode_decimal_raw() is correct + ok 143 - mp_decode_extl + mp_decode_decimal_raw len + ok 144 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001) + ok 145 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001) == len(mp_encode_decimal(-0.0000000000000000000000000000000000001))) + ok 146 - mp_next(mp_encode(-0.0000000000000000000000000000000000001)) + ok 147 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) len + ok 148 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) value + ok 149 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) scale + ok 150 - str(mp_decode(mp_encode())) == -0.0000000000000000000000000000000000001 + ok 151 - mp_ext_type is MP_DECIMAL + ok 152 - mp_decode_decimal_raw() + ok 153 - mp_decode_decimal_raw() is correct + ok 154 - mp_decode_extl + mp_decode_decimal_raw len + ok 155 - mp_sizeof_decimal(0.00000000000000000000000000000000000001) + ok 156 - mp_sizeof_decimal(0.00000000000000000000000000000000000001) == len(mp_encode_decimal(0.00000000000000000000000000000000000001))) + ok 157 - mp_next(mp_encode(0.00000000000000000000000000000000000001)) + ok 158 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) len + ok 159 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) value + ok 160 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) scale + ok 161 - str(mp_decode(mp_encode())) == 0.00000000000000000000000000000000000001 + ok 162 - mp_ext_type is MP_DECIMAL + ok 163 - mp_decode_decimal_raw() + ok 164 - mp_decode_decimal_raw() is correct + ok 165 - mp_decode_extl + mp_decode_decimal_raw len + ok 166 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001) + ok 167 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001) == len(mp_encode_decimal(-0.00000000000000000000000000000000000001))) + ok 168 - mp_next(mp_encode(-0.00000000000000000000000000000000000001)) + ok 169 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) len + ok 170 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) value + ok 171 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) scale + ok 172 - str(mp_decode(mp_encode())) == -0.00000000000000000000000000000000000001 + ok 173 - mp_ext_type is MP_DECIMAL + ok 174 - mp_decode_decimal_raw() + ok 175 - mp_decode_decimal_raw() is correct + ok 176 - mp_decode_extl + mp_decode_decimal_raw len + ok 177 - mp_sizeof_decimal(99999999999999999999999999999999999999) + ok 178 - mp_sizeof_decimal(99999999999999999999999999999999999999) == len(mp_encode_decimal(99999999999999999999999999999999999999))) + ok 179 - mp_next(mp_encode(99999999999999999999999999999999999999)) + ok 180 - mp_decode(mp_encode(99999999999999999999999999999999999999)) len + ok 181 - mp_decode(mp_encode(99999999999999999999999999999999999999)) value + ok 182 - mp_decode(mp_encode(99999999999999999999999999999999999999)) scale + ok 183 - str(mp_decode(mp_encode())) == 99999999999999999999999999999999999999 + ok 184 - mp_ext_type is MP_DECIMAL + ok 185 - mp_decode_decimal_raw() + ok 186 - mp_decode_decimal_raw() is correct + ok 187 - mp_decode_extl + mp_decode_decimal_raw len + ok 188 - mp_sizeof_decimal(-99999999999999999999999999999999999999) + ok 189 - mp_sizeof_decimal(-99999999999999999999999999999999999999) == len(mp_encode_decimal(-99999999999999999999999999999999999999))) + ok 190 - mp_next(mp_encode(-99999999999999999999999999999999999999)) + ok 191 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) len + ok 192 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) value + ok 193 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) scale + ok 194 - str(mp_decode(mp_encode())) == -99999999999999999999999999999999999999 + ok 195 - mp_ext_type is MP_DECIMAL + ok 196 - mp_decode_decimal_raw() + ok 197 - mp_decode_decimal_raw() is correct + ok 198 - mp_decode_extl + mp_decode_decimal_raw len + ok 199 - decode malformed decimal fails + ok 200 - decode malformed decimal saves buffer position +ok 51 - subtests -- 2.20.1 (Apple Git-117) ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support 2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko @ 2019-06-05 8:20 ` Георгий Кириченко 2019-06-05 12:40 ` Vladimir Davydov 1 sibling, 0 replies; 12+ messages in thread From: Георгий Кириченко @ 2019-06-05 8:20 UTC (permalink / raw) To: Serge Petrenko; +Cc: vdavydov.dev, tarantool-patches, kostja [-- Attachment #1: Type: text/plain, Size: 22671 bytes --] LGTM, but I would like to postpone this commit until we would be able to process decimals in lua. I'm not sure what would happen if lua faced this in a msgpuck/tuple. Thanks! On Tuesday, June 4, 2019 7:04:19 PM MSK Serge Petrenko wrote: > Add methods to encode/decode decimals as msgpack. > > Part of #692 > --- > cmake/BuildDecNumber.cmake | 1 + > src/lib/core/decimal.c | 64 +++++++++++ > src/lib/core/decimal.h | 38 +++++++ > src/lib/core/mp_user_types.h | 38 +++++++ > test/unit/decimal.c | 73 ++++++++++++- > test/unit/decimal.result | 204 ++++++++++++++++++++++++++++++++++- > 6 files changed, 416 insertions(+), 2 deletions(-) > create mode 100644 src/lib/core/mp_user_types.h > > diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake > index 80942fe05..abc6c64c4 100644 > --- a/cmake/BuildDecNumber.cmake > +++ b/cmake/BuildDecNumber.cmake > @@ -4,6 +4,7 @@ macro(decnumber_build) > set(decnumber_src > ${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c > ${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c > + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decPacked.c > ) > > add_library(decNumber STATIC ${decnumber_src}) > diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c > index 12250da95..7b3b1d8c9 100644 > --- a/src/lib/core/decimal.c > +++ b/src/lib/core/decimal.c > @@ -30,7 +30,10 @@ > */ > > #include "decimal.h" > +#include "mp_user_types.h" > +#include "src/lib/msgpuck/msgpuck.h" > #include "third_party/decNumber/decContext.h" > +#include "third_party/decNumber/decPacked.h" > #include <stdlib.h> > #include <assert.h> > > @@ -291,3 +294,64 @@ decimal_sqrt(decimal *res, const decimal *lhs, uint32_t > precision) > > return decimal_finalize(res, &op_context); > } > + > +static inline uint32_t > +decimal_len(decimal *dec) > +{ > + /* 1 + ceil((digits + 1) / 2) */ > + return 2 + dec->digits / 2; > +} > + > +uint32_t > +mp_sizeof_decimal(decimal *dec) { > + uint32_t l = decimal_len(dec); > + return mp_sizeof_extl(l) + l; > +} > + > +char * > +mp_encode_decimal(char *data, decimal *dec) > +{ > + uint32_t len = decimal_len(dec); > + data = mp_encode_extl(data, MP_DECIMAL, len); > + data = mp_store_u8(data, decimal_scale(dec)); > + len--; > + int32_t scale; > + char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, > dec); + assert(tmp == data); > + assert(scale == (int32_t)decimal_scale(dec)); > + data += len; > + return data; > +} > + > +decimal * > +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len) > +{ > + int32_t scale = mp_load_u8(data); > + len--; > + decimal *res = decPackedToNumber((uint8_t *)*data, len, &scale, dec); > + if (res) > + *data += len; > + else > + (*data)--; > + return res; > +} > + > +decimal * > +mp_decode_decimal(const char **data, decimal *dec) > +{ > + int8_t type; > + uint32_t len; > + > + if (mp_typeof(**data) != MP_EXT) > + return NULL; > + const char *const svp = *data; > + len = mp_decode_extl(data, &type); > + > + if (type != MP_DECIMAL || len == 0) > + return NULL; > + decimal *res = mp_decode_decimal_raw(data, dec, len); > + if (!res) { > + *data = svp; > + } > + return res; > +} > diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h > index f2812f500..cfcc98073 100644 > --- a/src/lib/core/decimal.h > +++ b/src/lib/core/decimal.h > @@ -156,4 +156,42 @@ decimal_exp(decimal *res, const decimal *lhs, uint32_t > precision); decimal * > decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); > > + > +/** > + * Return length of message pack encoded decimal with extension header. > + * Equivalent to mp_sizeof_ext(decimal representation length) > + */ > +uint32_t > +mp_sizeof_decimal(decimal *dec); > + > +/** > + * Encode a decimal value to msgpack. > + * > + * @return data + mp_sizeof_decimal(dec) > + */ > +char * > +mp_encode_decimal(char *data, decimal *dec); > + > +/** > + * Decode a decimal value as though msgpack > + * ext header was already decoded. > + * > + * \post *data = *data + value representation length > + * @return NULL if value encoding is incorrect > + * dec otherwise. > + */ > +decimal * > +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len); > + > +/** > + * Decode a decimal encoded as msgpack with ext header. > + * > + * \post *data = *data + mp_sizeof_decimal(dec) > + * @return NULL if mp_typeof(**data) != MP_EXT > + * or value encoding is incorrect > + * dec otherwise. > + */ > +decimal * > +mp_decode_decimal(const char **data, decimal *dec); > + > #endif /* TARANTOOL_LIB_CORE_DECIMAL_H_INCLUDED */ > diff --git a/src/lib/core/mp_user_types.h b/src/lib/core/mp_user_types.h > new file mode 100644 > index 000000000..d5694a386 > --- /dev/null > +++ b/src/lib/core/mp_user_types.h > @@ -0,0 +1,38 @@ > +#ifndef TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED > +#define TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED > +/* > + * Copyright 2019, Tarantool AUTHORS, please see AUTHORS file. > + * > + * Redistribution and use in source and binary forms, with or > + * without modification, are permitted provided that the following > + * conditions are met: > + * > + * 1. Redistributions of source code must retain the above > + * copyright notice, this list of conditions and the > + * following disclaimer. > + * > + * 2. Redistributions in binary form must reproduce the above > + * copyright notice, this list of conditions and the following > + * disclaimer in the documentation and/or other materials > + * provided with the distribution. > + * > + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND > + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL > + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, > + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL > + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF > + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF > + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF > + * SUCH DAMAGE. > + */ > + > +enum mp_user_type { > + MP_DECIMAL = 0 > +}; > + > +#endif /* TARANTOOL_LIB_CORE_MP_USER_TYPES_H_INCLUDED */ > diff --git a/test/unit/decimal.c b/test/unit/decimal.c > index adfbead91..1f4c9f385 100644 > --- a/test/unit/decimal.c > +++ b/test/unit/decimal.c > @@ -1,12 +1,81 @@ > #include "unit.h" > #include "decimal.h" > +#include "msgpuck.h" > +#include "mp_user_types.h" > + > #include <limits.h> > #include <string.h> > > +char buf[32]; > +char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; > + > +#define test_mpdec(_str) ({ \ > + decimal dec;\ > + decimal_from_string(&dec, _str);\ > + uint32_t l1 = mp_sizeof_decimal(&dec);\ > + ok(l1 <= 25 && l1 >= 4, "mp_sizeof_decimal(%s)", _str);\ > + char *b1 = mp_encode_decimal(buf, &dec);\ > + is(b1, buf + l1, "mp_sizeof_decimal(%s) == len(mp_encode_decimal(%s)))", > _str, _str);\ + const char *b2 = buf;\ > + const char *b3 = buf;\ > + decimal d2;\ > + mp_next(&b3);\ > + is(b3, b1, "mp_next(mp_encode(%s))", _str);\ > + mp_decode_decimal(&b2, &d2);\ > + is(b1, b2, "mp_decode(mp_encode(%s)) len", _str);\ > + is(decimal_compare(&dec, &d2), 0, "mp_decode(mp_encode(%s)) value", > _str);\ + is(decimal_scale(&dec), decimal_scale(&d2), > "mp_decode(mp_encode(%s)) scale", _str);\ + decimal_to_string(&d2, buf2);\ > + is(strcmp(buf2, _str), 0, "str(mp_decode(mp_encode())) == %s", _str);\ > + b2 = buf;\ > + int8_t type;\ > + uint32_t l2 = mp_decode_extl(&b2, &type);\ > + is(type, MP_DECIMAL, "mp_ext_type is MP_DECIMAL");\ > + ok(&d2 == mp_decode_decimal_raw(&b2, &d2, l2), > "mp_decode_decimal_raw()");\ + is(decimal_compare(&dec, &d2), 0, > "mp_decode_decimal_raw() is correct");\ + is(b2, buf + l1, "mp_decode_extl > + mp_decode_decimal_raw len");\ +}) > + > +static int > +test_encode_decode(void) > +{ > + plan(200); > + test_mpdec("0"); > + test_mpdec("-0"); > + test_mpdec("1"); > + test_mpdec("-1"); > + test_mpdec("0.1"); > + test_mpdec("-0.1"); > + test_mpdec("2.718281828459045"); > + test_mpdec("-2.718281828459045"); > + test_mpdec("3.141592653589793"); > + test_mpdec("-3.141592653589793"); > + test_mpdec("1234567891234567890.0987654321987654321"); > + test_mpdec("-1234567891234567890.0987654321987654321"); > + test_mpdec("0.0000000000000000000000000000000000001"); > + test_mpdec("-0.0000000000000000000000000000000000001"); > + test_mpdec("0.00000000000000000000000000000000000001"); > + test_mpdec("-0.00000000000000000000000000000000000001"); > + test_mpdec("99999999999999999999999999999999999999"); > + test_mpdec("-99999999999999999999999999999999999999"); > + > + /* Encode an invalid decimal. */ > + char *b = mp_encode_extl(buf, MP_DECIMAL, 2); > + b = mp_store_u8(b, 1); > + *b++ = '\xab'; > + *b++ = '\xcd'; > + const char *bb = buf; > + decimal dec; > + is(mp_decode_decimal(&bb, &dec), NULL, "decode malformed decimal fails"); > + is (bb, buf, "decode malformed decimal saves buffer position"); > + > + return check_plan(); > +} > + > int > main(void) > { > - plan(50); > + plan(51); > > char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; > char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; > @@ -164,5 +233,7 @@ main(void) > is(decimal_precision(&s), 38, "Correct precision"); > is(decimal_scale(&s), 38, "Correct scale"); > > + test_encode_decode(); > + > check_plan(); > } > diff --git a/test/unit/decimal.result b/test/unit/decimal.result > index 02bee118a..da34aa0fd 100644 > --- a/test/unit/decimal.result > +++ b/test/unit/decimal.result > @@ -1,4 +1,4 @@ > -1..50 > +1..51 > ok 1 - Basic construction from string. > ok 2 - Correct construction and to_string conversion. > ok 3 - Correct construction from INT_MAX. > @@ -49,3 +49,205 @@ ok 47 - Rounding too small number to zero > ok 48 - Rounding is correct > ok 49 - Correct precision > ok 50 - Correct scale > + 1..200 > + ok 1 - mp_sizeof_decimal(0) > + ok 2 - mp_sizeof_decimal(0) == len(mp_encode_decimal(0))) > + ok 3 - mp_next(mp_encode(0)) > + ok 4 - mp_decode(mp_encode(0)) len > + ok 5 - mp_decode(mp_encode(0)) value > + ok 6 - mp_decode(mp_encode(0)) scale > + ok 7 - str(mp_decode(mp_encode())) == 0 > + ok 8 - mp_ext_type is MP_DECIMAL > + ok 9 - mp_decode_decimal_raw() > + ok 10 - mp_decode_decimal_raw() is correct > + ok 11 - mp_decode_extl + mp_decode_decimal_raw len > + ok 12 - mp_sizeof_decimal(-0) > + ok 13 - mp_sizeof_decimal(-0) == len(mp_encode_decimal(-0))) > + ok 14 - mp_next(mp_encode(-0)) > + ok 15 - mp_decode(mp_encode(-0)) len > + ok 16 - mp_decode(mp_encode(-0)) value > + ok 17 - mp_decode(mp_encode(-0)) scale > + ok 18 - str(mp_decode(mp_encode())) == -0 > + ok 19 - mp_ext_type is MP_DECIMAL > + ok 20 - mp_decode_decimal_raw() > + ok 21 - mp_decode_decimal_raw() is correct > + ok 22 - mp_decode_extl + mp_decode_decimal_raw len > + ok 23 - mp_sizeof_decimal(1) > + ok 24 - mp_sizeof_decimal(1) == len(mp_encode_decimal(1))) > + ok 25 - mp_next(mp_encode(1)) > + ok 26 - mp_decode(mp_encode(1)) len > + ok 27 - mp_decode(mp_encode(1)) value > + ok 28 - mp_decode(mp_encode(1)) scale > + ok 29 - str(mp_decode(mp_encode())) == 1 > + ok 30 - mp_ext_type is MP_DECIMAL > + ok 31 - mp_decode_decimal_raw() > + ok 32 - mp_decode_decimal_raw() is correct > + ok 33 - mp_decode_extl + mp_decode_decimal_raw len > + ok 34 - mp_sizeof_decimal(-1) > + ok 35 - mp_sizeof_decimal(-1) == len(mp_encode_decimal(-1))) > + ok 36 - mp_next(mp_encode(-1)) > + ok 37 - mp_decode(mp_encode(-1)) len > + ok 38 - mp_decode(mp_encode(-1)) value > + ok 39 - mp_decode(mp_encode(-1)) scale > + ok 40 - str(mp_decode(mp_encode())) == -1 > + ok 41 - mp_ext_type is MP_DECIMAL > + ok 42 - mp_decode_decimal_raw() > + ok 43 - mp_decode_decimal_raw() is correct > + ok 44 - mp_decode_extl + mp_decode_decimal_raw len > + ok 45 - mp_sizeof_decimal(0.1) > + ok 46 - mp_sizeof_decimal(0.1) == len(mp_encode_decimal(0.1))) > + ok 47 - mp_next(mp_encode(0.1)) > + ok 48 - mp_decode(mp_encode(0.1)) len > + ok 49 - mp_decode(mp_encode(0.1)) value > + ok 50 - mp_decode(mp_encode(0.1)) scale > + ok 51 - str(mp_decode(mp_encode())) == 0.1 > + ok 52 - mp_ext_type is MP_DECIMAL > + ok 53 - mp_decode_decimal_raw() > + ok 54 - mp_decode_decimal_raw() is correct > + ok 55 - mp_decode_extl + mp_decode_decimal_raw len > + ok 56 - mp_sizeof_decimal(-0.1) > + ok 57 - mp_sizeof_decimal(-0.1) == len(mp_encode_decimal(-0.1))) > + ok 58 - mp_next(mp_encode(-0.1)) > + ok 59 - mp_decode(mp_encode(-0.1)) len > + ok 60 - mp_decode(mp_encode(-0.1)) value > + ok 61 - mp_decode(mp_encode(-0.1)) scale > + ok 62 - str(mp_decode(mp_encode())) == -0.1 > + ok 63 - mp_ext_type is MP_DECIMAL > + ok 64 - mp_decode_decimal_raw() > + ok 65 - mp_decode_decimal_raw() is correct > + ok 66 - mp_decode_extl + mp_decode_decimal_raw len > + ok 67 - mp_sizeof_decimal(2.718281828459045) > + ok 68 - mp_sizeof_decimal(2.718281828459045) == > len(mp_encode_decimal(2.718281828459045))) + ok 69 - > mp_next(mp_encode(2.718281828459045)) > + ok 70 - mp_decode(mp_encode(2.718281828459045)) len > + ok 71 - mp_decode(mp_encode(2.718281828459045)) value > + ok 72 - mp_decode(mp_encode(2.718281828459045)) scale > + ok 73 - str(mp_decode(mp_encode())) == 2.718281828459045 > + ok 74 - mp_ext_type is MP_DECIMAL > + ok 75 - mp_decode_decimal_raw() > + ok 76 - mp_decode_decimal_raw() is correct > + ok 77 - mp_decode_extl + mp_decode_decimal_raw len > + ok 78 - mp_sizeof_decimal(-2.718281828459045) > + ok 79 - mp_sizeof_decimal(-2.718281828459045) == > len(mp_encode_decimal(-2.718281828459045))) + ok 80 - > mp_next(mp_encode(-2.718281828459045)) > + ok 81 - mp_decode(mp_encode(-2.718281828459045)) len > + ok 82 - mp_decode(mp_encode(-2.718281828459045)) value > + ok 83 - mp_decode(mp_encode(-2.718281828459045)) scale > + ok 84 - str(mp_decode(mp_encode())) == -2.718281828459045 > + ok 85 - mp_ext_type is MP_DECIMAL > + ok 86 - mp_decode_decimal_raw() > + ok 87 - mp_decode_decimal_raw() is correct > + ok 88 - mp_decode_extl + mp_decode_decimal_raw len > + ok 89 - mp_sizeof_decimal(3.141592653589793) > + ok 90 - mp_sizeof_decimal(3.141592653589793) == > len(mp_encode_decimal(3.141592653589793))) + ok 91 - > mp_next(mp_encode(3.141592653589793)) > + ok 92 - mp_decode(mp_encode(3.141592653589793)) len > + ok 93 - mp_decode(mp_encode(3.141592653589793)) value > + ok 94 - mp_decode(mp_encode(3.141592653589793)) scale > + ok 95 - str(mp_decode(mp_encode())) == 3.141592653589793 > + ok 96 - mp_ext_type is MP_DECIMAL > + ok 97 - mp_decode_decimal_raw() > + ok 98 - mp_decode_decimal_raw() is correct > + ok 99 - mp_decode_extl + mp_decode_decimal_raw len > + ok 100 - mp_sizeof_decimal(-3.141592653589793) > + ok 101 - mp_sizeof_decimal(-3.141592653589793) == > len(mp_encode_decimal(-3.141592653589793))) + ok 102 - > mp_next(mp_encode(-3.141592653589793)) > + ok 103 - mp_decode(mp_encode(-3.141592653589793)) len > + ok 104 - mp_decode(mp_encode(-3.141592653589793)) value > + ok 105 - mp_decode(mp_encode(-3.141592653589793)) scale > + ok 106 - str(mp_decode(mp_encode())) == -3.141592653589793 > + ok 107 - mp_ext_type is MP_DECIMAL > + ok 108 - mp_decode_decimal_raw() > + ok 109 - mp_decode_decimal_raw() is correct > + ok 110 - mp_decode_extl + mp_decode_decimal_raw len > + ok 111 - mp_sizeof_decimal(1234567891234567890.0987654321987654321) > + ok 112 - mp_sizeof_decimal(1234567891234567890.0987654321987654321) == > len(mp_encode_decimal(1234567891234567890.0987654321987654321))) + ok > 113 - mp_next(mp_encode(1234567891234567890.0987654321987654321)) + ok > 114 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) len + > ok 115 - mp_decode(mp_encode(1234567891234567890.0987654321987654321)) > value + ok 116 - > mp_decode(mp_encode(1234567891234567890.0987654321987654321)) scale + ok > 117 - str(mp_decode(mp_encode())) == > 1234567891234567890.0987654321987654321 + ok 118 - mp_ext_type is > MP_DECIMAL > + ok 119 - mp_decode_decimal_raw() > + ok 120 - mp_decode_decimal_raw() is correct > + ok 121 - mp_decode_extl + mp_decode_decimal_raw len > + ok 122 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321) > + ok 123 - mp_sizeof_decimal(-1234567891234567890.0987654321987654321) == > len(mp_encode_decimal(-1234567891234567890.0987654321987654321))) + ok > 124 - mp_next(mp_encode(-1234567891234567890.0987654321987654321)) + ok > 125 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) len + > ok 126 - mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) > value + ok 127 - > mp_decode(mp_encode(-1234567891234567890.0987654321987654321)) scale + > ok 128 - str(mp_decode(mp_encode())) == > -1234567891234567890.0987654321987654321 + ok 129 - mp_ext_type is > MP_DECIMAL > + ok 130 - mp_decode_decimal_raw() > + ok 131 - mp_decode_decimal_raw() is correct > + ok 132 - mp_decode_extl + mp_decode_decimal_raw len > + ok 133 - mp_sizeof_decimal(0.0000000000000000000000000000000000001) > + ok 134 - mp_sizeof_decimal(0.0000000000000000000000000000000000001) == > len(mp_encode_decimal(0.0000000000000000000000000000000000001))) + ok > 135 - mp_next(mp_encode(0.0000000000000000000000000000000000001)) + ok > 136 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) len + > ok 137 - mp_decode(mp_encode(0.0000000000000000000000000000000000001)) > value + ok 138 - > mp_decode(mp_encode(0.0000000000000000000000000000000000001)) scale + ok > 139 - str(mp_decode(mp_encode())) == > 0.0000000000000000000000000000000000001 + ok 140 - mp_ext_type is > MP_DECIMAL > + ok 141 - mp_decode_decimal_raw() > + ok 142 - mp_decode_decimal_raw() is correct > + ok 143 - mp_decode_extl + mp_decode_decimal_raw len > + ok 144 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001) > + ok 145 - mp_sizeof_decimal(-0.0000000000000000000000000000000000001) == > len(mp_encode_decimal(-0.0000000000000000000000000000000000001))) + ok > 146 - mp_next(mp_encode(-0.0000000000000000000000000000000000001)) + ok > 147 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) len + > ok 148 - mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) > value + ok 149 - > mp_decode(mp_encode(-0.0000000000000000000000000000000000001)) scale + > ok 150 - str(mp_decode(mp_encode())) == > -0.0000000000000000000000000000000000001 + ok 151 - mp_ext_type is > MP_DECIMAL > + ok 152 - mp_decode_decimal_raw() > + ok 153 - mp_decode_decimal_raw() is correct > + ok 154 - mp_decode_extl + mp_decode_decimal_raw len > + ok 155 - mp_sizeof_decimal(0.00000000000000000000000000000000000001) > + ok 156 - mp_sizeof_decimal(0.00000000000000000000000000000000000001) == > len(mp_encode_decimal(0.00000000000000000000000000000000000001))) + ok > 157 - mp_next(mp_encode(0.00000000000000000000000000000000000001)) + ok > 158 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) len + > ok 159 - mp_decode(mp_encode(0.00000000000000000000000000000000000001)) > value + ok 160 - > mp_decode(mp_encode(0.00000000000000000000000000000000000001)) scale + > ok 161 - str(mp_decode(mp_encode())) == > 0.00000000000000000000000000000000000001 + ok 162 - mp_ext_type is > MP_DECIMAL > + ok 163 - mp_decode_decimal_raw() > + ok 164 - mp_decode_decimal_raw() is correct > + ok 165 - mp_decode_extl + mp_decode_decimal_raw len > + ok 166 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001) > + ok 167 - mp_sizeof_decimal(-0.00000000000000000000000000000000000001) > == len(mp_encode_decimal(-0.00000000000000000000000000000000000001))) + > ok 168 - mp_next(mp_encode(-0.00000000000000000000000000000000000001)) + > ok 169 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) > len + ok 170 - > mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) value + > ok 171 - mp_decode(mp_encode(-0.00000000000000000000000000000000000001)) > scale + ok 172 - str(mp_decode(mp_encode())) == > -0.00000000000000000000000000000000000001 + ok 173 - mp_ext_type is > MP_DECIMAL > + ok 174 - mp_decode_decimal_raw() > + ok 175 - mp_decode_decimal_raw() is correct > + ok 176 - mp_decode_extl + mp_decode_decimal_raw len > + ok 177 - mp_sizeof_decimal(99999999999999999999999999999999999999) > + ok 178 - mp_sizeof_decimal(99999999999999999999999999999999999999) == > len(mp_encode_decimal(99999999999999999999999999999999999999))) + ok 179 > - mp_next(mp_encode(99999999999999999999999999999999999999)) + ok 180 - > mp_decode(mp_encode(99999999999999999999999999999999999999)) len + ok > 181 - mp_decode(mp_encode(99999999999999999999999999999999999999)) value + > ok 182 - mp_decode(mp_encode(99999999999999999999999999999999999999)) > scale + ok 183 - str(mp_decode(mp_encode())) == > 99999999999999999999999999999999999999 + ok 184 - mp_ext_type is > MP_DECIMAL > + ok 185 - mp_decode_decimal_raw() > + ok 186 - mp_decode_decimal_raw() is correct > + ok 187 - mp_decode_extl + mp_decode_decimal_raw len > + ok 188 - mp_sizeof_decimal(-99999999999999999999999999999999999999) > + ok 189 - mp_sizeof_decimal(-99999999999999999999999999999999999999) == > len(mp_encode_decimal(-99999999999999999999999999999999999999))) + ok > 190 - mp_next(mp_encode(-99999999999999999999999999999999999999)) + ok > 191 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) len + > ok 192 - mp_decode(mp_encode(-99999999999999999999999999999999999999)) > value + ok 193 - > mp_decode(mp_encode(-99999999999999999999999999999999999999)) scale + ok > 194 - str(mp_decode(mp_encode())) == > -99999999999999999999999999999999999999 + ok 195 - mp_ext_type is > MP_DECIMAL > + ok 196 - mp_decode_decimal_raw() > + ok 197 - mp_decode_decimal_raw() is correct > + ok 198 - mp_decode_extl + mp_decode_decimal_raw len > + ok 199 - decode malformed decimal fails > + ok 200 - decode malformed decimal saves buffer position > +ok 51 - subtests [-- Attachment #2: This is a digitally signed message part. --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support 2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko 2019-06-05 8:20 ` Георгий Кириченко @ 2019-06-05 12:40 ` Vladimir Davydov 1 sibling, 0 replies; 12+ messages in thread From: Vladimir Davydov @ 2019-06-05 12:40 UTC (permalink / raw) To: Serge Petrenko; +Cc: georgy, tarantool-patches, kostja On Tue, Jun 04, 2019 at 07:04:19PM +0300, Serge Petrenko wrote: > Add methods to encode/decode decimals as msgpack. > > Part of #692 > --- > cmake/BuildDecNumber.cmake | 1 + > src/lib/core/decimal.c | 64 +++++++++++ > src/lib/core/decimal.h | 38 +++++++ > src/lib/core/mp_user_types.h | 38 +++++++ > test/unit/decimal.c | 73 ++++++++++++- > test/unit/decimal.result | 204 ++++++++++++++++++++++++++++++++++- > 6 files changed, 416 insertions(+), 2 deletions(-) > create mode 100644 src/lib/core/mp_user_types.h > > diff --git a/cmake/BuildDecNumber.cmake b/cmake/BuildDecNumber.cmake > index 80942fe05..abc6c64c4 100644 > --- a/cmake/BuildDecNumber.cmake > +++ b/cmake/BuildDecNumber.cmake > @@ -4,6 +4,7 @@ macro(decnumber_build) > set(decnumber_src > ${PROJECT_SOURCE_DIR}/third_party/decNumber/decNumber.c > ${PROJECT_SOURCE_DIR}/third_party/decNumber/decContext.c > + ${PROJECT_SOURCE_DIR}/third_party/decNumber/decPacked.c > ) > > add_library(decNumber STATIC ${decnumber_src}) > diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c > index 12250da95..7b3b1d8c9 100644 > --- a/src/lib/core/decimal.c > +++ b/src/lib/core/decimal.c > @@ -30,7 +30,10 @@ > */ > > #include "decimal.h" > +#include "mp_user_types.h" > +#include "src/lib/msgpuck/msgpuck.h" > #include "third_party/decNumber/decContext.h" > +#include "third_party/decNumber/decPacked.h" > #include <stdlib.h> > #include <assert.h> > > @@ -291,3 +294,64 @@ decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision) > > return decimal_finalize(res, &op_context); > } > + > +static inline uint32_t > +decimal_len(decimal *dec) > +{ > + /* 1 + ceil((digits + 1) / 2) */ Bad indentation. > + return 2 + dec->digits / 2; > +} > + > +uint32_t > +mp_sizeof_decimal(decimal *dec) { > + uint32_t l = decimal_len(dec); > + return mp_sizeof_extl(l) + l; Better use mp_sizeof_ext instead of mp_sizeof_extl here? > +} > + > +char * > +mp_encode_decimal(char *data, decimal *dec) > +{ > + uint32_t len = decimal_len(dec); > + data = mp_encode_extl(data, MP_DECIMAL, len); > + data = mp_store_u8(data, decimal_scale(dec)); > + len--; > + int32_t scale; > + char *tmp = (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec); > + assert(tmp == data); > + assert(scale == (int32_t)decimal_scale(dec)); > + data += len; > + return data; > +} > + > +decimal * > +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len) > +{ > + int32_t scale = mp_load_u8(data); > + len--; > + decimal *res = decPackedToNumber((uint8_t *)*data, len, &scale, dec); > + if (res) > + *data += len; > + else > + (*data)--; > + return res; > +} > + > +decimal * > +mp_decode_decimal(const char **data, decimal *dec) > +{ > + int8_t type; > + uint32_t len; > + > + if (mp_typeof(**data) != MP_EXT) > + return NULL; > + const char *const svp = *data; > + len = mp_decode_extl(data, &type); > + > + if (type != MP_DECIMAL || len == 0) > + return NULL; > + decimal *res = mp_decode_decimal_raw(data, dec, len); > + if (!res) { > + *data = svp; > + } > + return res; > +} > diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h > index f2812f500..cfcc98073 100644 > --- a/src/lib/core/decimal.h > +++ b/src/lib/core/decimal.h > @@ -156,4 +156,42 @@ decimal_exp(decimal *res, const decimal *lhs, uint32_t precision); > decimal * > decimal_sqrt(decimal *res, const decimal *lhs, uint32_t precision); > > + > +/** > + * Return length of message pack encoded decimal with extension header. > + * Equivalent to mp_sizeof_ext(decimal representation length) > + */ > +uint32_t > +mp_sizeof_decimal(decimal *dec); > + > +/** > + * Encode a decimal value to msgpack. > + * > + * @return data + mp_sizeof_decimal(dec) > + */ > +char * > +mp_encode_decimal(char *data, decimal *dec); > + > +/** > + * Decode a decimal value as though msgpack > + * ext header was already decoded. > + * > + * \post *data = *data + value representation length > + * @return NULL if value encoding is incorrect > + * dec otherwise. > + */ > +decimal * > +mp_decode_decimal_raw(const char **data, decimal *dec, uint32_t len); > + > +/** > + * Decode a decimal encoded as msgpack with ext header. > + * > + * \post *data = *data + mp_sizeof_decimal(dec) > + * @return NULL if mp_typeof(**data) != MP_EXT > + * or value encoding is incorrect > + * dec otherwise. > + */ > +decimal * > +mp_decode_decimal(const char **data, decimal *dec); > + Mixing mp_ and decimal_ methods in the same file looks ugly, because msgpack is just one format - we might add other formats in future and it doesn't sound like a good idea to implement their decoders in decimal.c. IMO decimal.h should only contain methods to pack/unpack decimals in some format, say decimal_pack_len/decimal_pack/decimal_unpack or decimal_encode/decimal_decode mp_ methods should live in some other file (mp_ext, mp_ext_decimal, mp_decimal.[hc]?) And I think it's better add and test those generic methods in patch 2, along with the decimal infrastructure while this patch should be about msgpack ext only. ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2019-06-11 16:14 UTC | newest] Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2019-06-04 16:04 [PATCH v3 0/4] Introduce decimal type to tarantool core Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 1/4] third-party: add decNumber library Serge Petrenko 2019-06-05 8:04 ` Георгий Кириченко 2019-06-04 16:04 ` [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Serge Petrenko 2019-06-05 8:11 ` Георгий Кириченко 2019-06-05 10:03 ` Serge Petrenko 2019-06-05 12:19 ` Vladimir Davydov 2019-06-11 16:14 ` [tarantool-patches] " Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 3/4] lib: update msgpuck library Serge Petrenko 2019-06-04 16:04 ` [PATCH v3 4/4] decimal: add MessagePack encoding/decoding support Serge Petrenko 2019-06-05 8:20 ` Георгий Кириченко 2019-06-05 12:40 ` Vladimir Davydov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox