[PATCH] decimal: add methods truncate and set_scale

Serge Petrenko sergepetrenko at tarantool.org
Fri Jul 19 14:21:36 MSK 2019


This patch adds 2 methods to decimal library and lua module:
truncate will remove all trailing fractional zeros and set_scale will
either perform rounding or append excess fractional zeros.

Closes #4372

@TarantoolBot document
Title: document 2 new functions in decimal Lua module

2 new functions are added to the decimal module:
`decimal.truncate()` and `decimal.set_scale()`

`decimal.truncate()` removes any trailing fractional zeros from the
number:
```
tarantool> a = dec.new('123.45570000')
---
...
tarantool> decimal.truncate(a)
---
- '123.4557'
...
```
`decimal.set_scale()` will round the number to a given scale, if it is
less than the number scale. Otherwise it will append trailing fractional
zeros, so that the resulting number scale will be the same as the given
one.
```
tarantool> a = dec.new(123.45)
---
...
tarantool> dec.set_scale(a,1)
---
- '123.5'
...
tarantool> dec.set_scale(a, 4)
---
- '123.4500'
...
```
---
https://github.com/tarantool/tarantool/issues/4372
https://github.com/tarantool/tarantool/tree/sp/gh-4372-decimal-truncate

 src/lib/core/decimal.c    | 28 +++++++++++++++++++++
 src/lib/core/decimal.h    | 19 ++++++++++++++
 src/lua/decimal.c         | 36 +++++++++++++++++++++++++++
 test/app/decimal.result   | 52 +++++++++++++++++++++++++++++++++++++++
 test/app/decimal.test.lua | 15 +++++++++++
 5 files changed, 150 insertions(+)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 1b4f44362..641d9feef 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -191,6 +191,34 @@ decimal_round(decimal_t *dec, int scale)
 	return dec;
 }
 
+decimal_t *
+decimal_truncate(decimal_t *dec)
+{
+	decimal_t *res = decNumberTrim(dec);
+
+	/* No errors are possible */
+	assert(res == dec);
+	return res;
+}
+
+decimal_t *
+decimal_set_scale(decimal_t *dec, int scale)
+{
+	if (scale < 0)
+		return NULL;
+	if (scale <= decimal_scale(dec))
+		return decimal_round(dec, scale);
+	/* how much zeros shoud we append. */
+	int delta = scale + dec->exponent;
+	if (scale > DECIMAL_MAX_DIGITS || dec->digits + delta > DECIMAL_MAX_DIGITS)
+		return NULL;
+	decimal_t new_scale;
+	decimal_from_int64(&new_scale, -scale);
+	decNumberRescale(dec, dec, &new_scale, &decimal_context);
+	assert(decimal_check_status(dec, &decimal_context) != NULL);
+	return dec;
+}
+
 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..f80f1a41b 100644
--- a/src/lib/core/decimal.h
+++ b/src/lib/core/decimal.h
@@ -122,6 +122,25 @@ decimal_compare(const decimal_t *lhs, const decimal_t *rhs);
 decimal_t *
 decimal_round(decimal_t *dec, int scale);
 
+/**
+ * Remove trailing zeros from the fractional part of a number.
+ * @return \a dec with trimmed fractional zeros.
+ */
+decimal_t *
+decimal_truncate(decimal_t *dec);
+
+/**
+ * Set scale of \a dec to \a scale.
+ * If \a scale is < than scale(\a dec),
+ * performs decimal_round().
+ * Otherwise appends a sufficient amount of trailing
+ * fractional zeros.
+ * @return NULL, scale < 0 or too big.
+ * @return \a dec with set scale.
+ */
+decimal_t *
+decimal_set_scale(decimal_t *dec, int scale);
+
 /**
  * res is set to the absolute value of dec
  * decimal_abs(&a, &a) is allowed.
diff --git a/src/lua/decimal.c b/src/lua/decimal.c
index e548cdb9d..db8c647db 100644
--- a/src/lua/decimal.c
+++ b/src/lua/decimal.c
@@ -264,10 +264,44 @@ ldecimal_round(struct lua_State *L)
 	int n = lua_tointeger(L, 2);
 	decimal_t *res = lua_pushdecimal(L);
 	*res = *lhs;
+	/*
+	 * If the operation fails, it just
+	 * leaves the number intact.
+	 */
 	decimal_round(res, n);
 	return 1;
 }
 
+static int
+ldecimal_truncate(struct lua_State *L)
+{
+	if (lua_gettop(L) < 1)
+		return luaL_error(L, "usage: decimal.truncate(decimal)");
+	decimal_t *lhs = lua_checkdecimal(L, 1);
+	decimal_t *res = lua_pushdecimal(L);
+	*res = *lhs;
+	/* truncate never fails */
+	decimal_truncate(res);
+	return 1;
+}
+
+static int
+ldecimal_set_scale(struct lua_State *L)
+{
+	if (lua_gettop(L) < 2)
+		return luaL_error(L, "usage: decimal.set_scale(decimal, scale)");
+	decimal_t *lhs = lua_checkdecimal(L, 1);
+	int n = lua_tointeger(L, 2);
+	decimal_t *res = lua_pushdecimal(L);
+	*res = *lhs;
+	/*
+	 * If the operation fails, it just
+	 * leaves the number intact.
+	 */
+	decimal_set_scale(res, n);
+	return 1;
+}
+
 static int
 ldecimal_scale(struct lua_State *L)
 {
@@ -321,6 +355,8 @@ static const luaL_Reg ldecimal_lib[] = {
 	{"sqrt", ldecimal_sqrt},
 	{"round", ldecimal_round},
 	{"scale", ldecimal_scale},
+	{"truncate", ldecimal_truncate},
+	{"set_scale", ldecimal_set_scale},
 	{"precision", ldecimal_precision},
 	{"abs", ldecimal_abs},
 	{"new", ldecimal_new},
diff --git a/test/app/decimal.result b/test/app/decimal.result
index ecb189277..9d58fe864 100644
--- a/test/app/decimal.result
+++ b/test/app/decimal.result
@@ -458,3 +458,55 @@ a ^ 2.5
  | ---
  | - error: '[string "return a ^ 2.5 "]:1: decimal operation failed'
  | ...
+
+a = decimal.new('1e5')
+ | ---
+ | ...
+a
+ | ---
+ | - '100000'
+ | ...
+decimal.truncate(a)
+ | ---
+ | - '100000'
+ | ...
+decimal.truncate(decimal.set_scale(a, 10))
+ | ---
+ | - '100000'
+ | ...
+decimal.set_scale(a, 10)
+ | ---
+ | - '100000.0000000000'
+ | ...
+decimal.set_scale(a, -5)
+ | ---
+ | - '100000'
+ | ...
+decimal.set_scale(a, 0)
+ | ---
+ | - '100000'
+ | ...
+decimal.set_scale(a, 32)
+ | ---
+ | - '100000.00000000000000000000000000000000'
+ | ...
+-- scale too big
+decimal.set_scale(a, 33)
+ | ---
+ | - '100000'
+ | ...
+decimal.truncate(decimal.set_scale(a, 10))
+ | ---
+ | - '100000'
+ | ...
+a = decimal.new('123.456789000000000')
+ | ---
+ | ...
+a
+ | ---
+ | - '123.456789000000000'
+ | ...
+decimal.truncate(a)
+ | ---
+ | - '123.456789'
+ | ...
diff --git a/test/app/decimal.test.lua b/test/app/decimal.test.lua
index 4aff0f2a4..6f2801f6d 100644
--- a/test/app/decimal.test.lua
+++ b/test/app/decimal.test.lua
@@ -128,3 +128,18 @@ a = decimal.new('-13')
 a ^ 2
 -- fractional powers are allowed only for positive numbers
 a ^ 2.5
+
+a = decimal.new('1e5')
+a
+decimal.truncate(a)
+decimal.truncate(decimal.set_scale(a, 10))
+decimal.set_scale(a, 10)
+decimal.set_scale(a, -5)
+decimal.set_scale(a, 0)
+decimal.set_scale(a, 32)
+-- scale too big
+decimal.set_scale(a, 33)
+decimal.truncate(decimal.set_scale(a, 10))
+a = decimal.new('123.456789000000000')
+a
+decimal.truncate(a)
-- 
2.20.1 (Apple Git-117)




More information about the Tarantool-patches mailing list