diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 33b64f6a680e5a518dbc584fbbcee27ee69f4918..acd719e9b2edfa2be4a0f55cebc7ccbb42108c3b 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 0000000000000000000000000000000000000000..e548cdb9d4fc7931df517a7427444a32966344a9
--- /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.precision(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 0000000000000000000000000000000000000000..0485d11ef1eece915df78f472eb1f9231927fda0
--- /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 9982828d93f8222d5eb936f7bdfdc45f9eb9c2e5..fbaedd4cd4b5f6d08b68e4ad2efa577d52f27f67 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 0000000000000000000000000000000000000000..ecb18927736fac9220eb88daf2b938e43d3a15f9
--- /dev/null
+++ b/test/app/decimal.result
@@ -0,0 +1,460 @@
+-- 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.scale()
+ | ---
+ | - error: 'usage: decimal.scale(decimal)'
+ | ...
+decimal.precision()
+ | ---
+ | - error: 'usage: decimal.precision(decimal)'
+ | ...
+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 0000000000000000000000000000000000000000..4aff0f2a4b86c50d584660e8ea773d3565da913f
--- /dev/null
+++ b/test/app/decimal.test.lua
@@ -0,0 +1,130 @@
+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.scale()
+decimal.precision()
+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