From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: =?utf-8?B?0JPQtdC+0YDQs9C40Lkg0JrQuNGA0LjRh9C10L3QutC+?= Subject: Re: [PATCH v3 2/4] lib/core: introduce decimal type to tarantool Date: Wed, 05 Jun 2019 11:11:34 +0300 Message-ID: <2416023.dxzGcB0dMB@localhost> In-Reply-To: <5d4075e8429eeb84b1b6bed24af9ef28fced5576.1559663794.git.sergepetrenko@tarantool.org> References: <5d4075e8429eeb84b1b6bed24af9ef28fced5576.1559663794.git.sergepetrenko@tarantool.org> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="nextPart3558222.NWiJAZDXnb"; micalg="pgp-sha256"; protocol="application/pgp-signature" To: Serge Petrenko Cc: vdavydov.dev@gmail.com, tarantool-patches@freelists.org, kostja@tarantool.org List-ID: --nextPart3558222.NWiJAZDXnb Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="UTF-8" Overall it looks great but please check travis has an error: /tarantool/src/lib/core/decimal.c:319:8: error: unused variable =E2=80=98tm= p=E2=80=99 [- Werror=3Dunused-variable] char *tmp =3D (char *)decPackedFromNumber((uint8_t *)data, len, &scale, dec= ); Perhaps, it was caused by assert elimination because of the Release-type=20 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. >=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/decNumbe= r) > + 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 --nextPart3558222.NWiJAZDXnb Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEFZT35EtIMRTDS5hJnoTdFFzh6LUFAlz3eTYACgkQnoTdFFzh 6LWHygf9FHAs5cmq0hqoQQNcLg6d89kiu+MS0JPkYtoczk8xJyKbVieF2GQoPTbo fazxJNralybdJ9EgXikBU53xDVf4Kz16bCfxSxI6n/wp+YrGDDcbhVpchpDJJEeH TmDArRSz17Nyi8VHZOnzah8V83aLlCzOZv+46YUlmTi2W+pBW7OdikQ0bfN1+CCY q5fUap13Q/+G8oZV58k3jAorw2kCCKhutqfqlbudkklxwflGazBMEB6ugEwE4Ktp C40fNqMIiAG8ywG6/moFQzutgrB6YH/bvwLexqhBGkapMcPqehT8wb5ohbI5nNo7 3ZO+9xFqydyu6rCfaIErPRTAPEV+zA== =CUJk -----END PGP SIGNATURE----- --nextPart3558222.NWiJAZDXnb--