From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Serge Petrenko Subject: [PATCH v2 7/8] decimal: add conversions to (u)int64_t Date: Thu, 8 Aug 2019 14:55:58 +0300 Message-Id: In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: vdavydov.dev@gmail.com Cc: tarantool-patches@freelists.org, kostja@tarantool.org, Serge Petrenko List-ID: Update decNumber library, add methods to convert decimals to uint64_t and int64_t, add unit tests. Also replace decimal_round() function with decimal_round_with_mode() to allow setting rounding mode. We need to round with mode DEC_ROUND_DOWN in to_int64 conversions in order to be consistent with double to int conversions. It will be needed to compute hints for decimal fields. Prerequisite #4333 --- src/lib/core/decimal.c | 57 +++++++++++++++++++++++++++++-- src/lib/core/decimal.h | 19 +++++++++++ test/unit/decimal.c | 66 ++++++++++++++++++++++++++++++++++- test/unit/decimal.result | 74 ++++++++++++++++++++++++++++++++++++++-- third_party/decNumber | 2 +- 5 files changed, 210 insertions(+), 8 deletions(-) diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c index 840aa5dfe..19b5096ed 100644 --- a/src/lib/core/decimal.c +++ b/src/lib/core/decimal.c @@ -157,6 +157,45 @@ decimal_to_string(const decimal_t *dec) return buf; } +static decimal_t * +decimal_to_integer(decimal_t *dec) +{ + decimal_t z; + decNumberZero(&z); + if (decimal_scale(dec) != 0) { + /* + * Rounding mode is important here. + * We want to be consistent with double + * to int conversion so that comparison + * hints work correctly. + */ + decimal_floor(dec, 0); + } + /* Zero the number exponent for decNumberToInt64. */ + decNumberRescale(dec, dec, &z, &decimal_context); + return decimal_check_status(dec, &decimal_context); +} + +decimal_t * +decimal_to_int64(const decimal_t *dec, int64_t *num) +{ + decimal_t d = *dec; + if (decimal_to_integer(&d) == NULL) + return NULL; + *num = decNumberToInt64(&d, &decimal_context); + return decimal_check_status(&d, &decimal_context); +} + +decimal_t * +decimal_to_uint64(const decimal_t *dec, uint64_t *num) +{ + decimal_t d = *dec; + if (decimal_to_integer(&d) == NULL) + return NULL; + *num = decNumberToUInt64(&d, &decimal_context); + return decimal_check_status(&d, &decimal_context); +} + int decimal_compare(const decimal_t *lhs, const decimal_t *rhs) { @@ -167,8 +206,8 @@ decimal_compare(const decimal_t *lhs, const decimal_t *rhs) return r; } -decimal_t * -decimal_round(decimal_t *dec, int scale) +static decimal_t * +decimal_round_with_mode(decimal_t *dec, int scale, enum rounding mode) { if (scale < 0 || scale > DECIMAL_MAX_DIGITS) return NULL; @@ -181,7 +220,7 @@ decimal_round(decimal_t *dec, int scale) ndig, /* Precision */ ndig, /* emax */ scale != 0 ? -1 : 0, /* emin */ - DECIMAL_ROUNDING, /* rounding */ + mode, /* rounding */ 0, /* no traps */ 0, /* zero status */ 0 /* no clamping */ @@ -192,6 +231,18 @@ decimal_round(decimal_t *dec, int scale) return dec; } +inline decimal_t * +decimal_round(decimal_t *dec, int scale) +{ + return decimal_round_with_mode(dec, scale, DECIMAL_ROUNDING); +} + +inline decimal_t * +decimal_floor(decimal_t *dec, int scale) +{ + return decimal_round_with_mode(dec, scale, DEC_ROUND_DOWN); +} + decimal_t * decimal_trim(decimal_t *dec) { diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h index 6e2cd3ce7..cf41a5052 100644 --- a/src/lib/core/decimal.h +++ b/src/lib/core/decimal.h @@ -101,6 +101,18 @@ decimal_from_uint64(decimal_t *dec, uint64_t num); const char * decimal_to_string(const decimal_t *dec); +/** + * Convert a given decimal to int64_t + * \param[out] num - the result + * @return NULL if \a dec doesn't fit into int64_t + */ +decimal_t * +decimal_to_int64(const decimal_t *dec, int64_t *num); + +/** \sa decimal_to_int64 */ +decimal_t * +decimal_to_uint64(const decimal_t *dec, uint64_t *num); + /** * Compare 2 decimal values. * @return -1, lhs < rhs, @@ -122,6 +134,13 @@ decimal_compare(const decimal_t *lhs, const decimal_t *rhs); decimal_t * decimal_round(decimal_t *dec, int scale); +/** + * Round a decimal towards zero. + * \sa decimal_round + */ +decimal_t * +decimal_floor(decimal_t *dec, int scale); + /** * Remove trailing zeros from the fractional part of a number. * @return \a dec with trimmed fractional zeros. diff --git a/test/unit/decimal.c b/test/unit/decimal.c index a70fe9a9e..9afe17432 100644 --- a/test/unit/decimal.c +++ b/test/unit/decimal.c @@ -5,6 +5,7 @@ #include "msgpuck.h" #include #include +#include #include /* DBL_DIG */ #define success(x) x @@ -117,6 +118,15 @@ char buf[32]; is(strcmp(decimal_to_string(&d2), str), 0, "str(decimal_unpack(decimal_pack("str")) == "str);\ }) +#define test_toint(type, num, out_fmt) ({\ + decimal_t dec;\ + type##_t val;\ + decimal_from_##type(&dec, num);\ + isnt(decimal_to_##type(&dec, &val), NULL, "Conversion of %"out_fmt\ + " to decimal and back to "#type" successful", (type##_t) num);\ + is(val, num, "Conversion back to "#type" correct");\ +}) + static int test_pack_unpack(void) { @@ -192,10 +202,62 @@ test_mp_decimal(void) return check_plan(); } +static int +test_to_int(void) +{ + plan(66); + + test_toint(uint64, ULLONG_MAX, PRIu64); + test_toint(int64, LLONG_MAX, PRId64); + test_toint(int64, LLONG_MIN, PRId64); + test_toint(uint64, 0, PRIu64); + test_toint(int64, 0, PRId64); + test_toint(int64, -1, PRId64); + + /* test some arbitrary values. */ + test_toint(uint64, ULLONG_MAX / 157, PRIu64); + test_toint(int64, LLONG_MAX / 157, PRId64); + test_toint(int64, LLONG_MIN / 157, PRId64); + + test_toint(uint64, ULLONG_MAX / 157 / 151, PRIu64); + test_toint(int64, LLONG_MAX / 157 / 151, PRId64); + test_toint(int64, LLONG_MIN / 157 / 151, PRId64); + + test_toint(uint64, ULLONG_MAX / 157 / 151 / 149, PRIu64); + test_toint(int64, LLONG_MAX / 157 / 151 / 149, PRId64); + test_toint(int64, LLONG_MIN / 157 / 151 / 149, PRId64); + + test_toint(uint64, ULLONG_MAX / 157 / 151 / 149 / 139, PRIu64); + test_toint(int64, LLONG_MAX / 157 / 151 / 149 / 139, PRId64); + test_toint(int64, LLONG_MIN / 157 / 151 / 149 / 139, PRId64); + + test_toint(uint64, ULLONG_MAX / 157 / 151 / 149 / 139 / 137, PRIu64); + test_toint(int64, LLONG_MAX / 156 / 151 / 149 / 139 / 137, PRId64); + test_toint(int64, LLONG_MIN / 156 / 151 / 149 / 139 / 137, PRId64); + + test_toint(uint64, UINT_MAX, PRIu64); + test_toint(int64, INT_MAX, PRId64); + test_toint(int64, INT_MIN, PRId64); + + test_toint(uint64, UINT_MAX / 157, PRIu64); /* ~ 27356479 */ + test_toint(int64, INT_MAX / 157, PRId64); + test_toint(int64, INT_MIN / 157, PRId64); + + test_toint(uint64, UINT_MAX / 157 / 151, PRIu64); /* ~ 181168 */ + test_toint(int64, INT_MAX / 157 / 151, PRId64); + test_toint(int64, INT_MIN / 157 / 151, PRId64); + + test_toint(uint64, UINT_MAX / 157 / 151 / 149, PRIu64); /* ~ 1215 */ + test_toint(int64, INT_MAX / 157 / 151 / 149, PRId64); + test_toint(int64, INT_MIN / 157 / 151 / 149, PRId64); + + return check_plan(); +} + int main(void) { - plan(280); + plan(281); dectest(314, 271, uint64, uint64_t); dectest(65535, 23456, uint64, uint64_t); @@ -255,6 +317,8 @@ main(void) dectest_op1_fail(log10, -1); dectest_op1_fail(sqrt, -10); + test_to_int(); + test_pack_unpack(); test_mp_decimal(); diff --git a/test/unit/decimal.result b/test/unit/decimal.result index e6812a8dd..e8432fb36 100644 --- a/test/unit/decimal.result +++ b/test/unit/decimal.result @@ -1,4 +1,4 @@ -1..280 +1..281 ok 1 - decimal(314) ok 2 - decimal(271) ok 3 - decimal(314) + decimal(271) @@ -277,6 +277,74 @@ ok 275 - decimal_from_string(-1) ok 276 - decimal_log10(-1) - error on wrong operands. ok 277 - decimal_from_string(-10) ok 278 - decimal_sqrt(-10) - error on wrong operands. + 1..66 + ok 1 - Conversion of 18446744073709551615 to decimal and back to uint64 successful + ok 2 - Conversion back to uint64 correct + ok 3 - Conversion of 9223372036854775807 to decimal and back to int64 successful + ok 4 - Conversion back to int64 correct + ok 5 - Conversion of -9223372036854775808 to decimal and back to int64 successful + ok 6 - Conversion back to int64 correct + ok 7 - Conversion of 0 to decimal and back to uint64 successful + ok 8 - Conversion back to uint64 correct + ok 9 - Conversion of 0 to decimal and back to int64 successful + ok 10 - Conversion back to int64 correct + ok 11 - Conversion of -1 to decimal and back to int64 successful + ok 12 - Conversion back to int64 correct + ok 13 - Conversion of 117495185182863386 to decimal and back to uint64 successful + ok 14 - Conversion back to uint64 correct + ok 15 - Conversion of 58747592591431693 to decimal and back to int64 successful + ok 16 - Conversion back to int64 correct + ok 17 - Conversion of -58747592591431693 to decimal and back to int64 successful + ok 18 - Conversion back to int64 correct + ok 19 - Conversion of 778113809158035 to decimal and back to uint64 successful + ok 20 - Conversion back to uint64 correct + ok 21 - Conversion of 389056904579017 to decimal and back to int64 successful + ok 22 - Conversion back to int64 correct + ok 23 - Conversion of -389056904579017 to decimal and back to int64 successful + ok 24 - Conversion back to int64 correct + ok 25 - Conversion of 5222240329919 to decimal and back to uint64 successful + ok 26 - Conversion back to uint64 correct + ok 27 - Conversion of 2611120164959 to decimal and back to int64 successful + ok 28 - Conversion back to int64 correct + ok 29 - Conversion of -2611120164959 to decimal and back to int64 successful + ok 30 - Conversion back to int64 correct + ok 31 - Conversion of 37570074315 to decimal and back to uint64 successful + ok 32 - Conversion back to uint64 correct + ok 33 - Conversion of 18785037157 to decimal and back to int64 successful + ok 34 - Conversion back to int64 correct + ok 35 - Conversion of -18785037157 to decimal and back to int64 successful + ok 36 - Conversion back to int64 correct + ok 37 - Conversion of 274234119 to decimal and back to uint64 successful + ok 38 - Conversion back to uint64 correct + ok 39 - Conversion of 137996015 to decimal and back to int64 successful + ok 40 - Conversion back to int64 correct + ok 41 - Conversion of -137996015 to decimal and back to int64 successful + ok 42 - Conversion back to int64 correct + ok 43 - Conversion of 4294967295 to decimal and back to uint64 successful + ok 44 - Conversion back to uint64 correct + ok 45 - Conversion of 2147483647 to decimal and back to int64 successful + ok 46 - Conversion back to int64 correct + ok 47 - Conversion of -2147483648 to decimal and back to int64 successful + ok 48 - Conversion back to int64 correct + ok 49 - Conversion of 27356479 to decimal and back to uint64 successful + ok 50 - Conversion back to uint64 correct + ok 51 - Conversion of 13678239 to decimal and back to int64 successful + ok 52 - Conversion back to int64 correct + ok 53 - Conversion of -13678239 to decimal and back to int64 successful + ok 54 - Conversion back to int64 correct + ok 55 - Conversion of 181168 to decimal and back to uint64 successful + ok 56 - Conversion back to uint64 correct + ok 57 - Conversion of 90584 to decimal and back to int64 successful + ok 58 - Conversion back to int64 correct + ok 59 - Conversion of -90584 to decimal and back to int64 successful + ok 60 - Conversion back to int64 correct + ok 61 - Conversion of 1215 to decimal and back to uint64 successful + ok 62 - Conversion back to uint64 correct + ok 63 - Conversion of 607 to decimal and back to int64 successful + ok 64 - Conversion back to int64 correct + ok 65 - Conversion of -607 to decimal and back to int64 successful + ok 66 - Conversion back to int64 correct +ok 279 - subtests 1..151 ok 1 - decimal_len(0) ok 2 - decimal_len(0) == len(decimal_pack(0) @@ -429,7 +497,7 @@ ok 278 - decimal_sqrt(-10) - error on wrong operands. ok 149 - positive exponent number is packed/unpacked correctly ok 150 - unpack malformed decimal fails ok 151 - decode malformed decimal preserves buffer position -ok 279 - subtests +ok 280 - subtests 1..198 ok 1 - mp_sizeof_decimal(0) ok 2 - mp_sizeof_decimal(0) == len(mp_encode_decimal(0)) @@ -629,4 +697,4 @@ ok 279 - subtests ok 196 - decimal_unpack() after mp_decode_extl() ok 197 - decimal_unpack() after mp_decode_extl() value ok 198 - decimal_unpack() after mp_decode_extl() len -ok 280 - subtests +ok 281 - subtests diff --git a/third_party/decNumber b/third_party/decNumber index ee540fca6..878ed752f 160000 --- a/third_party/decNumber +++ b/third_party/decNumber @@ -1 +1 @@ -Subproject commit ee540fca6a44b3a2df7258dc7a1ec612cbf84dce +Subproject commit 878ed752f2453cd5e73587e67f7782aec9181a22 -- 2.20.1 (Apple Git-117)