Tarantool development patches archive
 help / color / mirror / Atom feed
* [PATCH v3 0/5] decimal: expose decimal module to Lua.
@ 2019-07-02 17:27 Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 1/5] decimal: fix ln hang on values between ~ 0.9 and 1.1 Serge Petrenko
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Serge Petrenko @ 2019-07-02 17:27 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches, Serge Petrenko

https://github.com/tarantool/tarantool/issues/692
https://github.com/tarantool/tarantool/tree/sp/gh-692-decimal-lua

This patchset adds decimal module to lua.

The first three patches address some bugs found in decimal during the review
process.

The first patch fixes ln operation being stuck in an infinite loop for some
input values.

The second patch makes sure infinities and NaNs cannot be constructed neither
result in any operation.

The third patch changes the snprint format specifier used in double to decimal
conversion to the one trimming trailing zeros.

Since FFI CDATA is chosen to store decimals on lua stack, a new helper to
register FFI metatypes is added in the fourth patch.

The fifth patch does the job itself of exposing the decimal module to lua and
adds a minor test. The patch also contains a documentation request in the commit
message.

Changes in v3:
  - add 3 patches fixing bugs found during review.
  - increase test coverage.

Changes in v2:
 - various review fixes requested by @vdavydov
   - clarify commit message and docbot request.
   - remove decimal.add and similar. Only leave
     the '+', ... operators for binary operations.
   - add more tests.
   - minor code cleanup.

Serge Petrenko (5):
  decimal: fix ln hang on values between ~ 0.9 and 1.1
  decimal: diallow infinity and NaN entirely.
  decimal: fix string formatting on construction from double
  lua/utils: add a function to register FFI metatypes.
  decimal: expose decimal type to lua.

 src/CMakeLists.txt        |   1 +
 src/lib/core/decimal.c    | 102 ++++-----
 src/lua/decimal.c         | 351 +++++++++++++++++++++++++++++
 src/lua/decimal.h         |  47 ++++
 src/lua/init.c            |   2 +
 src/lua/utils.c           |  28 +++
 src/lua/utils.h           |  13 ++
 test/app/decimal.result   | 452 ++++++++++++++++++++++++++++++++++++++
 test/app/decimal.test.lua | 128 +++++++++++
 test/unit/decimal.c       |  24 +-
 test/unit/decimal.result  |  71 +++---
 11 files changed, 1137 insertions(+), 82 deletions(-)
 create mode 100644 src/lua/decimal.c
 create mode 100644 src/lua/decimal.h
 create mode 100644 test/app/decimal.result
 create mode 100644 test/app/decimal.test.lua

-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v3 1/5] decimal: fix ln hang on values between ~ 0.9 and 1.1
  2019-07-02 17:27 [PATCH v3 0/5] decimal: expose decimal module to Lua Serge Petrenko
@ 2019-07-02 17:27 ` Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 2/5] decimal: diallow infinity and NaN entirely Serge Petrenko
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Serge Petrenko @ 2019-07-02 17:27 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches, Serge Petrenko

Turns out decNumberLn hangs when result is subnormal, according to the
current context settings. To fix this, reset minimal allowed exponent
to a smaller value during the ln operation and round the result afterwards.

Follow-up 6d62c6c19ed418932ead1bba44fcd7cd84c78a19
---
 src/lib/core/decimal.c   | 19 +++++++++++++
 test/unit/decimal.c      |  5 +++-
 test/unit/decimal.result | 58 +++++++++++++++++++++++-----------------
 3 files changed, 56 insertions(+), 26 deletions(-)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 1f9b5838d..c76387d76 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -264,11 +264,30 @@ decimal_log10(decimal_t *res, const decimal_t *lhs)
 decimal_t *
 decimal_ln(decimal_t *res, const decimal_t *lhs)
 {
+	/*
+	 * ln hangs in an infinite loop when result is
+	 * between -10 ^ emin and 10 ^ emin.
+	 * For small x, ln(1 + x) = x. Say, we take the
+	 * smallest allowed value for
+	 * (1 + x) = 1 + 10 ^ -(DECIMAL_MAX_DIGITS - 1).
+	 * For ln to work for this value we need to set emin to
+	 * -DECIMAL_MAX_DIGITS.
+	 */
+	int32_t emin = decimal_context.emin;
+	decimal_context.emin = -DECIMAL_MAX_DIGITS;
+
 	decNumberLn(res, lhs, &decimal_context);
 
+	decimal_context.emin = emin;
 	if (decimal_check_status(&decimal_context) != 0) {
 		return NULL;
 	} else {
+		/*
+		 * The increased EMIN allows for scale up to
+		 * 2 * (DECIMAL_MAX_DIGITS - 1).
+		 * Round back to DECIMAL_MAX_DIGITS - 1.
+		 */
+		decimal_round(res, DECIMAL_MAX_DIGITS - 1);
 		return res;
 	}
 }
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index 7453d13ca..327a06d72 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -121,7 +121,7 @@ test_pack_unpack(void)
 int
 main(void)
 {
-	plan(258);
+	plan(266);
 
 	dectest(314, 271, uint64, uint64_t);
 	dectest(65535, 23456, uint64, uint64_t);
@@ -152,6 +152,9 @@ main(void)
 
 	dectest_op1(log10, 100, 2, 0);
 	dectest_op1(ln, 10, 2.3, 2);
+	dectest_op1(ln, 1.1, 0.1, 1);
+	dectest_op1(ln, 1.0000000000000000000000000000000000001,
+		    0.0000000000000000000000000000000000001, 0);
 	dectest_op1(exp, 2, 7.39, 2);
 	dectest_op1(sqrt, 100, 10, 0);
 
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index 051dc7960..7e767dcd2 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -1,4 +1,4 @@
-1..258
+1..266
 ok 1 - decimal(314)
 ok 2 - decimal(271)
 ok 3 - decimal(314) + decimal(271)
@@ -233,29 +233,37 @@ ok 231 - decimal_from_string(10)
 ok 232 - decimal_from_string(2.3)
 ok 233 - decimal_ln(10)
 ok 234 - decimal_compare(2.3)
-ok 235 - decimal_from_string(2)
-ok 236 - decimal_from_string(7.39)
-ok 237 - decimal_exp(2)
-ok 238 - decimal_compare(7.39)
-ok 239 - decimal_from_string(100)
-ok 240 - decimal_from_string(10)
-ok 241 - decimal_sqrt(100)
-ok 242 - decimal_compare(10)
-ok 243 - decimal construction from 2e38 failure
-ok 244 - decimal construction from "1e38" failure
-ok 245 - decimal construction from "100000000000000000000000000000000000000" failure
-ok 246 - decimal construction from LONG_MIN success
-ok 247 - decimal construction from LONG_MAX success
-ok 248 - decimal construction from ULONG_MAX success
-ok 249 - decimal_from_string(9e37)
-ok 250 - decimal_from_string(1e37)
-ok 251 - decimal_add(9e37, 1e37) - overflow
-ok 252 - decimal_from_string(1e19)
-ok 253 - decimal_from_string(1e19)
-ok 254 - decimal_mul(1e19, 1e19) - overflow
-ok 255 - decimal_from_string(1e19)
-ok 256 - decimal_from_string(1e-19)
-ok 257 - decimal_div(1e19, 1e-19) - overflow
+ok 235 - decimal_from_string(1.1)
+ok 236 - decimal_from_string(0.1)
+ok 237 - decimal_ln(1.1)
+ok 238 - decimal_compare(0.1)
+ok 239 - decimal_from_string(1.0000000000000000000000000000000000001)
+ok 240 - decimal_from_string(0.0000000000000000000000000000000000001)
+ok 241 - decimal_ln(1.0000000000000000000000000000000000001)
+ok 242 - decimal_compare(0.0000000000000000000000000000000000001)
+ok 243 - decimal_from_string(2)
+ok 244 - decimal_from_string(7.39)
+ok 245 - decimal_exp(2)
+ok 246 - decimal_compare(7.39)
+ok 247 - decimal_from_string(100)
+ok 248 - decimal_from_string(10)
+ok 249 - decimal_sqrt(100)
+ok 250 - decimal_compare(10)
+ok 251 - decimal construction from 2e38 failure
+ok 252 - decimal construction from "1e38" failure
+ok 253 - decimal construction from "100000000000000000000000000000000000000" failure
+ok 254 - decimal construction from LONG_MIN success
+ok 255 - decimal construction from LONG_MAX success
+ok 256 - decimal construction from ULONG_MAX success
+ok 257 - decimal_from_string(9e37)
+ok 258 - decimal_from_string(1e37)
+ok 259 - decimal_add(9e37, 1e37) - overflow
+ok 260 - decimal_from_string(1e19)
+ok 261 - decimal_from_string(1e19)
+ok 262 - decimal_mul(1e19, 1e19) - overflow
+ok 263 - decimal_from_string(1e19)
+ok 264 - decimal_from_string(1e-19)
+ok 265 - decimal_div(1e19, 1e-19) - overflow
     1..146
     ok 1 - decimal_len(0)
     ok 2 - decimal_len(0) == len(decimal_pack(0)
@@ -403,4 +411,4 @@ ok 257 - decimal_div(1e19, 1e-19) - overflow
     ok 144 - str(decimal_unpack(decimal_pack(-99999999999999999999999999999999999999)) == -99999999999999999999999999999999999999
     ok 145 - unpack malformed decimal fails
     ok 146 - decode malformed decimal preserves buffer position
-ok 258 - subtests
+ok 266 - subtests
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v3 2/5] decimal: diallow infinity and NaN entirely.
  2019-07-02 17:27 [PATCH v3 0/5] decimal: expose decimal module to Lua Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 1/5] decimal: fix ln hang on values between ~ 0.9 and 1.1 Serge Petrenko
@ 2019-07-02 17:27 ` Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 3/5] decimal: fix string formatting on construction from double Serge Petrenko
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Serge Petrenko @ 2019-07-02 17:27 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches, Serge Petrenko

While arithmetic operations do not return infinities or NaNs, it is
possbile to construct an invalid decimal value from strings 'Infinity',
'NaN' and similar. Some decimal mathematic functions may also result in
an infinity, say, ln(0) yields '-Infinity'.
So, add checks that the number is not a NaN or infinity after each
operation, so that the operation either returns an error, or a valid
finite decimal number.

Follow-up 6d62c6c19ed418932ead1bba44fcd7cd84c78a19
---
 src/lib/core/decimal.c   | 73 +++++++++++-----------------------------
 test/unit/decimal.c      | 18 +++++++++-
 test/unit/decimal.result | 39 ++++++++++++++-------
 3 files changed, 62 insertions(+), 68 deletions(-)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index c76387d76..91ee3f307 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -69,13 +69,14 @@ static __thread decContext decimal_context = {
 
 /**
  * A finalizer for all the operations.
- * Check the operation context status and empty it.
+ * Check the operation context status and empty it,
+ * check that result isn't a NaN or infinity.
  *
  * @return NULL if finalization failed.
  *         result pointer otherwise.
  */
-static inline int
-decimal_check_status(decContext *context)
+static decimal_t *
+decimal_check_status(decimal_t *dec, decContext *context)
 {
 	uint32_t status = decContextGetStatus(context);
 	decContextZeroStatus(context);
@@ -88,7 +89,7 @@ decimal_check_status(decContext *context)
 	 */
 	status &= ~(uint32_t)(DEC_Inexact | DEC_Rounded | DEC_Underflow |
 			      DEC_Subnormal | DEC_Clamped);
-	return status ? -1 : 0;
+	return status || !decNumberIsFinite(dec) ? NULL : dec;
 }
 
 int decimal_precision(const decimal_t *dec) {
@@ -111,11 +112,7 @@ decimal_t *
 decimal_from_string(decimal_t *dec, const char *str)
 {
 	decNumberFromString(dec, str, &decimal_context);
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return dec;
-	}
+	return decimal_check_status(dec, &decimal_context);
 }
 
 decimal_t *
@@ -157,7 +154,7 @@ decimal_compare(const decimal_t *lhs, const decimal_t *rhs)
 	decNumber res;
 	decNumberCompare(&res, lhs, rhs, &decimal_context);
 	int r = decNumberToInt32(&res, &decimal_context);
-	assert(decimal_check_status(&decimal_context) == 0);
+	assert(decimal_check_status(&res, &decimal_context) != NULL);
 	return r;
 }
 
@@ -182,7 +179,7 @@ decimal_round(decimal_t *dec, int scale)
 	};
 
 	decNumberPlus(dec, dec, &context);
-	assert(decimal_check_status(&context) == 0);
+	assert(decimal_check_status(dec, &context) != NULL);
 	return dec;
 }
 
@@ -190,7 +187,7 @@ decimal_t *
 decimal_abs(decimal_t *res, const decimal_t *dec)
 {
 	decNumberAbs(res, dec, &decimal_context);
-	assert(decimal_check_status(&decimal_context) == 0);
+	assert(decimal_check_status(res, &decimal_context) != NULL);
 	return res;
 }
 
@@ -198,7 +195,7 @@ decimal_t *
 decimal_minus(decimal_t *res, const decimal_t *dec)
 {
 	decNumberMinus(res, dec, &decimal_context);
-	assert(decimal_check_status(&decimal_context) == 0);
+	assert(decimal_check_status(res, &decimal_context) != NULL);
 	return res;
 }
 
@@ -206,11 +203,7 @@ decimal_t *
 decimal_add(decimal_t *res, const decimal_t *lhs, const decimal_t *rhs)
 {
 	decNumberAdd(res, lhs, rhs, &decimal_context);
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 decimal_t *
@@ -218,11 +211,7 @@ decimal_sub(decimal_t *res, const decimal_t *lhs, const decimal_t *rhs)
 {
 	decNumberSubtract(res, lhs, rhs, &decimal_context);
 
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 decimal_t *
@@ -230,11 +219,7 @@ decimal_mul(decimal_t *res, const decimal_t *lhs, const decimal_t *rhs)
 {
 	decNumberMultiply(res, lhs, rhs, &decimal_context);
 
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 decimal_t *
@@ -242,11 +227,7 @@ decimal_div(decimal_t *res, const decimal_t *lhs, const decimal_t *rhs)
 {
 	decNumberDivide(res, lhs, rhs, &decimal_context);
 
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 decimal_t *
@@ -254,11 +235,7 @@ decimal_log10(decimal_t *res, const decimal_t *lhs)
 {
 	decNumberLog10(res, lhs, &decimal_context);
 
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 decimal_t *
@@ -279,7 +256,7 @@ decimal_ln(decimal_t *res, const decimal_t *lhs)
 	decNumberLn(res, lhs, &decimal_context);
 
 	decimal_context.emin = emin;
-	if (decimal_check_status(&decimal_context) != 0) {
+	if (decimal_check_status(res, &decimal_context) == NULL) {
 		return NULL;
 	} else {
 		/*
@@ -297,11 +274,7 @@ decimal_pow(decimal_t *res, const decimal_t *lhs, const decimal_t *rhs)
 {
 	decNumberPower(res, lhs, rhs, &decimal_context);
 
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 decimal_t *
@@ -309,11 +282,7 @@ decimal_exp(decimal_t *res, const decimal_t *lhs)
 {
 	decNumberExp(res, lhs, &decimal_context);
 
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 decimal_t *
@@ -321,11 +290,7 @@ decimal_sqrt(decimal_t *res, const decimal_t *lhs)
 {
 	decNumberSquareRoot(res, lhs, &decimal_context);
 
-	if (decimal_check_status(&decimal_context) != 0) {
-		return NULL;
-	} else {
-		return res;
-	}
+	return decimal_check_status(res, &decimal_context);
 }
 
 uint32_t
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index 327a06d72..ae806a106 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -62,6 +62,12 @@
 	is(decimal_##op(&c, &a, &b), NULL, "decimal_"#op"("#stra", "#strb") - overflow");\
 })
 
+#define dectest_op1_fail(op, stra) ({\
+	decimal_t a, b;\
+	is(decimal_from_string(&a, #stra), &a, "decimal_from_string("#stra")");\
+	is(decimal_##op(&b, &a), NULL, "decimal_"#op"("#stra") - error on wrong operands.");\
+})
+
 char buf[32];
 
 #define test_decpack(str) ({\
@@ -121,7 +127,7 @@ test_pack_unpack(void)
 int
 main(void)
 {
-	plan(266);
+	plan(279);
 
 	dectest(314, 271, uint64, uint64_t);
 	dectest(65535, 23456, uint64, uint64_t);
@@ -162,6 +168,10 @@ main(void)
 	dectest_construct(double, 2e38, failure);
 	dectest_construct(string, "1e38", failure);
 	dectest_construct(string, "100000000000000000000000000000000000000", failure);
+	/* Check that inf and NaN are not allowed. Check bad input. */
+	dectest_construct(string, "inf", failure);
+	dectest_construct(string, "NaN", failure);
+	dectest_construct(string, "a random string", failure);
 
 	dectest_construct(int64, LONG_MIN, success);
 	dectest_construct(int64, LONG_MAX, success);
@@ -171,6 +181,12 @@ main(void)
 	dectest_op_fail(mul, 1e19, 1e19);
 	dectest_op_fail(div, 1e19, 1e-19);
 
+	dectest_op1_fail(ln, 0);
+	dectest_op1_fail(ln, -1);
+	dectest_op1_fail(log10, 0);
+	dectest_op1_fail(log10, -1);
+	dectest_op1_fail(sqrt, -10);
+
 	test_pack_unpack();
 
 	return check_plan();
diff --git a/test/unit/decimal.result b/test/unit/decimal.result
index 7e767dcd2..1c72cdfab 100644
--- a/test/unit/decimal.result
+++ b/test/unit/decimal.result
@@ -1,4 +1,4 @@
-1..266
+1..279
 ok 1 - decimal(314)
 ok 2 - decimal(271)
 ok 3 - decimal(314) + decimal(271)
@@ -252,18 +252,31 @@ ok 250 - decimal_compare(10)
 ok 251 - decimal construction from 2e38 failure
 ok 252 - decimal construction from "1e38" failure
 ok 253 - decimal construction from "100000000000000000000000000000000000000" failure
-ok 254 - decimal construction from LONG_MIN success
-ok 255 - decimal construction from LONG_MAX success
-ok 256 - decimal construction from ULONG_MAX success
-ok 257 - decimal_from_string(9e37)
-ok 258 - decimal_from_string(1e37)
-ok 259 - decimal_add(9e37, 1e37) - overflow
-ok 260 - decimal_from_string(1e19)
-ok 261 - decimal_from_string(1e19)
-ok 262 - decimal_mul(1e19, 1e19) - overflow
+ok 254 - decimal construction from "inf" failure
+ok 255 - decimal construction from "NaN" failure
+ok 256 - decimal construction from "a random string" failure
+ok 257 - decimal construction from LONG_MIN success
+ok 258 - decimal construction from LONG_MAX success
+ok 259 - decimal construction from ULONG_MAX success
+ok 260 - decimal_from_string(9e37)
+ok 261 - decimal_from_string(1e37)
+ok 262 - decimal_add(9e37, 1e37) - overflow
 ok 263 - decimal_from_string(1e19)
-ok 264 - decimal_from_string(1e-19)
-ok 265 - decimal_div(1e19, 1e-19) - overflow
+ok 264 - decimal_from_string(1e19)
+ok 265 - decimal_mul(1e19, 1e19) - overflow
+ok 266 - decimal_from_string(1e19)
+ok 267 - decimal_from_string(1e-19)
+ok 268 - decimal_div(1e19, 1e-19) - overflow
+ok 269 - decimal_from_string(0)
+ok 270 - decimal_ln(0) - error on wrong operands.
+ok 271 - decimal_from_string(-1)
+ok 272 - decimal_ln(-1) - error on wrong operands.
+ok 273 - decimal_from_string(0)
+ok 274 - decimal_log10(0) - error on wrong operands.
+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..146
     ok 1 - decimal_len(0)
     ok 2 - decimal_len(0) == len(decimal_pack(0)
@@ -411,4 +424,4 @@ ok 265 - decimal_div(1e19, 1e-19) - overflow
     ok 144 - str(decimal_unpack(decimal_pack(-99999999999999999999999999999999999999)) == -99999999999999999999999999999999999999
     ok 145 - unpack malformed decimal fails
     ok 146 - decode malformed decimal preserves buffer position
-ok 266 - subtests
+ok 279 - subtests
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v3 3/5] decimal: fix string formatting on construction from double
  2019-07-02 17:27 [PATCH v3 0/5] decimal: expose decimal module to Lua Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 1/5] decimal: fix ln hang on values between ~ 0.9 and 1.1 Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 2/5] decimal: diallow infinity and NaN entirely Serge Petrenko
@ 2019-07-02 17:27 ` Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 4/5] lua/utils: add a function to register FFI metatypes Serge Petrenko
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Serge Petrenko @ 2019-07-02 17:27 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches, Serge Petrenko

Use printf "%g" option instead of "%f" to trim traling zeros in such
cases:
decimal_from_double(1) -> '1.000000000000000' -> decimal_from_string()
Now it should be
decimal_from_double(1) -> '1' ->decimal_from_string()

Follow-up 6d62c6c19ed418932ead1bba44fcd7cd84c78a19
---
 src/lib/core/decimal.c | 10 +++++++++-
 test/unit/decimal.c    |  3 ++-
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/lib/core/decimal.c b/src/lib/core/decimal.c
index 91ee3f307..1b4f44362 100644
--- a/src/lib/core/decimal.c
+++ b/src/lib/core/decimal.c
@@ -121,7 +121,15 @@ decimal_from_double(decimal_t *dec, double d)
 	char buf[DECIMAL_MAX_DIGITS + 3];
 	if (isinf(d) || isnan(d))
 		return NULL;
-	snprintf(buf, sizeof(buf), "%.*f", DBL_DIG, d);
+	/*
+	 * DBL_DIG is 15, it is the guaranteed amount of
+	 * correct significant decimal digits in a double
+	 * value.  There is no point in using higher precision,
+	 * since every non-representable number has a long
+	 * tail of erroneous digits:
+	 * `23.42` -> `23.420000000000001705302565824240446091`
+	 */
+	snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, d);
 	return decimal_from_string(dec, buf);
 }
 
diff --git a/test/unit/decimal.c b/test/unit/decimal.c
index ae806a106..b587e1f14 100644
--- a/test/unit/decimal.c
+++ b/test/unit/decimal.c
@@ -27,7 +27,8 @@
 	\
 	is(decimal_div(&t, &u, &v), &t, "decimal("#a") / decimal("#b")");\
 	is(decimal_from_double(&w, (double)((a)) / (b)), &w, "decimal(("#a") / ("#b"))");\
-	is(decimal_round(&t, DBL_DIG), &t, "decimal_round(("#a")/("#b"), %d)", DBL_DIG);\
+	is(decimal_round(&t, DBL_DIG - decimal_precision(&t) + decimal_scale(&t)), &t,\
+	   "decimal_round(("#a")/("#b"), %d)", DBL_DIG);\
 	is(decimal_compare(&t, &w), 0, "decimal("#a") / decimal("#b") == ("#a") / ("#b")");\
 })
 
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v3 4/5] lua/utils: add a function to register FFI metatypes.
  2019-07-02 17:27 [PATCH v3 0/5] decimal: expose decimal module to Lua Serge Petrenko
                   ` (2 preceding siblings ...)
  2019-07-02 17:27 ` [PATCH v3 3/5] decimal: fix string formatting on construction from double Serge Petrenko
@ 2019-07-02 17:27 ` Serge Petrenko
  2019-07-02 17:27 ` [PATCH v3 5/5] decimal: expose decimal type to lua Serge Petrenko
  2019-07-08 14:25 ` [PATCH v3 0/5] decimal: expose decimal module to Lua Vladimir Davydov
  5 siblings, 0 replies; 8+ messages in thread
From: Serge Petrenko @ 2019-07-02 17:27 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches, Serge Petrenko

A ffi metatype has a CTypeID, which can be used to push cdata of the
type on the lua stack, and has an associated metatable, automatically
applied to every created member of the type.
This allows the behavior similar to pushing userdata and assigning a
metatable to it.

Needed for #692
---
 src/lua/utils.c | 28 ++++++++++++++++++++++++++++
 src/lua/utils.h | 13 +++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/src/lua/utils.c b/src/lua/utils.c
index 01a0cd894..0a4bcf517 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -141,6 +141,34 @@ luaL_ctypeid(struct lua_State *L, const char *ctypename)
 	return ctypeid;
 }
 
+uint32_t
+luaL_metatype(struct lua_State *L, const char *ctypename,
+	      const struct luaL_Reg *methods)
+{
+	/* Create a metatable for our ffi metatype. */
+	luaL_register_type(L, ctypename, methods);
+	int idx = lua_gettop(L);
+	/*
+	 * Get ffi.metatype function. It is like typeof with
+	 * an additional effect of registering a metatable for
+	 * all the cdata objects of the type.
+	 */
+	luaL_loadstring(L, "return require('ffi').metatype");
+	lua_call(L, 0, 1);
+	assert(lua_gettop(L) == idx + 1 && lua_isfunction(L, idx + 1));
+	lua_pushstring(L, ctypename);
+	/* Push the freshly created metatable as the second parameter. */
+	luaL_getmetatable(L, ctypename);
+	assert(lua_gettop(L) == idx + 3 && lua_istable(L, idx + 3));
+	lua_call(L, 2, 1);
+	uint32_t ctypetypeid;
+	CTypeID ctypeid = *(CTypeID *)luaL_checkcdata(L, idx + 1, &ctypetypeid);
+	assert(ctypetypeid == CTID_CTYPEID);
+
+	lua_settop(L, idx);
+	return ctypeid;
+}
+
 int
 luaL_cdef(struct lua_State *L, const char *what)
 {
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 943840ec0..7e7cdc0c6 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -129,6 +129,19 @@ luaL_cdef(struct lua_State *L, const char *ctypename);
 
 /** \endcond public */
 
+/**
+ * @brief Return CTypeID (FFI) of given CDATA type,
+ * register a metatable with \a methods to be
+ * associated with every value of the given
+ * type on its creation iva FFI.
+ * @sa luaL_register_type
+ * @sa luaL_ctypeid
+ * @return CTypeID
+ */
+uint32_t
+luaL_metatype(struct lua_State *L, const char *ctypename,
+	      const struct luaL_Reg *methods);
+
 static inline lua_Integer
 luaL_arrlen(struct lua_State *L, int idx)
 {
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v3 5/5] decimal: expose decimal type to lua.
  2019-07-02 17:27 [PATCH v3 0/5] decimal: expose decimal module to Lua Serge Petrenko
                   ` (3 preceding siblings ...)
  2019-07-02 17:27 ` [PATCH v3 4/5] lua/utils: add a function to register FFI metatypes Serge Petrenko
@ 2019-07-02 17:27 ` Serge Petrenko
  2019-07-03  6:50   ` [tarantool-patches] " Serge Petrenko
  2019-07-08 14:25 ` [PATCH v3 0/5] decimal: expose decimal module to Lua Vladimir Davydov
  5 siblings, 1 reply; 8+ messages in thread
From: Serge Petrenko @ 2019-07-02 17:27 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: tarantool-patches, Serge Petrenko

Add a decimal library to lua.

Part of #692

@TarantoolBot document
Title: Document decimal module in lua.

First of all, you have to require the package via
`decimal = require('decimal')`
Now you can construct decimals via `new` method.
Decimals may be constructed from lua numbers, strings, unsigned and
signed 64 bit integers.
Decimal is a fixed-point type with maximum 38 digits of precision. All
the calculations are exact, so, be careful when constructing decimals
from lua numbers: they may hold only 15 decimal digits of precision.
You are advised to construct decimals from strings, since strings
represent decimals exactly, and vice versa.

```
a = decimal.new(123e-7)
b = decimal.new('123.456')
c = decimal.new('123.456e2')
d = decimal.new(123ULL)
e = decimal.new(2)
```
The allowed operations are addition, subtraction, division,
multiplication and power. If at least one of the operands is decimal,
decimal operations are performed. The other operand may be either
decimal or string, containing a number representation, or a lua number.

Operations only fail on an overflow, i.e. when result exceeds 10^38 - 1.
This includes division by zero. In these cases an error `Operation
failed` is raised.
Underflow is also possible, when precision needed to store the exact
result exceeds 38 digits. Underflow is not an error. When an underflow
happens, the result is rounded to 38 digits of precision.

```
a = decimal.new(123e-7)
b = decimal.new('123.456')
c = decimal.new('123.456e2')
d = decimal.new(123ULL)
e = decimal.new(2)
```
```
tarantool> a + b
---
- '123.456012300000000'
...

tarantool> c - d
---
- '12222.6'
...

tarantool> c / b
---
- '100'
...

tarantool> d * d
---
- '15129'
...

tarantool> d ^ 2
---
- '15129'
...

tarantool> 2 ^ d
---
- '10633823966279326983230456482242756608'...

tarantool> e ^ d
---
- '10633823966279326983230456482242756608'
...
```
The following math functions are also supported:
log10, ln, exp, sqrt. When specified as
`decimal.opname()`, operations may be performed on
strings and lua numbers.
```
f = decimal.new(100)

tarantool> decimal.log10(f)
---
- '2'
...

tarantool> decimal.sqrt(f)
---
- '10'
...

tarantool> e2 = decimal.exp(2)
---
...

tarantool> decimal.ln(e2)
---
- '2.0000000000000000000000000000000000000'
...

There are also `abs` and `tostring` methods, and an unary minus
operator, which are pretty self-explanatory.

```
tarantool> a = decimal.new('-5')
---
...

tarantool> a
---
- '-5'
...

tarantool> decimal.abs(a)
---
- '5'
...

tarantool> -a
---
- '5'
...

tostring(a)
---
- '-5'
...

```

`decimal.precision`, `decimal.scale` and `decimal.round` :
The first two methods return precision, i.e. decimal digits in
number representation, and scale, i.e. decimal digits after the decimal
point in the number representation.
`decimal.round` rounds the number to the given scale.
```
tarantool> a = decimal.new('123.456789')
---
...

tarantool> decimal.precision(a)
---
- 9
...

tarantool> decimal.scale(a)
---
- 6
...

tarantool> decimal.round(a, 4)
---
- '123.4568'
...

```

Comparsions: `>`, `<`, `>=`, `<=`, `==` are also legal and work as
expected. You may compare decimals with lua numbers or strings. In that
case comparsion will happen after the values are converted to decimal
type.
---
 src/CMakeLists.txt        |   1 +
 src/lua/decimal.c         | 351 +++++++++++++++++++++++++++++
 src/lua/decimal.h         |  47 ++++
 src/lua/init.c            |   2 +
 test/app/decimal.result   | 452 ++++++++++++++++++++++++++++++++++++++
 test/app/decimal.test.lua | 128 +++++++++++
 6 files changed, 981 insertions(+)
 create mode 100644 src/lua/decimal.c
 create mode 100644 src/lua/decimal.h
 create mode 100644 test/app/decimal.result
 create mode 100644 test/app/decimal.test.lua

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 33b64f6a6..acd719e9b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -120,6 +120,7 @@ set (server_sources
      lua/string.c
      lua/buffer.c
      lua/swim.c
+     lua/decimal.c
      ${lua_sources}
      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc
      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c
diff --git a/src/lua/decimal.c b/src/lua/decimal.c
new file mode 100644
index 000000000..aecd88175
--- /dev/null
+++ b/src/lua/decimal.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2010-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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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 "lua/decimal.h"
+#include "lib/core/decimal.h"
+#include "lua/utils.h"
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#define LDECIMAL_BINOP(name, opname)						\
+static int									\
+ldecimal_##name(struct lua_State *L) {						\
+	assert(lua_gettop(L) == 2);						\
+	decimal_t *lhs = lua_todecimal(L, 1);					\
+	decimal_t *rhs = lua_todecimal(L, 2);					\
+	decimal_t *res = lua_pushdecimal(L);					\
+	if (decimal_##opname(res, lhs, rhs) == NULL) {				\
+		lua_pop(L, 1);							\
+		luaL_error(L, "decimal operation failed");			\
+	}									\
+	return 1;								\
+}
+
+#define LDECIMAL_FUNC(name, opname)						\
+static int									\
+ldecimal_##name(struct lua_State *L) {						\
+	if (lua_gettop(L) < 1)							\
+		return luaL_error(L, "usage: decimal."#name"(decimal)");	\
+	decimal_t *lhs = lua_todecimal(L, 1);					\
+	decimal_t *res = lua_pushdecimal(L);					\
+	if (decimal_##opname(res, lhs) == NULL) {				\
+		lua_pop(L, 1);							\
+		luaL_error(L, "decimal operation failed");			\
+	}									\
+	return 1;								\
+}
+
+#define LDECIMAL_CMPOP(name, cmp)						\
+static int									\
+ldecimal_##name(struct lua_State *L) {						\
+	assert(lua_gettop(L) == 2);						\
+	decimal_t *lhs = lua_todecimal(L, 1);					\
+	decimal_t *rhs = lua_todecimal(L, 2);					\
+	lua_pushboolean(L, decimal_compare(lhs, rhs) cmp 0);			\
+	return 1;								\
+}
+
+static uint32_t CTID_DECIMAL;
+
+/** Push a new decimal on the stack and return a pointer to it. */
+static decimal_t *
+lua_pushdecimal(struct lua_State *L)
+{
+	decimal_t *res = luaL_pushcdata(L, CTID_DECIMAL);
+	return res;
+}
+
+/** Check whether a value at a given index is a decimal. */
+static decimal_t *
+lua_checkdecimal(struct lua_State *L, int index)
+{
+	uint32_t ctypeid;
+	decimal_t *res = luaL_checkcdata(L, index, &ctypeid);
+	if (ctypeid != CTID_DECIMAL)
+		luaL_error(L, "expected decimal as %d argument", index);
+	return res;
+}
+
+/**
+ * Convert the value at the given index to a decimal in place.
+ * The possible conversions are string->decimal and number->decimal.
+ */
+static decimal_t *
+lua_todecimal(struct lua_State *L, int index)
+{
+	/*
+	 * Convert the index, if it is given relative to the top.
+	 * Othervise it will point to a wrong position after
+	 * pushdecimal().
+	 */
+	if (index < 0)
+		index = lua_gettop(L) + index + 1;
+	decimal_t *res = lua_pushdecimal(L);
+	switch(lua_type(L, index))
+	{
+	case LUA_TNUMBER:
+	{
+		double n = lua_tonumber(L, index);
+		if (decimal_from_double(res, n) == NULL)
+			goto err;
+		break;
+	}
+	case LUA_TSTRING:
+	{
+		const char *str = lua_tostring(L, index);
+		if (decimal_from_string(res, str) == NULL)
+			goto err;
+		break;
+	}
+	case LUA_TCDATA:
+	{
+		uint32_t ctypeid;
+		void *cdata = luaL_checkcdata(L, index, &ctypeid);
+		int64_t ival;
+		uint64_t uval;
+		double d;
+		if (ctypeid == CTID_DECIMAL) {
+			/*
+			 * We already have a decimal at the
+			 * desired position.
+			 */
+			lua_pop(L, 1);
+			return (decimal_t *) cdata;
+		}
+		switch (ctypeid)
+		{
+		case CTID_CCHAR:
+		case CTID_INT8:
+			ival = *(int8_t *) cdata;
+			/*
+			 * no errors are possible in decimal from
+			 * (u)int construction.
+			 */
+			decimal_from_int64(res, ival);
+			break;
+		case CTID_INT16:
+			ival = *(int16_t *) cdata;
+			decimal_from_int64(res, ival);
+			break;
+		case CTID_INT32:
+			ival = *(int32_t *) cdata;
+			decimal_from_int64(res, ival);
+			break;
+		case CTID_INT64:
+			ival = *(int64_t *) cdata;
+			decimal_from_int64(res, ival);
+			break;
+		case CTID_UINT8:
+			uval = *(uint8_t *) cdata;
+			decimal_from_uint64(res, uval);
+			break;
+		case CTID_UINT16:
+			uval = *(uint16_t *) cdata;
+			decimal_from_uint64(res, uval);
+			break;
+		case CTID_UINT32:
+			uval = *(uint32_t *) cdata;
+			decimal_from_uint64(res, uval);
+			break;
+		case CTID_UINT64:
+			uval = *(uint64_t *) cdata;
+			decimal_from_uint64(res, uval);
+			break;
+		case CTID_FLOAT:
+			d = *(float *) cdata;
+			if (decimal_from_double(res, d) == NULL)
+				goto err;
+			break;
+		case CTID_DOUBLE:
+			d = *(double *) cdata;
+			if (decimal_from_double(res, d) == NULL)
+				goto err;
+			break;
+		default:
+			lua_pop(L, 1);
+			luaL_error(L, "expected decimal, number or string as "
+				      "%d argument", index);
+		}
+		break;
+	}
+	default:
+		lua_pop(L, 1);
+		luaL_error(L, "expected decimal, number or string as "
+			      "%d argument", index);
+	}
+	lua_replace(L, index);
+	return res;
+err:	/* pop the decimal we prepared on top of the stack. */
+	lua_pop(L, 1);
+	luaL_error(L, "incorrect value to convert to decimal as %d argument",
+		   index);
+	/* luaL_error never returns, this is to silence compiler warning. */
+	return NULL;
+}
+
+LDECIMAL_BINOP(add, add)
+LDECIMAL_BINOP(sub, sub)
+LDECIMAL_BINOP(mul, mul)
+LDECIMAL_BINOP(div, div)
+LDECIMAL_BINOP(pow, pow)
+
+LDECIMAL_FUNC(log10, log10)
+LDECIMAL_FUNC(ln, ln)
+LDECIMAL_FUNC(exp, exp)
+LDECIMAL_FUNC(sqrt, sqrt)
+LDECIMAL_FUNC(abs, abs)
+
+LDECIMAL_CMPOP(eq, ==)
+LDECIMAL_CMPOP(lt, <)
+LDECIMAL_CMPOP(le, <=)
+
+static int
+ldecimal_minus(struct lua_State *L)
+{
+	/*
+	 * Unary operations get a fake second operand. See
+	 * http://lua-users.org/lists/lua-l/2016-10/msg00351.html
+	 */
+	assert(lua_gettop(L) == 2);
+	decimal_t *lhs = lua_todecimal(L, 1);
+	decimal_t *res = lua_pushdecimal(L);
+	/* _minus never fails. */
+	decimal_minus(res, lhs);
+	return 1;
+}
+
+static int
+ldecimal_new(struct lua_State *L)
+{
+	if (lua_gettop(L) < 1)
+		luaL_error(L, "usage: decimal.new(value)");
+	decimal_t *lhs = lua_todecimal(L, 1);
+	decimal_t *res = lua_pushdecimal(L);
+	*res = *lhs;
+	return 1;
+}
+
+static int
+ldecimal_round(struct lua_State *L)
+{
+	if (lua_gettop(L) < 2)
+		return luaL_error(L, "usage: decimal.round(decimal, scale)");
+	decimal_t *lhs = lua_checkdecimal(L, 1);
+	int n = lua_tointeger(L, 2);
+	decimal_t *res = lua_pushdecimal(L);
+	*res = *lhs;
+	decimal_round(res, n);
+	return 1;
+}
+
+static int
+ldecimal_scale(struct lua_State *L)
+{
+	if (lua_gettop(L) < 1)
+		return luaL_error(L, "usage: decimal.scale(decimal)");
+	decimal_t *lhs = lua_checkdecimal(L, 1);
+	int scale = decimal_scale(lhs);
+	lua_pushnumber(L, scale);
+	return 1;
+}
+
+static int
+ldecimal_precision(struct lua_State *L)
+{
+	if (lua_gettop(L) < 1)
+		return luaL_error(L, "usage: decimal.precisiion(decimal)");
+	decimal_t *lhs = lua_checkdecimal(L, 1);
+	int precision = decimal_precision(lhs);
+	lua_pushnumber(L, precision);
+	return 1;
+}
+
+static int
+ldecimal_tostring(struct lua_State *L)
+{
+	if (lua_gettop(L) < 1)
+		return luaL_error(L, "usage: decimal.tostring(decimal)");
+	decimal_t *lhs = lua_checkdecimal(L, 1);
+	lua_pushstring(L, decimal_to_string(lhs));
+	return 1;
+}
+
+static const luaL_Reg ldecimal_mt[] = {
+	{"__unm", ldecimal_minus},
+	{"__add", ldecimal_add},
+	{"__sub", ldecimal_sub},
+	{"__mul", ldecimal_mul},
+	{"__div", ldecimal_div},
+	{"__pow", ldecimal_pow},
+	{"__eq", ldecimal_eq},
+	{"__lt", ldecimal_lt},
+	{"__le", ldecimal_le},
+	{"__tostring", ldecimal_tostring},
+	{NULL, NULL}
+};
+
+static const luaL_Reg ldecimal_lib[] = {
+	{"log10", ldecimal_log10},
+	{"ln", ldecimal_ln},
+	{"exp", ldecimal_exp},
+	{"sqrt", ldecimal_sqrt},
+	{"round", ldecimal_round},
+	{"scale", ldecimal_scale},
+	{"precision", ldecimal_precision},
+	{"abs", ldecimal_abs},
+	{"new", ldecimal_new},
+	{NULL, NULL}
+};
+
+void
+tarantool_lua_decimal_init(struct lua_State *L)
+{
+	int rc = luaL_cdef(L, "typedef struct {"
+				       "int32_t digits;"
+				       "int32_t exponent;"
+				       "uint8_t bits;"
+				       "uint16_t lsu[13];"
+			      "} decimal_t;");
+	assert(rc == 0);
+	(void)rc;
+	luaL_register_module(L, "decimal", ldecimal_lib);
+	lua_pop(L, 1);
+	/*
+	 * luaL_metatype is similar to luaL_ctypeid +
+	 * luaL_register_type.
+	 * The metatable is set automatically to every
+	 * cdata of the new ctypeid ever created via ffi.
+	 */
+	CTID_DECIMAL = luaL_metatype(L, "decimal_t", ldecimal_mt);
+	assert(CTID_DECIMAL != 0);
+}
diff --git a/src/lua/decimal.h b/src/lua/decimal.h
new file mode 100644
index 000000000..0485d11ef
--- /dev/null
+++ b/src/lua/decimal.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010-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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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.
+ */
+#ifndef TARANTOOL_LUA_DECIMAL_H_INCLUDED
+#define TARANTOOL_LUA_DECIMAL_H_INCLUDED
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct lua_State;
+
+void
+tarantool_lua_decimal_init(struct lua_State *L);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* TARANTOOL_LUA_DECIMAL_H_INCLUDED */
diff --git a/src/lua/init.c b/src/lua/init.c
index 9982828d9..fbaedd4cd 100644
--- a/src/lua/init.c
+++ b/src/lua/init.c
@@ -59,6 +59,7 @@
 #include "lua/httpc.h"
 #include "lua/utf8.h"
 #include "lua/swim.h"
+#include "lua/decimal.h"
 #include "digest.h"
 #include <small/ibuf.h>
 
@@ -454,6 +455,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
 	tarantool_lua_pickle_init(L);
 	tarantool_lua_digest_init(L);
 	tarantool_lua_swim_init(L);
+	tarantool_lua_decimal_init(L);
 	luaopen_http_client_driver(L);
 	lua_pop(L, 1);
 	luaopen_msgpack(L);
diff --git a/test/app/decimal.result b/test/app/decimal.result
new file mode 100644
index 000000000..65973ae09
--- /dev/null
+++ b/test/app/decimal.result
@@ -0,0 +1,452 @@
+-- test-run result file version 2
+decimal = require('decimal')
+ | ---
+ | ...
+test_run = require('test_run').new()
+ | ---
+ | ...
+ffi = require('ffi')
+ | ---
+ | ...
+
+-- check various constructors
+decimal.new('1234.5678')
+ | ---
+ | - '1234.5678'
+ | ...
+decimal.new('1e6')
+ | ---
+ | - '1000000'
+ | ...
+decimal.new('-6.234612e2')
+ | ---
+ | - '-623.4612'
+ | ...
+-- check (u)int16/32/64_t
+decimal.new(2ULL ^ 63)
+ | ---
+ | - '9223372036854775808'
+ | ...
+decimal.new(123456789123456789ULL)
+ | ---
+ | - '123456789123456789'
+ | ...
+decimal.new(-123456789123456789LL)
+ | ---
+ | - '-123456789123456789'
+ | ...
+decimal.new(ffi.new('uint8_t', 231))
+ | ---
+ | - '231'
+ | ...
+decimal.new(ffi.new('int8_t', -113))
+ | ---
+ | - '-113'
+ | ...
+decimal.new(ffi.new('uint16_t', 65535))
+ | ---
+ | - '65535'
+ | ...
+decimal.new(ffi.new('int16_t', -31263))
+ | ---
+ | - '-31263'
+ | ...
+decimal.new(ffi.new('uint32_t', 4123123123))
+ | ---
+ | - '4123123123'
+ | ...
+decimal.new(ffi.new('int32_t', -2123123123))
+ | ---
+ | - '-2123123123'
+ | ...
+decimal.new(ffi.new('float', 128.5))
+ | ---
+ | - '128.5'
+ | ...
+decimal.new(ffi.new('double', 128.5))
+ | ---
+ | - '128.5'
+ | ...
+
+decimal.new(1)
+ | ---
+ | - '1'
+ | ...
+decimal.new(-1)
+ | ---
+ | - '-1'
+ | ...
+decimal.new(2^64)
+ | ---
+ | - '18446744073709600000'
+ | ...
+decimal.new(2^(-20))
+ | ---
+ | - '0.00000095367431640625'
+ | ...
+
+-- incorrect constructions
+decimal.new(box.NULL)
+ | ---
+ | - error: expected decimal, number or string as 1 argument
+ | ...
+decimal.new(ffi.new('float', 1 / 0))
+ | ---
+ | - error: incorrect value to convert to decimal as 1 argument
+ | ...
+decimal.new(ffi.new('double', 1 / 0))
+ | ---
+ | - error: incorrect value to convert to decimal as 1 argument
+ | ...
+decimal.new(1 / 0)
+ | ---
+ | - error: incorrect value to convert to decimal as 1 argument
+ | ...
+decimal.new({1, 2, 3})
+ | ---
+ | - error: expected decimal, number or string as 1 argument
+ | ...
+decimal.new()
+ | ---
+ | - error: 'usage: decimal.new(value)'
+ | ...
+decimal.new('inf')
+ | ---
+ | - error: incorrect value to convert to decimal as 1 argument
+ | ...
+decimal.new('NaN')
+ | ---
+ | - error: incorrect value to convert to decimal as 1 argument
+ | ...
+decimal.new('not a valid number')
+ | ---
+ | - error: incorrect value to convert to decimal as 1 argument
+ | ...
+
+a = decimal.new('10')
+ | ---
+ | ...
+a
+ | ---
+ | - '10'
+ | ...
+b = decimal.new('0.1')
+ | ---
+ | ...
+b
+ | ---
+ | - '0.1'
+ | ...
+a + b
+ | ---
+ | - '10.1'
+ | ...
+a - b
+ | ---
+ | - '9.9'
+ | ...
+a * b
+ | ---
+ | - '1.0'
+ | ...
+a / b
+ | ---
+ | - '100'
+ | ...
+a ^ b
+ | ---
+ | - '1.2589254117941672104239541063958006061'
+ | ...
+b ^ a
+ | ---
+ | - '0.0000000001'
+ | ...
+-a + -b == -(a + b)
+ | ---
+ | - true
+ | ...
+a
+ | ---
+ | - '10'
+ | ...
+b
+ | ---
+ | - '0.1'
+ | ...
+
+a < b
+ | ---
+ | - false
+ | ...
+b < a
+ | ---
+ | - true
+ | ...
+a <= b
+ | ---
+ | - false
+ | ...
+b <= a
+ | ---
+ | - true
+ | ...
+a > b
+ | ---
+ | - true
+ | ...
+b > a
+ | ---
+ | - false
+ | ...
+a >= b
+ | ---
+ | - true
+ | ...
+b >= a
+ | ---
+ | - false
+ | ...
+a == b
+ | ---
+ | - false
+ | ...
+a ~= b
+ | ---
+ | - true
+ | ...
+a
+ | ---
+ | - '10'
+ | ...
+b
+ | ---
+ | - '0.1'
+ | ...
+
+decimal.sqrt(a)
+ | ---
+ | - '3.1622776601683793319988935444327185337'
+ | ...
+decimal.ln(a)
+ | ---
+ | - '2.3025850929940456840179914546843642076'
+ | ...
+decimal.log10(a)
+ | ---
+ | - '1'
+ | ...
+decimal.exp(a)
+ | ---
+ | - '22026.465794806716516957900645284244366'
+ | ...
+a == decimal.ln(decimal.exp(a))
+ | ---
+ | - true
+ | ...
+a == decimal.sqrt(a ^ 2)
+ | ---
+ | - true
+ | ...
+a == decimal.log10('10' ^ a)
+ | ---
+ | - true
+ | ...
+a == decimal.abs(-a)
+ | ---
+ | - true
+ | ...
+a + -a == 0
+ | ---
+ | - true
+ | ...
+a
+ | ---
+ | - '10'
+ | ...
+
+a = decimal.new('1.1234567891234567891234567891234567891')
+ | ---
+ | ...
+a
+ | ---
+ | - '1.1234567891234567891234567891234567891'
+ | ...
+decimal.precision(a)
+ | ---
+ | - 38
+ | ...
+decimal.scale(a)
+ | ---
+ | - 37
+ | ...
+decimal.round(a, 37) == a
+ | ---
+ | - true
+ | ...
+a
+ | ---
+ | - '1.1234567891234567891234567891234567891'
+ | ...
+a = decimal.round(a, 36)
+ | ---
+ | ...
+decimal.precision(a)
+ | ---
+ | - 37
+ | ...
+decimal.scale(a)
+ | ---
+ | - 36
+ | ...
+decimal.round(a, 100) == a
+ | ---
+ | - true
+ | ...
+-- noop
+decimal.round(a, -5) == a
+ | ---
+ | - true
+ | ...
+decimal.round(a, 7)
+ | ---
+ | - '1.1234568'
+ | ...
+decimal.round(a, 3)
+ | ---
+ | - '1.123'
+ | ...
+decimal.round(a, 0)
+ | ---
+ | - '1'
+ | ...
+a
+ | ---
+ | - '1.123456789123456789123456789123456789'
+ | ...
+
+decimal.ln(0)
+ | ---
+ | - error: decimal operation failed
+ | ...
+decimal.ln(-1)
+ | ---
+ | - error: decimal operation failed
+ | ...
+decimal.ln(1)
+ | ---
+ | - '0'
+ | ...
+decimal.log10(0)
+ | ---
+ | - error: decimal operation failed
+ | ...
+decimal.log10(-1)
+ | ---
+ | - error: decimal operation failed
+ | ...
+decimal.log10(1)
+ | ---
+ | - '0'
+ | ...
+decimal.exp(88)
+ | ---
+ | - error: decimal operation failed
+ | ...
+decimal.exp(87)
+ | ---
+ | - '60760302250568721495223289381302760753'
+ | ...
+decimal.sqrt(-5)
+ | ---
+ | - error: decimal operation failed
+ | ...
+decimal.sqrt(5)
+ | ---
+ | - '2.2360679774997896964091736687312762354'
+ | ...
+
+-- various incorrect operands
+decimal.round(a)
+ | ---
+ | - error: 'usage: decimal.round(decimal, scale)'
+ | ...
+decimal.round(1, 2)
+ | ---
+ | - error: expected cdata as 1 argument
+ | ...
+decimal.scale(1.234)
+ | ---
+ | - error: expected cdata as 1 argument
+ | ...
+decimal.precision(1.234)
+ | ---
+ | - error: expected cdata as 1 argument
+ | ...
+decimal.abs()
+ | ---
+ | - error: 'usage: decimal.abs(decimal)'
+ | ...
+
+a = decimal.new('1e19')
+ | ---
+ | ...
+a * '1e19'
+ | ---
+ | - error: '[string "return a * ''1e19'' "]:1: decimal operation failed'
+ | ...
+a ^ 2
+ | ---
+ | - error: '[string "return a ^ 2 "]:1: decimal operation failed'
+ | ...
+a ^ 1.9
+ | ---
+ | - '1258925411794167210423954106395800606.1'
+ | ...
+a * '1e18'
+ | ---
+ | - '10000000000000000000000000000000000000'
+ | ...
+a = decimal.new(string.rep('9', 38))
+ | ---
+ | ...
+decimal.precision(a)
+ | ---
+ | - 38
+ | ...
+a + 1
+ | ---
+ | - error: '[string "return a + 1 "]:1: decimal operation failed'
+ | ...
+a + '0.9'
+ | ---
+ | - error: '[string "return a + ''0.9'' "]:1: decimal operation failed'
+ | ...
+a + '0.5'
+ | ---
+ | - error: '[string "return a + ''0.5'' "]:1: decimal operation failed'
+ | ...
+a + '0.4'
+ | ---
+ | - '99999999999999999999999999999999999999'
+ | ...
+a / 0.5
+ | ---
+ | - error: '[string "return a / 0.5 "]:1: decimal operation failed'
+ | ...
+1 / decimal.new('0')
+ | ---
+ | - error: '[string "return 1 / decimal.new(''0'') "]:1: decimal operation failed'
+ | ...
+
+a = decimal.new('-13')
+ | ---
+ | ...
+a ^ 2
+ | ---
+ | - '169'
+ | ...
+-- fractional powers are allowed only for positive numbers
+a ^ 2.5
+ | ---
+ | - error: '[string "return a ^ 2.5 "]:1: decimal operation failed'
+ | ...
diff --git a/test/app/decimal.test.lua b/test/app/decimal.test.lua
new file mode 100644
index 000000000..aaba694aa
--- /dev/null
+++ b/test/app/decimal.test.lua
@@ -0,0 +1,128 @@
+decimal = require('decimal')
+test_run = require('test_run').new()
+ffi = require('ffi')
+
+-- check various constructors
+decimal.new('1234.5678')
+decimal.new('1e6')
+decimal.new('-6.234612e2')
+-- check (u)int16/32/64_t
+decimal.new(2ULL ^ 63)
+decimal.new(123456789123456789ULL)
+decimal.new(-123456789123456789LL)
+decimal.new(ffi.new('uint8_t', 231))
+decimal.new(ffi.new('int8_t', -113))
+decimal.new(ffi.new('uint16_t', 65535))
+decimal.new(ffi.new('int16_t', -31263))
+decimal.new(ffi.new('uint32_t', 4123123123))
+decimal.new(ffi.new('int32_t', -2123123123))
+decimal.new(ffi.new('float', 128.5))
+decimal.new(ffi.new('double', 128.5))
+
+decimal.new(1)
+decimal.new(-1)
+decimal.new(2^64)
+decimal.new(2^(-20))
+
+-- incorrect constructions
+decimal.new(box.NULL)
+decimal.new(ffi.new('float', 1 / 0))
+decimal.new(ffi.new('double', 1 / 0))
+decimal.new(1 / 0)
+decimal.new({1, 2, 3})
+decimal.new()
+decimal.new('inf')
+decimal.new('NaN')
+decimal.new('not a valid number')
+
+a = decimal.new('10')
+a
+b = decimal.new('0.1')
+b
+a + b
+a - b
+a * b
+a / b
+a ^ b
+b ^ a
+-a + -b == -(a + b)
+a
+b
+
+a < b
+b < a
+a <= b
+b <= a
+a > b
+b > a
+a >= b
+b >= a
+a == b
+a ~= b
+a
+b
+
+decimal.sqrt(a)
+decimal.ln(a)
+decimal.log10(a)
+decimal.exp(a)
+a == decimal.ln(decimal.exp(a))
+a == decimal.sqrt(a ^ 2)
+a == decimal.log10('10' ^ a)
+a == decimal.abs(-a)
+a + -a == 0
+a
+
+a = decimal.new('1.1234567891234567891234567891234567891')
+a
+decimal.precision(a)
+decimal.scale(a)
+decimal.round(a, 37) == a
+a
+a = decimal.round(a, 36)
+decimal.precision(a)
+decimal.scale(a)
+decimal.round(a, 100) == a
+-- noop
+decimal.round(a, -5) == a
+decimal.round(a, 7)
+decimal.round(a, 3)
+decimal.round(a, 0)
+a
+
+decimal.ln(0)
+decimal.ln(-1)
+decimal.ln(1)
+decimal.log10(0)
+decimal.log10(-1)
+decimal.log10(1)
+decimal.exp(88)
+decimal.exp(87)
+decimal.sqrt(-5)
+decimal.sqrt(5)
+
+-- various incorrect operands
+decimal.round(a)
+decimal.round(1, 2)
+decimal.scale(1.234)
+decimal.precision(1.234)
+decimal.abs()
+
+a = decimal.new('1e19')
+a * '1e19'
+a ^ 2
+a ^ 1.9
+a * '1e18'
+a = decimal.new(string.rep('9', 38))
+decimal.precision(a)
+a + 1
+a + '0.9'
+a + '0.5'
+a + '0.4'
+a / 0.5
+1 / decimal.new('0')
+
+a = decimal.new('-13')
+a ^ 2
+-- fractional powers are allowed only for positive numbers
+a ^ 2.5
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [tarantool-patches] [PATCH v3 5/5] decimal: expose decimal type to lua.
  2019-07-02 17:27 ` [PATCH v3 5/5] decimal: expose decimal type to lua Serge Petrenko
@ 2019-07-03  6:50   ` Serge Petrenko
  0 siblings, 0 replies; 8+ messages in thread
From: Serge Petrenko @ 2019-07-03  6:50 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

A tiny fix to increase coveralls coverage a little bit more.
The only red lines left are the ones that truly never get executed (with the current implementation)

diff --git a/test/app/decimal.result b/test/app/decimal.result
index 65973ae09..481dbdb8c 100644
--- a/test/app/decimal.result
+++ b/test/app/decimal.result
@@ -382,6 +382,14 @@ decimal.precision(1.234)
  | ---
  | - error: expected cdata as 1 argument
  | ...
+decimal.scale()
+ | ---
+ | - error: 'usage: decimal.scale(decimal)'
+ | ...
+decimal.precision()
+ | ---
+ | - error: 'usage: decimal.precisiion(decimal)'
+ | ...
 decimal.abs()
  | ---
  | - error: 'usage: decimal.abs(decimal)'
diff --git a/test/app/decimal.test.lua b/test/app/decimal.test.lua
index aaba694aa..4aff0f2a4 100644
--- a/test/app/decimal.test.lua
+++ b/test/app/decimal.test.lua
@@ -106,6 +106,8 @@ decimal.round(a)
 decimal.round(1, 2)
 decimal.scale(1.234)
 decimal.precision(1.234)
+decimal.scale()
+decimal.precision()
 decimal.abs()
 
 a = decimal.new('1e19')


--
Serge Petrenko
sergepetrenko@tarantool.org




> 2 июля 2019 г., в 20:27, Serge Petrenko <sergepetrenko@tarantool.org> написал(а):
> 
> Add a decimal library to lua.
> 
> Part of #692
> 
> @TarantoolBot document
> Title: Document decimal module in lua.
> 
> First of all, you have to require the package via
> `decimal = require('decimal')`
> Now you can construct decimals via `new` method.
> Decimals may be constructed from lua numbers, strings, unsigned and
> signed 64 bit integers.
> Decimal is a fixed-point type with maximum 38 digits of precision. All
> the calculations are exact, so, be careful when constructing decimals
> from lua numbers: they may hold only 15 decimal digits of precision.
> You are advised to construct decimals from strings, since strings
> represent decimals exactly, and vice versa.
> 
> ```
> a = decimal.new(123e-7)
> b = decimal.new('123.456')
> c = decimal.new('123.456e2')
> d = decimal.new(123ULL)
> e = decimal.new(2)
> ```
> The allowed operations are addition, subtraction, division,
> multiplication and power. If at least one of the operands is decimal,
> decimal operations are performed. The other operand may be either
> decimal or string, containing a number representation, or a lua number.
> 
> Operations only fail on an overflow, i.e. when result exceeds 10^38 - 1.
> This includes division by zero. In these cases an error `Operation
> failed` is raised.
> Underflow is also possible, when precision needed to store the exact
> result exceeds 38 digits. Underflow is not an error. When an underflow
> happens, the result is rounded to 38 digits of precision.
> 
> ```
> a = decimal.new(123e-7)
> b = decimal.new('123.456')
> c = decimal.new('123.456e2')
> d = decimal.new(123ULL)
> e = decimal.new(2)
> ```
> ```
> tarantool> a + b
> ---
> - '123.456012300000000'
> ...
> 
> tarantool> c - d
> ---
> - '12222.6'
> ...
> 
> tarantool> c / b
> ---
> - '100'
> ...
> 
> tarantool> d * d
> ---
> - '15129'
> ...
> 
> tarantool> d ^ 2
> ---
> - '15129'
> ...
> 
> tarantool> 2 ^ d
> ---
> - '10633823966279326983230456482242756608'...
> 
> tarantool> e ^ d
> ---
> - '10633823966279326983230456482242756608'
> ...
> ```
> The following math functions are also supported:
> log10, ln, exp, sqrt. When specified as
> `decimal.opname()`, operations may be performed on
> strings and lua numbers.
> ```
> f = decimal.new(100)
> 
> tarantool> decimal.log10(f)
> ---
> - '2'
> ...
> 
> tarantool> decimal.sqrt(f)
> ---
> - '10'
> ...
> 
> tarantool> e2 = decimal.exp(2)
> ---
> ...
> 
> tarantool> decimal.ln(e2)
> ---
> - '2.0000000000000000000000000000000000000'
> ...
> 
> There are also `abs` and `tostring` methods, and an unary minus
> operator, which are pretty self-explanatory.
> 
> ```
> tarantool> a = decimal.new('-5')
> ---
> ...
> 
> tarantool> a
> ---
> - '-5'
> ...
> 
> tarantool> decimal.abs(a)
> ---
> - '5'
> ...
> 
> tarantool> -a
> ---
> - '5'
> ...
> 
> tostring(a)
> ---
> - '-5'
> ...
> 
> ```
> 
> `decimal.precision`, `decimal.scale` and `decimal.round` :
> The first two methods return precision, i.e. decimal digits in
> number representation, and scale, i.e. decimal digits after the decimal
> point in the number representation.
> `decimal.round` rounds the number to the given scale.
> ```
> tarantool> a = decimal.new('123.456789')
> ---
> ...
> 
> tarantool> decimal.precision(a)
> ---
> - 9
> ...
> 
> tarantool> decimal.scale(a)
> ---
> - 6
> ...
> 
> tarantool> decimal.round(a, 4)
> ---
> - '123.4568'
> ...
> 
> ```
> 
> Comparsions: `>`, `<`, `>=`, `<=`, `==` are also legal and work as
> expected. You may compare decimals with lua numbers or strings. In that
> case comparsion will happen after the values are converted to decimal
> type.
> ---
> src/CMakeLists.txt        |   1 +
> src/lua/decimal.c         | 351 +++++++++++++++++++++++++++++
> src/lua/decimal.h         |  47 ++++
> src/lua/init.c            |   2 +
> test/app/decimal.result   | 452 ++++++++++++++++++++++++++++++++++++++
> test/app/decimal.test.lua | 128 +++++++++++
> 6 files changed, 981 insertions(+)
> create mode 100644 src/lua/decimal.c
> create mode 100644 src/lua/decimal.h
> create mode 100644 test/app/decimal.result
> create mode 100644 test/app/decimal.test.lua
> 
> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
> index 33b64f6a6..acd719e9b 100644
> --- a/src/CMakeLists.txt
> +++ b/src/CMakeLists.txt
> @@ -120,6 +120,7 @@ set (server_sources
>      lua/string.c
>      lua/buffer.c
>      lua/swim.c
> +     lua/decimal.c
>      ${lua_sources}
>      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc
>      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c
> diff --git a/src/lua/decimal.c b/src/lua/decimal.c
> new file mode 100644
> index 000000000..aecd88175
> --- /dev/null
> +++ b/src/lua/decimal.c
> @@ -0,0 +1,351 @@
> +/*
> + * Copyright 2010-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 <COPYRIGHT HOLDER> ``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
> + * <COPYRIGHT HOLDER> 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 "lua/decimal.h"
> +#include "lib/core/decimal.h"
> +#include "lua/utils.h"
> +
> +#include <lua.h>
> +#include <lauxlib.h>
> +#include <lualib.h>
> +
> +#define LDECIMAL_BINOP(name, opname)						\
> +static int									\
> +ldecimal_##name(struct lua_State *L) {						\
> +	assert(lua_gettop(L) == 2);						\
> +	decimal_t *lhs = lua_todecimal(L, 1);					\
> +	decimal_t *rhs = lua_todecimal(L, 2);					\
> +	decimal_t *res = lua_pushdecimal(L);					\
> +	if (decimal_##opname(res, lhs, rhs) == NULL) {				\
> +		lua_pop(L, 1);							\
> +		luaL_error(L, "decimal operation failed");			\
> +	}									\
> +	return 1;								\
> +}
> +
> +#define LDECIMAL_FUNC(name, opname)						\
> +static int									\
> +ldecimal_##name(struct lua_State *L) {						\
> +	if (lua_gettop(L) < 1)							\
> +		return luaL_error(L, "usage: decimal."#name"(decimal)");	\
> +	decimal_t *lhs = lua_todecimal(L, 1);					\
> +	decimal_t *res = lua_pushdecimal(L);					\
> +	if (decimal_##opname(res, lhs) == NULL) {				\
> +		lua_pop(L, 1);							\
> +		luaL_error(L, "decimal operation failed");			\
> +	}									\
> +	return 1;								\
> +}
> +
> +#define LDECIMAL_CMPOP(name, cmp)						\
> +static int									\
> +ldecimal_##name(struct lua_State *L) {						\
> +	assert(lua_gettop(L) == 2);						\
> +	decimal_t *lhs = lua_todecimal(L, 1);					\
> +	decimal_t *rhs = lua_todecimal(L, 2);					\
> +	lua_pushboolean(L, decimal_compare(lhs, rhs) cmp 0);			\
> +	return 1;								\
> +}
> +
> +static uint32_t CTID_DECIMAL;
> +
> +/** Push a new decimal on the stack and return a pointer to it. */
> +static decimal_t *
> +lua_pushdecimal(struct lua_State *L)
> +{
> +	decimal_t *res = luaL_pushcdata(L, CTID_DECIMAL);
> +	return res;
> +}
> +
> +/** Check whether a value at a given index is a decimal. */
> +static decimal_t *
> +lua_checkdecimal(struct lua_State *L, int index)
> +{
> +	uint32_t ctypeid;
> +	decimal_t *res = luaL_checkcdata(L, index, &ctypeid);
> +	if (ctypeid != CTID_DECIMAL)
> +		luaL_error(L, "expected decimal as %d argument", index);
> +	return res;
> +}
> +
> +/**
> + * Convert the value at the given index to a decimal in place.
> + * The possible conversions are string->decimal and number->decimal.
> + */
> +static decimal_t *
> +lua_todecimal(struct lua_State *L, int index)
> +{
> +	/*
> +	 * Convert the index, if it is given relative to the top.
> +	 * Othervise it will point to a wrong position after
> +	 * pushdecimal().
> +	 */
> +	if (index < 0)
> +		index = lua_gettop(L) + index + 1;
> +	decimal_t *res = lua_pushdecimal(L);
> +	switch(lua_type(L, index))
> +	{
> +	case LUA_TNUMBER:
> +	{
> +		double n = lua_tonumber(L, index);
> +		if (decimal_from_double(res, n) == NULL)
> +			goto err;
> +		break;
> +	}
> +	case LUA_TSTRING:
> +	{
> +		const char *str = lua_tostring(L, index);
> +		if (decimal_from_string(res, str) == NULL)
> +			goto err;
> +		break;
> +	}
> +	case LUA_TCDATA:
> +	{
> +		uint32_t ctypeid;
> +		void *cdata = luaL_checkcdata(L, index, &ctypeid);
> +		int64_t ival;
> +		uint64_t uval;
> +		double d;
> +		if (ctypeid == CTID_DECIMAL) {
> +			/*
> +			 * We already have a decimal at the
> +			 * desired position.
> +			 */
> +			lua_pop(L, 1);
> +			return (decimal_t *) cdata;
> +		}
> +		switch (ctypeid)
> +		{
> +		case CTID_CCHAR:
> +		case CTID_INT8:
> +			ival = *(int8_t *) cdata;
> +			/*
> +			 * no errors are possible in decimal from
> +			 * (u)int construction.
> +			 */
> +			decimal_from_int64(res, ival);
> +			break;
> +		case CTID_INT16:
> +			ival = *(int16_t *) cdata;
> +			decimal_from_int64(res, ival);
> +			break;
> +		case CTID_INT32:
> +			ival = *(int32_t *) cdata;
> +			decimal_from_int64(res, ival);
> +			break;
> +		case CTID_INT64:
> +			ival = *(int64_t *) cdata;
> +			decimal_from_int64(res, ival);
> +			break;
> +		case CTID_UINT8:
> +			uval = *(uint8_t *) cdata;
> +			decimal_from_uint64(res, uval);
> +			break;
> +		case CTID_UINT16:
> +			uval = *(uint16_t *) cdata;
> +			decimal_from_uint64(res, uval);
> +			break;
> +		case CTID_UINT32:
> +			uval = *(uint32_t *) cdata;
> +			decimal_from_uint64(res, uval);
> +			break;
> +		case CTID_UINT64:
> +			uval = *(uint64_t *) cdata;
> +			decimal_from_uint64(res, uval);
> +			break;
> +		case CTID_FLOAT:
> +			d = *(float *) cdata;
> +			if (decimal_from_double(res, d) == NULL)
> +				goto err;
> +			break;
> +		case CTID_DOUBLE:
> +			d = *(double *) cdata;
> +			if (decimal_from_double(res, d) == NULL)
> +				goto err;
> +			break;
> +		default:
> +			lua_pop(L, 1);
> +			luaL_error(L, "expected decimal, number or string as "
> +				      "%d argument", index);
> +		}
> +		break;
> +	}
> +	default:
> +		lua_pop(L, 1);
> +		luaL_error(L, "expected decimal, number or string as "
> +			      "%d argument", index);
> +	}
> +	lua_replace(L, index);
> +	return res;
> +err:	/* pop the decimal we prepared on top of the stack. */
> +	lua_pop(L, 1);
> +	luaL_error(L, "incorrect value to convert to decimal as %d argument",
> +		   index);
> +	/* luaL_error never returns, this is to silence compiler warning. */
> +	return NULL;
> +}
> +
> +LDECIMAL_BINOP(add, add)
> +LDECIMAL_BINOP(sub, sub)
> +LDECIMAL_BINOP(mul, mul)
> +LDECIMAL_BINOP(div, div)
> +LDECIMAL_BINOP(pow, pow)
> +
> +LDECIMAL_FUNC(log10, log10)
> +LDECIMAL_FUNC(ln, ln)
> +LDECIMAL_FUNC(exp, exp)
> +LDECIMAL_FUNC(sqrt, sqrt)
> +LDECIMAL_FUNC(abs, abs)
> +
> +LDECIMAL_CMPOP(eq, ==)
> +LDECIMAL_CMPOP(lt, <)
> +LDECIMAL_CMPOP(le, <=)
> +
> +static int
> +ldecimal_minus(struct lua_State *L)
> +{
> +	/*
> +	 * Unary operations get a fake second operand. See
> +	 * http://lua-users.org/lists/lua-l/2016-10/msg00351.html
> +	 */
> +	assert(lua_gettop(L) == 2);
> +	decimal_t *lhs = lua_todecimal(L, 1);
> +	decimal_t *res = lua_pushdecimal(L);
> +	/* _minus never fails. */
> +	decimal_minus(res, lhs);
> +	return 1;
> +}
> +
> +static int
> +ldecimal_new(struct lua_State *L)
> +{
> +	if (lua_gettop(L) < 1)
> +		luaL_error(L, "usage: decimal.new(value)");
> +	decimal_t *lhs = lua_todecimal(L, 1);
> +	decimal_t *res = lua_pushdecimal(L);
> +	*res = *lhs;
> +	return 1;
> +}
> +
> +static int
> +ldecimal_round(struct lua_State *L)
> +{
> +	if (lua_gettop(L) < 2)
> +		return luaL_error(L, "usage: decimal.round(decimal, scale)");
> +	decimal_t *lhs = lua_checkdecimal(L, 1);
> +	int n = lua_tointeger(L, 2);
> +	decimal_t *res = lua_pushdecimal(L);
> +	*res = *lhs;
> +	decimal_round(res, n);
> +	return 1;
> +}
> +
> +static int
> +ldecimal_scale(struct lua_State *L)
> +{
> +	if (lua_gettop(L) < 1)
> +		return luaL_error(L, "usage: decimal.scale(decimal)");
> +	decimal_t *lhs = lua_checkdecimal(L, 1);
> +	int scale = decimal_scale(lhs);
> +	lua_pushnumber(L, scale);
> +	return 1;
> +}
> +
> +static int
> +ldecimal_precision(struct lua_State *L)
> +{
> +	if (lua_gettop(L) < 1)
> +		return luaL_error(L, "usage: decimal.precisiion(decimal)");
> +	decimal_t *lhs = lua_checkdecimal(L, 1);
> +	int precision = decimal_precision(lhs);
> +	lua_pushnumber(L, precision);
> +	return 1;
> +}
> +
> +static int
> +ldecimal_tostring(struct lua_State *L)
> +{
> +	if (lua_gettop(L) < 1)
> +		return luaL_error(L, "usage: decimal.tostring(decimal)");
> +	decimal_t *lhs = lua_checkdecimal(L, 1);
> +	lua_pushstring(L, decimal_to_string(lhs));
> +	return 1;
> +}
> +
> +static const luaL_Reg ldecimal_mt[] = {
> +	{"__unm", ldecimal_minus},
> +	{"__add", ldecimal_add},
> +	{"__sub", ldecimal_sub},
> +	{"__mul", ldecimal_mul},
> +	{"__div", ldecimal_div},
> +	{"__pow", ldecimal_pow},
> +	{"__eq", ldecimal_eq},
> +	{"__lt", ldecimal_lt},
> +	{"__le", ldecimal_le},
> +	{"__tostring", ldecimal_tostring},
> +	{NULL, NULL}
> +};
> +
> +static const luaL_Reg ldecimal_lib[] = {
> +	{"log10", ldecimal_log10},
> +	{"ln", ldecimal_ln},
> +	{"exp", ldecimal_exp},
> +	{"sqrt", ldecimal_sqrt},
> +	{"round", ldecimal_round},
> +	{"scale", ldecimal_scale},
> +	{"precision", ldecimal_precision},
> +	{"abs", ldecimal_abs},
> +	{"new", ldecimal_new},
> +	{NULL, NULL}
> +};
> +
> +void
> +tarantool_lua_decimal_init(struct lua_State *L)
> +{
> +	int rc = luaL_cdef(L, "typedef struct {"
> +				       "int32_t digits;"
> +				       "int32_t exponent;"
> +				       "uint8_t bits;"
> +				       "uint16_t lsu[13];"
> +			      "} decimal_t;");
> +	assert(rc == 0);
> +	(void)rc;
> +	luaL_register_module(L, "decimal", ldecimal_lib);
> +	lua_pop(L, 1);
> +	/*
> +	 * luaL_metatype is similar to luaL_ctypeid +
> +	 * luaL_register_type.
> +	 * The metatable is set automatically to every
> +	 * cdata of the new ctypeid ever created via ffi.
> +	 */
> +	CTID_DECIMAL = luaL_metatype(L, "decimal_t", ldecimal_mt);
> +	assert(CTID_DECIMAL != 0);
> +}
> diff --git a/src/lua/decimal.h b/src/lua/decimal.h
> new file mode 100644
> index 000000000..0485d11ef
> --- /dev/null
> +++ b/src/lua/decimal.h
> @@ -0,0 +1,47 @@
> +/*
> + * Copyright 2010-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 <COPYRIGHT HOLDER> ``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
> + * <COPYRIGHT HOLDER> 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.
> + */
> +#ifndef TARANTOOL_LUA_DECIMAL_H_INCLUDED
> +#define TARANTOOL_LUA_DECIMAL_H_INCLUDED
> +
> +#if defined(__cplusplus)
> +extern "C" {
> +#endif /* defined(__cplusplus) */
> +
> +struct lua_State;
> +
> +void
> +tarantool_lua_decimal_init(struct lua_State *L);
> +
> +#if defined(__cplusplus)
> +} /* extern "C" */
> +#endif /* defined(__cplusplus) */
> +
> +#endif /* TARANTOOL_LUA_DECIMAL_H_INCLUDED */
> diff --git a/src/lua/init.c b/src/lua/init.c
> index 9982828d9..fbaedd4cd 100644
> --- a/src/lua/init.c
> +++ b/src/lua/init.c
> @@ -59,6 +59,7 @@
> #include "lua/httpc.h"
> #include "lua/utf8.h"
> #include "lua/swim.h"
> +#include "lua/decimal.h"
> #include "digest.h"
> #include <small/ibuf.h>
> 
> @@ -454,6 +455,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
> 	tarantool_lua_pickle_init(L);
> 	tarantool_lua_digest_init(L);
> 	tarantool_lua_swim_init(L);
> +	tarantool_lua_decimal_init(L);
> 	luaopen_http_client_driver(L);
> 	lua_pop(L, 1);
> 	luaopen_msgpack(L);
> diff --git a/test/app/decimal.result b/test/app/decimal.result
> new file mode 100644
> index 000000000..65973ae09
> --- /dev/null
> +++ b/test/app/decimal.result
> @@ -0,0 +1,452 @@
> +-- test-run result file version 2
> +decimal = require('decimal')
> + | ---
> + | ...
> +test_run = require('test_run').new()
> + | ---
> + | ...
> +ffi = require('ffi')
> + | ---
> + | ...
> +
> +-- check various constructors
> +decimal.new('1234.5678')
> + | ---
> + | - '1234.5678'
> + | ...
> +decimal.new('1e6')
> + | ---
> + | - '1000000'
> + | ...
> +decimal.new('-6.234612e2')
> + | ---
> + | - '-623.4612'
> + | ...
> +-- check (u)int16/32/64_t
> +decimal.new(2ULL ^ 63)
> + | ---
> + | - '9223372036854775808'
> + | ...
> +decimal.new(123456789123456789ULL)
> + | ---
> + | - '123456789123456789'
> + | ...
> +decimal.new(-123456789123456789LL)
> + | ---
> + | - '-123456789123456789'
> + | ...
> +decimal.new(ffi.new('uint8_t', 231))
> + | ---
> + | - '231'
> + | ...
> +decimal.new(ffi.new('int8_t', -113))
> + | ---
> + | - '-113'
> + | ...
> +decimal.new(ffi.new('uint16_t', 65535))
> + | ---
> + | - '65535'
> + | ...
> +decimal.new(ffi.new('int16_t', -31263))
> + | ---
> + | - '-31263'
> + | ...
> +decimal.new(ffi.new('uint32_t', 4123123123))
> + | ---
> + | - '4123123123'
> + | ...
> +decimal.new(ffi.new('int32_t', -2123123123))
> + | ---
> + | - '-2123123123'
> + | ...
> +decimal.new(ffi.new('float', 128.5))
> + | ---
> + | - '128.5'
> + | ...
> +decimal.new(ffi.new('double', 128.5))
> + | ---
> + | - '128.5'
> + | ...
> +
> +decimal.new(1)
> + | ---
> + | - '1'
> + | ...
> +decimal.new(-1)
> + | ---
> + | - '-1'
> + | ...
> +decimal.new(2^64)
> + | ---
> + | - '18446744073709600000'
> + | ...
> +decimal.new(2^(-20))
> + | ---
> + | - '0.00000095367431640625'
> + | ...
> +
> +-- incorrect constructions
> +decimal.new(box.NULL)
> + | ---
> + | - error: expected decimal, number or string as 1 argument
> + | ...
> +decimal.new(ffi.new('float', 1 / 0))
> + | ---
> + | - error: incorrect value to convert to decimal as 1 argument
> + | ...
> +decimal.new(ffi.new('double', 1 / 0))
> + | ---
> + | - error: incorrect value to convert to decimal as 1 argument
> + | ...
> +decimal.new(1 / 0)
> + | ---
> + | - error: incorrect value to convert to decimal as 1 argument
> + | ...
> +decimal.new({1, 2, 3})
> + | ---
> + | - error: expected decimal, number or string as 1 argument
> + | ...
> +decimal.new()
> + | ---
> + | - error: 'usage: decimal.new(value)'
> + | ...
> +decimal.new('inf')
> + | ---
> + | - error: incorrect value to convert to decimal as 1 argument
> + | ...
> +decimal.new('NaN')
> + | ---
> + | - error: incorrect value to convert to decimal as 1 argument
> + | ...
> +decimal.new('not a valid number')
> + | ---
> + | - error: incorrect value to convert to decimal as 1 argument
> + | ...
> +
> +a = decimal.new('10')
> + | ---
> + | ...
> +a
> + | ---
> + | - '10'
> + | ...
> +b = decimal.new('0.1')
> + | ---
> + | ...
> +b
> + | ---
> + | - '0.1'
> + | ...
> +a + b
> + | ---
> + | - '10.1'
> + | ...
> +a - b
> + | ---
> + | - '9.9'
> + | ...
> +a * b
> + | ---
> + | - '1.0'
> + | ...
> +a / b
> + | ---
> + | - '100'
> + | ...
> +a ^ b
> + | ---
> + | - '1.2589254117941672104239541063958006061'
> + | ...
> +b ^ a
> + | ---
> + | - '0.0000000001'
> + | ...
> +-a + -b == -(a + b)
> + | ---
> + | - true
> + | ...
> +a
> + | ---
> + | - '10'
> + | ...
> +b
> + | ---
> + | - '0.1'
> + | ...
> +
> +a < b
> + | ---
> + | - false
> + | ...
> +b < a
> + | ---
> + | - true
> + | ...
> +a <= b
> + | ---
> + | - false
> + | ...
> +b <= a
> + | ---
> + | - true
> + | ...
> +a > b
> + | ---
> + | - true
> + | ...
> +b > a
> + | ---
> + | - false
> + | ...
> +a >= b
> + | ---
> + | - true
> + | ...
> +b >= a
> + | ---
> + | - false
> + | ...
> +a == b
> + | ---
> + | - false
> + | ...
> +a ~= b
> + | ---
> + | - true
> + | ...
> +a
> + | ---
> + | - '10'
> + | ...
> +b
> + | ---
> + | - '0.1'
> + | ...
> +
> +decimal.sqrt(a)
> + | ---
> + | - '3.1622776601683793319988935444327185337'
> + | ...
> +decimal.ln(a)
> + | ---
> + | - '2.3025850929940456840179914546843642076'
> + | ...
> +decimal.log10(a)
> + | ---
> + | - '1'
> + | ...
> +decimal.exp(a)
> + | ---
> + | - '22026.465794806716516957900645284244366'
> + | ...
> +a == decimal.ln(decimal.exp(a))
> + | ---
> + | - true
> + | ...
> +a == decimal.sqrt(a ^ 2)
> + | ---
> + | - true
> + | ...
> +a == decimal.log10('10' ^ a)
> + | ---
> + | - true
> + | ...
> +a == decimal.abs(-a)
> + | ---
> + | - true
> + | ...
> +a + -a == 0
> + | ---
> + | - true
> + | ...
> +a
> + | ---
> + | - '10'
> + | ...
> +
> +a = decimal.new('1.1234567891234567891234567891234567891')
> + | ---
> + | ...
> +a
> + | ---
> + | - '1.1234567891234567891234567891234567891'
> + | ...
> +decimal.precision(a)
> + | ---
> + | - 38
> + | ...
> +decimal.scale(a)
> + | ---
> + | - 37
> + | ...
> +decimal.round(a, 37) == a
> + | ---
> + | - true
> + | ...
> +a
> + | ---
> + | - '1.1234567891234567891234567891234567891'
> + | ...
> +a = decimal.round(a, 36)
> + | ---
> + | ...
> +decimal.precision(a)
> + | ---
> + | - 37
> + | ...
> +decimal.scale(a)
> + | ---
> + | - 36
> + | ...
> +decimal.round(a, 100) == a
> + | ---
> + | - true
> + | ...
> +-- noop
> +decimal.round(a, -5) == a
> + | ---
> + | - true
> + | ...
> +decimal.round(a, 7)
> + | ---
> + | - '1.1234568'
> + | ...
> +decimal.round(a, 3)
> + | ---
> + | - '1.123'
> + | ...
> +decimal.round(a, 0)
> + | ---
> + | - '1'
> + | ...
> +a
> + | ---
> + | - '1.123456789123456789123456789123456789'
> + | ...
> +
> +decimal.ln(0)
> + | ---
> + | - error: decimal operation failed
> + | ...
> +decimal.ln(-1)
> + | ---
> + | - error: decimal operation failed
> + | ...
> +decimal.ln(1)
> + | ---
> + | - '0'
> + | ...
> +decimal.log10(0)
> + | ---
> + | - error: decimal operation failed
> + | ...
> +decimal.log10(-1)
> + | ---
> + | - error: decimal operation failed
> + | ...
> +decimal.log10(1)
> + | ---
> + | - '0'
> + | ...
> +decimal.exp(88)
> + | ---
> + | - error: decimal operation failed
> + | ...
> +decimal.exp(87)
> + | ---
> + | - '60760302250568721495223289381302760753'
> + | ...
> +decimal.sqrt(-5)
> + | ---
> + | - error: decimal operation failed
> + | ...
> +decimal.sqrt(5)
> + | ---
> + | - '2.2360679774997896964091736687312762354'
> + | ...
> +
> +-- various incorrect operands
> +decimal.round(a)
> + | ---
> + | - error: 'usage: decimal.round(decimal, scale)'
> + | ...
> +decimal.round(1, 2)
> + | ---
> + | - error: expected cdata as 1 argument
> + | ...
> +decimal.scale(1.234)
> + | ---
> + | - error: expected cdata as 1 argument
> + | ...
> +decimal.precision(1.234)
> + | ---
> + | - error: expected cdata as 1 argument
> + | ...
> +decimal.abs()
> + | ---
> + | - error: 'usage: decimal.abs(decimal)'
> + | ...
> +
> +a = decimal.new('1e19')
> + | ---
> + | ...
> +a * '1e19'
> + | ---
> + | - error: '[string "return a * ''1e19'' "]:1: decimal operation failed'
> + | ...
> +a ^ 2
> + | ---
> + | - error: '[string "return a ^ 2 "]:1: decimal operation failed'
> + | ...
> +a ^ 1.9
> + | ---
> + | - '1258925411794167210423954106395800606.1'
> + | ...
> +a * '1e18'
> + | ---
> + | - '10000000000000000000000000000000000000'
> + | ...
> +a = decimal.new(string.rep('9', 38))
> + | ---
> + | ...
> +decimal.precision(a)
> + | ---
> + | - 38
> + | ...
> +a + 1
> + | ---
> + | - error: '[string "return a + 1 "]:1: decimal operation failed'
> + | ...
> +a + '0.9'
> + | ---
> + | - error: '[string "return a + ''0.9'' "]:1: decimal operation failed'
> + | ...
> +a + '0.5'
> + | ---
> + | - error: '[string "return a + ''0.5'' "]:1: decimal operation failed'
> + | ...
> +a + '0.4'
> + | ---
> + | - '99999999999999999999999999999999999999'
> + | ...
> +a / 0.5
> + | ---
> + | - error: '[string "return a / 0.5 "]:1: decimal operation failed'
> + | ...
> +1 / decimal.new('0')
> + | ---
> + | - error: '[string "return 1 / decimal.new(''0'') "]:1: decimal operation failed'
> + | ...
> +
> +a = decimal.new('-13')
> + | ---
> + | ...
> +a ^ 2
> + | ---
> + | - '169'
> + | ...
> +-- fractional powers are allowed only for positive numbers
> +a ^ 2.5
> + | ---
> + | - error: '[string "return a ^ 2.5 "]:1: decimal operation failed'
> + | ...
> diff --git a/test/app/decimal.test.lua b/test/app/decimal.test.lua
> new file mode 100644
> index 000000000..aaba694aa
> --- /dev/null
> +++ b/test/app/decimal.test.lua
> @@ -0,0 +1,128 @@
> +decimal = require('decimal')
> +test_run = require('test_run').new()
> +ffi = require('ffi')
> +
> +-- check various constructors
> +decimal.new('1234.5678')
> +decimal.new('1e6')
> +decimal.new('-6.234612e2')
> +-- check (u)int16/32/64_t
> +decimal.new(2ULL ^ 63)
> +decimal.new(123456789123456789ULL)
> +decimal.new(-123456789123456789LL)
> +decimal.new(ffi.new('uint8_t', 231))
> +decimal.new(ffi.new('int8_t', -113))
> +decimal.new(ffi.new('uint16_t', 65535))
> +decimal.new(ffi.new('int16_t', -31263))
> +decimal.new(ffi.new('uint32_t', 4123123123))
> +decimal.new(ffi.new('int32_t', -2123123123))
> +decimal.new(ffi.new('float', 128.5))
> +decimal.new(ffi.new('double', 128.5))
> +
> +decimal.new(1)
> +decimal.new(-1)
> +decimal.new(2^64)
> +decimal.new(2^(-20))
> +
> +-- incorrect constructions
> +decimal.new(box.NULL)
> +decimal.new(ffi.new('float', 1 / 0))
> +decimal.new(ffi.new('double', 1 / 0))
> +decimal.new(1 / 0)
> +decimal.new({1, 2, 3})
> +decimal.new()
> +decimal.new('inf')
> +decimal.new('NaN')
> +decimal.new('not a valid number')
> +
> +a = decimal.new('10')
> +a
> +b = decimal.new('0.1')
> +b
> +a + b
> +a - b
> +a * b
> +a / b
> +a ^ b
> +b ^ a
> +-a + -b == -(a + b)
> +a
> +b
> +
> +a < b
> +b < a
> +a <= b
> +b <= a
> +a > b
> +b > a
> +a >= b
> +b >= a
> +a == b
> +a ~= b
> +a
> +b
> +
> +decimal.sqrt(a)
> +decimal.ln(a)
> +decimal.log10(a)
> +decimal.exp(a)
> +a == decimal.ln(decimal.exp(a))
> +a == decimal.sqrt(a ^ 2)
> +a == decimal.log10('10' ^ a)
> +a == decimal.abs(-a)
> +a + -a == 0
> +a
> +
> +a = decimal.new('1.1234567891234567891234567891234567891')
> +a
> +decimal.precision(a)
> +decimal.scale(a)
> +decimal.round(a, 37) == a
> +a
> +a = decimal.round(a, 36)
> +decimal.precision(a)
> +decimal.scale(a)
> +decimal.round(a, 100) == a
> +-- noop
> +decimal.round(a, -5) == a
> +decimal.round(a, 7)
> +decimal.round(a, 3)
> +decimal.round(a, 0)
> +a
> +
> +decimal.ln(0)
> +decimal.ln(-1)
> +decimal.ln(1)
> +decimal.log10(0)
> +decimal.log10(-1)
> +decimal.log10(1)
> +decimal.exp(88)
> +decimal.exp(87)
> +decimal.sqrt(-5)
> +decimal.sqrt(5)
> +
> +-- various incorrect operands
> +decimal.round(a)
> +decimal.round(1, 2)
> +decimal.scale(1.234)
> +decimal.precision(1.234)
> +decimal.abs()
> +
> +a = decimal.new('1e19')
> +a * '1e19'
> +a ^ 2
> +a ^ 1.9
> +a * '1e18'
> +a = decimal.new(string.rep('9', 38))
> +decimal.precision(a)
> +a + 1
> +a + '0.9'
> +a + '0.5'
> +a + '0.4'
> +a / 0.5
> +1 / decimal.new('0')
> +
> +a = decimal.new('-13')
> +a ^ 2
> +-- fractional powers are allowed only for positive numbers
> +a ^ 2.5
> -- 
> 2.20.1 (Apple Git-117)
> 
> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3 0/5] decimal: expose decimal module to Lua.
  2019-07-02 17:27 [PATCH v3 0/5] decimal: expose decimal module to Lua Serge Petrenko
                   ` (4 preceding siblings ...)
  2019-07-02 17:27 ` [PATCH v3 5/5] decimal: expose decimal type to lua Serge Petrenko
@ 2019-07-08 14:25 ` Vladimir Davydov
  5 siblings, 0 replies; 8+ messages in thread
From: Vladimir Davydov @ 2019-07-08 14:25 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: tarantool-patches

On Tue, Jul 02, 2019 at 08:27:47PM +0300, Serge Petrenko wrote:
> Serge Petrenko (5):
>   decimal: fix ln hang on values between ~ 0.9 and 1.1
>   decimal: diallow infinity and NaN entirely.
>   decimal: fix string formatting on construction from double
>   lua/utils: add a function to register FFI metatypes.
>   decimal: expose decimal type to lua.

Pushed to master, thanks!

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2019-07-08 14:25 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-07-02 17:27 [PATCH v3 0/5] decimal: expose decimal module to Lua Serge Petrenko
2019-07-02 17:27 ` [PATCH v3 1/5] decimal: fix ln hang on values between ~ 0.9 and 1.1 Serge Petrenko
2019-07-02 17:27 ` [PATCH v3 2/5] decimal: diallow infinity and NaN entirely Serge Petrenko
2019-07-02 17:27 ` [PATCH v3 3/5] decimal: fix string formatting on construction from double Serge Petrenko
2019-07-02 17:27 ` [PATCH v3 4/5] lua/utils: add a function to register FFI metatypes Serge Petrenko
2019-07-02 17:27 ` [PATCH v3 5/5] decimal: expose decimal type to lua Serge Petrenko
2019-07-03  6:50   ` [tarantool-patches] " Serge Petrenko
2019-07-08 14:25 ` [PATCH v3 0/5] decimal: expose decimal module to Lua Vladimir Davydov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox