[PATCH 4/5] decimal: add conversions to (u)int64_t

Serge Petrenko sergepetrenko at tarantool.org
Wed Jul 17 18:33:45 MSK 2019


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   | 63 ++++++++++++++++++++++++++++++++--
 src/lib/core/decimal.h   | 12 +++++++
 test/unit/decimal.c      | 66 ++++++++++++++++++++++++++++++++++-
 test/unit/decimal.result | 74 ++++++++++++++++++++++++++++++++++++++--
 third_party/decNumber    |  2 +-
 5 files changed, 209 insertions(+), 8 deletions(-)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 2c69a773c..5a576e6e5 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -157,6 +157,57 @@ decimal_to_string(const decimal_t *dec)
 	return buf;
 }
 
+static decimal_t *
+decimal_round_with_mode(decimal_t *dec, int scale, enum rounding mode);
+
+decimal_t *
+decimal_to_int64(decimal_t *dec, int64_t *num)
+{
+	decimal_t d, z;
+	decNumberZero(&z);
+	d = *dec;
+	dec = &d;
+
+	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.
+		 */
+		dec = decimal_round_with_mode(dec, 0, DEC_ROUND_DOWN);
+	}
+	/*
+	 * decNumberToInt64 works only with numbers with
+	 * zero exponent.
+	 */
+	decNumberRescale(dec, dec, &z, &decimal_context);
+	if (decimal_check_status(dec, &decimal_context) == NULL) {
+		return NULL;
+	}
+	*num = decNumberToInt64(dec, &decimal_context);
+	return decimal_check_status(dec, &decimal_context);
+}
+
+decimal_t *
+decimal_to_uint64(decimal_t *dec, uint64_t *num)
+{
+	decimal_t d, z;
+	decNumberZero(&z);
+	d = *dec;
+	dec = &d;
+
+	if (decimal_scale(dec) != 0) {
+		dec = decimal_round_with_mode(dec, 0, DEC_ROUND_DOWN);
+	}
+	decNumberRescale(dec, dec, &z, &decimal_context);
+	if (decimal_check_status(dec, &decimal_context) == NULL) {
+		return NULL;
+	}
+	*num = decNumberToUInt64(dec, &decimal_context);
+	return decimal_check_status(dec, &decimal_context);
+}
+
 int
 decimal_compare(const decimal_t *lhs, const decimal_t *rhs)
 {
@@ -167,8 +218,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 +232,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 +243,12 @@ 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);
+}
+
 decimal_t *
 decimal_abs(decimal_t *res, const decimal_t *dec)
 {
diff --git a/src/lib/core/decimal.h b/src/lib/core/decimal.h
index 1d0f2582e..a4e7683c7 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(decimal_t *dec, int64_t *num);
+
+/** \sa decimal_to_int64 */
+decimal_t *
+decimal_to_uint64(decimal_t *dec, uint64_t *num);
+
 /**
  * Compare 2 decimal values.
  * @return -1, lhs < rhs,
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index dd64b95ed..6b609140b 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -5,6 +5,7 @@
 #include "msgpuck.h"
 #include <limits.h>
 #include <string.h>
+#include <inttypes.h>
 #include <float.h> /* 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)
 {
@@ -181,10 +191,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);
@@ -244,6 +306,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 6d00996d3..5a9c804bc 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..146
     ok 1 - decimal_len(0)
     ok 2 - decimal_len(0) == len(decimal_pack(0)
@@ -424,7 +492,7 @@ ok 278 - decimal_sqrt(-10) - error on wrong operands.
     ok 144 - str(decimal_unpack(decimal_pack(-99999999999999999999999999999999999999)) == -99999999999999999999999999999999999999
     ok 145 - unpack malformed decimal fails
     ok 146 - 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))
@@ -624,4 +692,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)




More information about the Tarantool-patches mailing list