From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 12.4 \(3445.104.11\)) Subject: Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool From: Serge Petrenko In-Reply-To: <2416023.dxzGcB0dMB@localhost> Date: Wed, 5 Jun 2019 13:03:12 +0300 Content-Transfer-Encoding: quoted-printable Message-Id: References: <5d4075e8429eeb84b1b6bed24af9ef28fced5576.1559663794.git.sergepetrenko@tarantool.org> <2416023.dxzGcB0dMB@localhost> To: Georgy Kirichenko Cc: Vladimir Davydov , tarantool-patches@freelists.org, kostja@tarantool.org List-ID: > 5 =D0=B8=D1=8E=D0=BD=D1=8F 2019 =D0=B3., =D0=B2 11:11, =D0=93=D0=B5=D0=BE= =D1=80=D0=B3=D0=B8=D0=B9 =D0=9A=D0=B8=D1=80=D0=B8=D1=87=D0=B5=D0=BD=D0=BA=D0= =BE =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB(=D0=B0= ): >=20 > Overall it looks great but please check travis has an error: > /tarantool/src/lib/core/decimal.c:319:8: error: unused variable = =E2=80=98tmp=E2=80=99 [- > Werror=3Dunused-variable] > char *tmp =3D (char *)decPackedFromNumber((uint8_t *)data, len, = &scale, dec); >=20 > Perhaps, it was caused by assert elimination because of the = Release-type=20 > 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 =3D (char *)decPackedFromNumber((uint8_t *)data, len, = &scale, dec); assert(tmp =3D=3D data); assert(scale =3D=3D (int32_t)decimal_scale(dec)); + (void)tmp; data +=3D len; return data; } >=20 > 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. >>=20 >> 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. >>=20 >> 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]. >>=20 >> Every mathematic function has precision as one of its parameters, and >> the results are rounded to fit the desired precision. >>=20 >> 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 >>=20 >> 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() >>=20 >> +# >> +# 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}) >>=20 >> 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 >> ) >>=20 >> if (TARGET_OS_NETBSD) >> @@ -37,7 +38,7 @@ endif() >>=20 >> add_library(core STATIC ${core_sources}) >>=20 >> -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}) >>=20 >> 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 ``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 >> + * 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 >> +#include >> + >> +#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 =3D { >> + /* Maximum precision during operations. */ >> + TARANTOOL_MAX_DECIMAL_DIGITS, >> + /* >> + * Maximum decimal lagarithm of the number. >> + * Allows for precision =3D 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) =3D >> + * exp(-TARANTOOL_MAX_DECIMAL_DIGITS) allowing for scale =3D >> + * 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 =3D 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 |=20 > 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 =3D decimal_get_op_status(context); >> + if (status || ! decNumberIsFinite(res)) { >> + return NULL; >> + } >> + return res; >> +} >> + >> +uint8_t decimal_precision(const decimal *dec) { >> + return dec->exponent <=3D 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 >=3D TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits =3D precision; >> + op_context.emax =3D op_context.digits - 1; >> + op_context.emin =3D -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 >=3D TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits =3D precision; >> + op_context.emax =3D op_context.digits - 1; >> + op_context.emin =3D -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 >=3D TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits =3D precision; >> + op_context.emax =3D op_context.digits - 1; >> + op_context.emin =3D -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 >=3D TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits =3D precision; >> + op_context.emax =3D op_context.digits - 1; >> + op_context.emin =3D -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 >=3D TARANTOOL_MAX_DECIMAL_DIGITS) >> + return NULL; >> + >> + /* A separate context to enforce desired precision. */ >> + static decContext op_context; >> + op_context.digits =3D precision; >> + op_context.emax =3D op_context.digits - 1; >> + op_context.emin =3D -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 ``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 >> + * 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 (>=3D 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 =3D 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) >>=20 >> 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 >> +#include >> + >> +int >> +main(void) >> +{ >> + plan(50); >> + >> + char buf[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; >> + char buf2[TARANTOOL_MAX_DECIMAL_DIGITS + 3]; >> + char *b =3D "2.718281828"; >> + >> + decimal s; >> + decimal *ret; >> + >> + ret =3D 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=20 > conversion."); >> + >> + ret =3D 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=20= > INT_MAX"); >> + >> + ret =3D 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=20= > INT_MIN"); >> + >> + char *up1 =3D "2.5"; >> + char *down1 =3D "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 =3D "1.23456789123456789123456789123456789123"; >> + ret =3D decimal_from_string(&s, l); >> + isnt(ret, NULL, "Precision too high. Rounding happens."); >> + ok(decimal_precision(&s) =3D=3D 38 && decimal_scale(&s) =3D=3D = 37 ,=20 > "Construction >> is correct."); + char *ll =3D=20 > "123456789123456789123456789123456789123"; >> + ret =3D decimal_from_string(&s, ll); >> + is(ret, NULL, "Precision too high and scale =3D 0. Cannot = round"); >> + >> + /* 38 digits. */ >> + char *long_str =3D "0.0000000000000000000000000000000000001"; >> + ret =3D decimal_from_string(&s, long_str); >> + isnt(ret, NULL, "Construncting the smallest possible number from=20= > string"); >> + decimal_to_string(&s, buf); >> + is(strcmp(buf, long_str), 0, "Correct representation of smallest=20= > possible >> number"); + >> + /* Comparsions. */ >> + char *max_str =3D "3.11", *min_str =3D "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 =3D=3D max"); >> + >> + ret =3D 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 =3D 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 =3D decimal_from_string(&s, b); >> + ret =3D decimal_from_string(&d, "1.25"); >> + sprintf(buf2, "%.9f", 1.25 + 2.718281828); >> + ret =3D 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 =3D 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=20 > correct"); >> + >> + decimal_from_int(&s, 4); >> + ret =3D 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=20 > correct"); >> + >> + ret =3D 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 =3D decimal_from_string(&s, "40.96"); >> + ret =3D decimal_from_string(&check, "6.4"); >> + ret =3D decimal_sqrt(&s, &s, 2); >> + isnt(ret, NULL, "sqrt"); >> + is(decimal_compare(&s, &check), 0, "sqrt is correct"); >> + >> + ret =3D decimal_from_string(&s, "40.96"); >> + ret =3D decimal_from_string(&d, "0.5"); >> + ret =3D decimal_pow(&s, &s, &d, 2); >> + isnt(ret, NULL, "pow"); >> + is(decimal_compare(&s, &check), 0, "pow is correct"); >> + >> + ret =3D decimal_from_string(&s, "3.000"); >> + ret =3D decimal_exp(&d, &s, 4); >> + isnt(ret, NULL, "exp"); >> + >> + ret =3D decimal_from_string(&check, "20.09"); >> + is(decimal_compare(&d, &check), 0, "exp is correct") >> + ret =3D 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 =3D decimal_from_string(&s, "3162.27766"); >> + ret =3D decimal_log10(&d, &s, 2); >> + isnt(ret, NULL, "log10"); >> + ret =3D decimal_from_string(&check, "3.5"); >> + is(decimal_compare(&d, &check), 0, "log10 is correct"); >> + >> + /* Advanced test. */ >> + /* 38 digits. */ >> + char *bignum =3D "33.333333333333333333333333333333333333"; >> + char *test =3D "133.33333333333333333333333333333333333"; >> + ret =3D decimal_from_string(&s, bignum); >> + ret =3D decimal_from_int(&d, 4); >> + ret =3D decimal_mul(&s, &s, &d); >> + isnt(ret, NULL, "Rounding when more than=20 > TARANTOOL_MAX_DECIMAL_DIGITS >> digits"); + ret =3D 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 =3D "0.000000000000000000000000001"; >> + ret =3D decimal_from_string(&s, small); >> + ret =3D decimal_mul(&s, &s, &s); >> + isnt(ret, NULL, "Rounding too small number to zero"); >> + ret =3D 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 =3D 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 =3D 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 =3D=3D 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 >=20