From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Serge Petrenko Message-Id: <1DC2A934-16CF-469A-8302-196187817B1C@tarantool.org> Content-Type: multipart/alternative; boundary="Apple-Mail=_BEC54B79-D16B-4629-BC23-D29405219B5E" Mime-Version: 1.0 (Mac OS X Mail 12.4 \(3445.104.11\)) Subject: Re: [tarantool-patches] [PATCH 2/2] decimal: expose decimal type to lua. Date: Fri, 28 Jun 2019 17:39:39 +0300 In-Reply-To: <20190626140253.qzeog6z3rlopm7m4@esperanza> References: <89c41adbdf7452c1ef4a8f478363fe2ccb95a848.1560958964.git.sergepetrenko@tarantool.org> <20190621155307.z6pnpfbgqgni6zud@esperanza> <977F1A26-1374-4D64-8717-AB67F41E298F@tarantool.org> <20190626140253.qzeog6z3rlopm7m4@esperanza> To: Vladimir Davydov Cc: tarantool-patches@freelists.org List-ID: --Apple-Mail=_BEC54B79-D16B-4629-BC23-D29405219B5E Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Hi! Thank you for review! I addressed your comments and sent v2. The only inconsistency is with macro names: I feel like LDECIMAL_BINOP = and LDECIMAL_FUNC are better, since the latter one is only used for functions now (ln, = log10, sqrt, abs). -- Serge Petrenko sergepetrenko@tarantool.org > 26 =D0=B8=D1=8E=D0=BD=D1=8F 2019 =D0=B3., =D0=B2 17:02, Vladimir = Davydov =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BB= (=D0=B0): >=20 > Pasting the whole patch here for review: >=20 >> =46rom 565aa457dfb40302797cdd1d9863559fe2c52468 Mon Sep 17 00:00:00 = 2001 >> From: Serge Petrenko >> Date: Wed, 5 Jun 2019 15:58:49 +0300 >> Subject: [PATCH] decimal: expose decimal type to lua. >>=20 >> Add a decimal library to lua. >>=20 >> Part of #692 >>=20 >> @TarantoolBot document >> Title: Document decimal module in lua. >>=20 >> First of all, you have to require the package via >> `decimal =3D 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. >>=20 >> ``` >> a =3D decimal.new(123e-7) >> b =3D decimal.new('123.456') >> c =3D decimal.new('123.456e2') >> d =3D decimal.new(123ULL) >> e =3D 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. >> When the operation is called as `decimal.opname`, both operands may = be >> strings or lua numbers, e.g. `decimal.add(23, '123.456') =3D=3D = 146.456`: >=20 > There's no decimal.add anymore. >=20 >>=20 >> 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. >>=20 >> ``` >> tarantool> a + b >> --- >> - '123.456012300000000' >> ... >>=20 >> tarantool> c - d >> --- >> - '12222.6' >> ... >>=20 >> tarantool> c / b >> --- >> - '100' >> ... >>=20 >> tarantool> d * d >> --- >> - '15129' >> ... >>=20 >> tarantool> d ^ 2 >> --- >> - '15129' >> ... >>=20 >> tarantool> 2 ^ d >> --- >> - '10633823966279326983230456482242756608' >> ... >>=20 >> tarantool> e ^ d >> --- >> - '10633823966279326983230456482242756608' >> ... >> ``` >=20 > It's unclear what a, b, c, d, e are in this example. >=20 >> The following math functions are also supported: >> log10, ln, exp, sqrt, pow. When specified as >> `decimal.opname()`, operations may be performed on >> strings and lua numbers. >> ``` >> f =3D decimal.new(100) >>=20 >> tarantool> decimal.log10(f) >> --- >> - '2' >> ... >>=20 >> tarantool> decimal.sqrt(f) >> --- >> - '10' >> ... >>=20 >> tarantool> e2 =3D decimal.exp(2) >> --- >> ... >>=20 >> tarantool> decimal.ln(e2) >> --- >> - '2.0000000000000000000000000000000000000' >> ... >>=20 >> There are also `abs` and `tostring` methods, and an unary minus >> operator, which are pretty self-explanatory. >>=20 >> ``` >> tarantool> a =3D decimal.new(-5) >> --- >> ... >>=20 >> tarantool> a >> --- >> - '-5.000000000000000' >> ... >=20 > This looks weird. Can't we trim trailing zeros somehow? >=20 >>=20 >> tarantool> decimal.abs(a) >> --- >> - '5.000000000000000' >> ... >>=20 >> tarantool> -a >> --- >> - '5.000000000000000' >> ... >>=20 >> tostring(a) >> --- >> - '-5.000000000000000' >> ... >>=20 >> ``` >>=20 >> `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 =3D decimal.new('123.456789') >> --- >> ... >>=20 >> tarantool> decimal.precision(a) >> --- >> - 9 >> ... >>=20 >> tarantool> decimal.scale(a) >> --- >> - 6 >> ... >>=20 >> tarantool> decimal.round(a, 4) >> --- >> - '123.4568' >> ... >>=20 >> ``` >>=20 >> Comparsions: `>`, `<`, `>=3D`, `<=3D`, `=3D=3D` 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. >>=20 >> diff --git a/src/lua/decimal.c b/src/lua/decimal.c >> new file mode 100644 >> index 00000000..4dc0dff7 >> --- /dev/null >> +++ b/src/lua/decimal.c >> @@ -0,0 +1,335 @@ >=20 >> +#define LDECIMAL_OP2(name, opname)\ >> +static int \ >> +ldecimal_##name(struct lua_State *L) {\ >> + if (lua_gettop(L) < 2)\ >> + return luaL_error(L, "Usage: decimal."#name"(decimal, = decimal)");\ >=20 > There's no decimal_mul or decimal_add anymore... >=20 >> + decimal_t *lhs =3D lua_todecimal(L, 1);\ >> + decimal_t *rhs =3D lua_todecimal(L, 2);\ >> + decimal_t *res =3D lua_pushdecimal(L);\ >> + if (decimal_##opname(res, lhs, rhs) =3D=3D NULL) {\ >> + lua_pop(L, 1);\ >> + luaL_error(L, "Operation failed.");\ >=20 > The error message isn't informative. Should be "Decimal overflow" > I think. >=20 >> + }\ >> + return 1;\ >> +} >=20 > Please align backslash (\): >=20 > #define LDECIMAL_OP2(name, opname) \ > static int \ > ldecimal_##name(struct luaState *L) \ >=20 > and so on. >=20 > Also, please add brief comments to all helper functions. >=20 >> +#define LDECIMAL_UNOP(name, opname)\ >=20 > I'd call them LDECIMAL_OP1 and LDECIMAL_OP2 or LDECIMAL_UNOP and > LDECIMAL_BINOP, for consistency. >=20 >> +static int \ >> +ldecimal_##name(struct lua_State *L) {\ >> + if (lua_gettop(L) < 1)\ >> + return luaL_error(L, "Usage: = decimal."#name"(decimal)");\ >=20 > There's no decimal.minus. >=20 >> + decimal_t *lhs =3D lua_todecimal(L, 1);\ >> + decimal_t *res =3D lua_pushdecimal(L);\ >> + if (decimal_##opname(res, lhs) =3D=3D NULL) {\ >> + lua_pop(L, 1);\ >> + luaL_error(L, "Operation failed");\ >> + }\ >> + return 1;\ >> +} >> + >> +#define LDECIMAL_CMPOP(name, cmp)\ >> +static int \ >> +ldecimal_##name(struct lua_State *L) {\ >> + if (lua_gettop(L) < 2)\ >> + return luaL_error(L, "Usage: decimal.__"#name"(decimal, = decimal)");\ >> + decimal_t *lhs =3D lua_todecimal(L, 1);\ >> + decimal_t *rhs =3D lua_todecimal(L, 2);\ >> + lua_pushboolean(L, decimal_compare(lhs, rhs) cmp 0);\ >> + return 1;\ >> +} >> + >> +uint32_t CTID_DECIMAL; >=20 > Should be static. >=20 >> + >> +static decimal_t * >> +lua_pushdecimal(struct lua_State *L) >> +{ >> + decimal_t *res =3D luaL_pushcdata(L, CTID_DECIMAL); >> + return res; >> +} >> + >> +static decimal_t * >> +lua_checkdecimal(struct lua_State *L, int index) >> +{ >> + uint32_t ctypeid; >> + decimal_t *res =3D luaL_checkcdata(L, index, &ctypeid); >> + if (ctypeid !=3D CTID_DECIMAL) >> + luaL_error(L, "Expected decimal as %d argument", index); >> + return res; >> +} >> + >> +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 =3D lua_gettop(L) + index + 1; >> + decimal_t *res =3D lua_pushdecimal(L); >> + switch(lua_type(L, index)) >> + { >> + case LUA_TNUMBER: >> + { >> + double n =3D lua_tonumber(L, index); >> + if (decimal_from_double(res, n) =3D=3D NULL) >> + goto err; >> + break; >> + } >> + case LUA_TSTRING: >> + { >> + const char *str =3D lua_tostring(L, index); >> + if (decimal_from_string(res, str) =3D=3D NULL) >> + goto err; >> + break; >> + } >> + case LUA_TCDATA: >> + { >> + uint32_t ctypeid; >> + void *cdata =3D luaL_checkcdata(L, index, &ctypeid); >> + int64_t ival; >> + uint64_t uval; >> + double d; >> + if (ctypeid =3D=3D 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 =3D *(int8_t *) cdata; >> + if (decimal_from_int64(res, ival) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_INT16: >> + ival =3D *(int16_t *) cdata; >> + if (decimal_from_int64(res, ival) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_INT32: >> + ival =3D *(int32_t *) cdata; >> + if (decimal_from_int64(res, ival) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_INT64: >> + ival =3D *(int64_t *) cdata; >> + if (decimal_from_int64(res, ival) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_UINT8: >> + uval =3D *(uint8_t *) cdata; >> + if (decimal_from_uint64(res, uval) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_UINT16: >> + uval =3D *(uint16_t *) cdata; >> + if (decimal_from_uint64(res, uval) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_UINT32: >> + uval =3D *(uint32_t *) cdata; >> + if (decimal_from_uint64(res, uval) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_UINT64: >> + uval =3D *(uint64_t *) cdata; >> + if (decimal_from_uint64(res, uval) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_FLOAT: >> + d =3D *(float *) cdata; >> + if (decimal_from_double(res, d) =3D=3D NULL) >> + goto err; >> + break; >> + case CTID_DOUBLE: >> + d =3D *(double *) cdata; >> + if (decimal_from_double(res, d) =3D=3D 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); >=20 > Too long lines. Please split. >=20 > Also, sometimes you start an error message with a lower-case letter, > sometimes with an upper-case. Please be consistent. >=20 >> + /* luaL_error never returns, this is to silence compiler = warning. */ >> + return NULL; >> +} >> + >> +LDECIMAL_OP2(add, add); >> +LDECIMAL_OP2(sub, sub); >> +LDECIMAL_OP2(mul, mul); >> +LDECIMAL_OP2(div, div); >> +LDECIMAL_OP2(pow, pow); >> + >> +LDECIMAL_UNOP(log10, log10); >> +LDECIMAL_UNOP(ln, ln); >> +LDECIMAL_UNOP(exp, exp); >> +LDECIMAL_UNOP(sqrt, sqrt); >> +LDECIMAL_UNOP(minus, minus); >> +LDECIMAL_UNOP(abs, abs); >> + >> +LDECIMAL_CMPOP(eq, =3D=3D); >> +LDECIMAL_CMPOP(lt, <); >> +LDECIMAL_CMPOP(le, <=3D); >=20 > Semicolons (;) are not needed here, as these macros define functions. >=20 >> + >> +static int >> +ldecimal_new(struct lua_State *L) >> +{ >> + if (lua_gettop(L) < 1) >> + luaL_error(L, "Usage: decimal.new(value)"); >> + lua_todecimal(L, 1); >> + decimal_t *lhs =3D lua_checkdecimal(L, 1); >=20 > Why not simply >=20 > decimal_t lhs =3D lua_todecimal(L, 1); >=20 > ? >=20 >> + decimal_t *res =3D lua_pushdecimal(L); >> + *res =3D *lhs; >> + return 1; >> +} >=20 >> diff --git a/src/lua/decimal.h b/src/lua/decimal.h >> new file mode 100644 >> index 00000000..12b29d39 >> --- /dev/null >> +++ b/src/lua/decimal.h >> @@ -0,0 +1,39 @@ >=20 >> +#ifndef TARANTOOL_LUA_DECIMAL_H_INCLUDED >> +#define TARANTOOL_LUA_DECIMAL_H_INCLUDED >=20 > You forgot to add extern "C". >=20 >> + >> +struct lua_State; >> + >> +void >> +tarantool_lua_decimal_init(struct lua_State *L); >> + >> +#endif /* TARANTOOL_LUA_DECIMAL_H_INCLUDED */ >=20 >> diff --git a/test/app/decimal.test.lua b/test/app/decimal.test.lua >> new file mode 100644 >> index 00000000..b101f07b >> --- /dev/null >> +++ b/test/app/decimal.test.lua >> @@ -0,0 +1,42 @@ >> +decimal =3D require('decimal') >> +test_run =3D require('test_run').new() >> + >> +-- check various constructors >> +decimal.new('1234.5678') >> +decimal.new('1e6') >> +decimal.new('-6.234612e2') >> +decimal.new(tonumber64(2^63)) >> +decimal.new(12345678ULL) >> +decimal.new(-12345678LL) >> +decimal.new(1) >> +decimal.new(-1) >> +decimal.new(2^64) >> +decimal.new(2^(-20)) >> + >> +a =3D decimal.new('100') >> +decimal.log10(a) >> +decimal.ln(a) >> +-- overflow, e^100 > 10^38 >> +decimal.exp(a) >> +-- e^87 < 10^38, no overflow, but rounds >> +-- to 0 digits after the decimal point. >> +decimal.exp(decimal.new(87)) >> +decimal.sqrt(a) >> +a >> +a =3D decimal.new('-123.456') >> +decimal.round(a, 2) >> +decimal.abs(a) >> +a >> +-a >> +a / 10 >> +a * 5 >> +a + 17 >> +a - 0.0001 >> +a ^ 2 >> +decimal.abs(a) ^ 0.5 >> +decimal.abs(a) ^ 0.5 =3D=3D decimal.sqrt(decimal.abs(a)) >> +a - 2 < a - 1 >> +a + 1e-10 > a >> +a <=3D a >> +a >=3D a >> +a =3D=3D a >=20 > Please add more tests: >=20 > - All possible errors thrown by the code checking arguments. > - All overflow errors, resulting from both unary and binary operators. > - Creating a decimal from all kinds of objects (UINT/INT/DOUBLE...) >=20 > In other words, I want to see that every single line of you code is > covered: >=20 > https://coveralls.io/builds/24193215/source?filename=3Dsrc/lua/decimal.c= >=20 > Also, it might be worth checking that none of the functions = occasionally > modifies the decimal passed to it (i.e. it does create a new object). >=20 --Apple-Mail=_BEC54B79-D16B-4629-BC23-D29405219B5E Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8 Hi! = Thank you for review! I addressed your comments and sent v2.
The only inconsistency is with macro names: I feel like = LDECIMAL_BINOP and LDECIMAL_FUNC
are better, since = the latter one is only used for functions now (ln, log10, sqrt, abs).




26 =D0=B8=D1=8E=D0=BD=D1=8F 2019 =D0=B3., =D0=B2 17:02, = Vladimir Davydov <vdavydov.dev@gmail.com> =D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0= =B0=D0=BB(=D0=B0):

Pasting the whole patch here for review:

=46rom = 565aa457dfb40302797cdd1d9863559fe2c52468 Mon Sep 17 00:00:00 2001
From: Serge Petrenko <sergepetrenko@tarantool.org>
Date: Wed, = 5 Jun 2019 15:58:49 +0300
Subject: [PATCH] decimal: expose = decimal type to lua.

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 =3D = 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 =3D decimal.new(123e-7)
b =3D = decimal.new('123.456')
c =3D decimal.new('123.456e2')
d =3D decimal.new(123ULL)
e =3D = 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.
When the operation is called as = `decimal.opname`, both operands may be
strings or lua = numbers, e.g. `decimal.add(23, '123.456') =3D=3D 146.456`:

There's no decimal.add = anymore.


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.

```
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'
...```

It's unclear = what a, b, c, d, e are in this example.

The following math = functions are also supported:
log10, ln, exp, sqrt, pow. = When specified as
`decimal.opname()`, operations may be = performed on
strings and lua numbers.
```
f =3D decimal.new(100)

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

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

tarantool> e2 =3D = 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 =3D decimal.new(-5)
---
...

tarantool> a
---
- '-5.000000000000000'
...

This looks weird. Can't we trim = trailing zeros somehow?


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

tarantool> -a
---
- '5.000000000000000'
...

tostring(a)
---
- = '-5.000000000000000'
...

```
`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 =3D = decimal.new('123.456789')
---
...

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

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

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

```

Comparsions: `>`, `<`, = `>=3D`, `<=3D`, `=3D=3D` 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.

diff --git a/src/lua/decimal.c b/src/lua/decimal.c
new file mode 100644
index = 00000000..4dc0dff7
--- /dev/null
+++ = b/src/lua/decimal.c
@@ -0,0 +1,335 @@

+#define LDECIMAL_OP2(name, opname)\
+static = int \
+ldecimal_##name(struct lua_State *L) {\
+ = if (lua_gettop(L) < 2)\
+ return = luaL_error(L, "Usage: decimal."#name"(decimal, decimal)");\

There's no decimal_mul or = decimal_add anymore...

+ decimal_t *lhs =3D = lua_todecimal(L, 1);\
+ decimal_t *rhs =3D = lua_todecimal(L, 2);\
+ decimal_t *res =3D = lua_pushdecimal(L);\
+ if (decimal_##opname(res, lhs, = rhs) =3D=3D NULL) {\
+ lua_pop(L, 1);\
+ = = luaL_error(L, "Operation failed.");\
The error message isn't informative. Should be "Decimal = overflow"
I think.

+ }\
+ return = 1;\
+}

Please = align backslash (\):

#define = LDECIMAL_OP2(name, opname) \
static int \
ldecimal_##name(struct luaState *L) \

and so on.

Also, = please add brief comments to all helper functions.

+#define = LDECIMAL_UNOP(name, opname)\

I'd= call them LDECIMAL_OP1 and LDECIMAL_OP2 or LDECIMAL_UNOP and
LDECIMAL_BINOP, for consistency.

+static int \
+ldecimal_##name(struct lua_State *L) {\
+ if = (lua_gettop(L) < 1)\
+ return luaL_error(L, "Usage: = decimal."#name"(decimal)");\

There's no decimal.minus.

+ decimal_t = *lhs =3D lua_todecimal(L, 1);\
+ decimal_t = *res =3D lua_pushdecimal(L);\
+ if = (decimal_##opname(res, lhs) =3D=3D NULL) {\
+ = lua_pop(L, 1);\
+ luaL_error(L, "Operation = failed");\
+ }\
+ return = 1;\
+}
+
+#define = LDECIMAL_CMPOP(name, cmp)\
+static int \
+ldecimal_##name(struct lua_State *L) {\
+ if = (lua_gettop(L) < 2)\
+ return luaL_error(L, "Usage: = decimal.__"#name"(decimal, decimal)");\
+ decimal_t = *lhs =3D lua_todecimal(L, 1);\
+ decimal_t = *rhs =3D lua_todecimal(L, 2);\
+ = lua_pushboolean(L, decimal_compare(lhs, rhs) cmp 0);\
+ = return 1;\
+}
+
+uint32_t= CTID_DECIMAL;

Should be = static.

+
+static decimal_t *
+lua_pushdecimal(struct lua_State *L)
+{
+ = decimal_t *res =3D luaL_pushcdata(L, CTID_DECIMAL);
+ = return res;
+}
+
+static = decimal_t *
+lua_checkdecimal(struct lua_State *L, int = index)
+{
+ uint32_t ctypeid;
+ = decimal_t *res =3D luaL_checkcdata(L, index, &ctypeid);
+ = if (ctypeid !=3D CTID_DECIMAL)
+ = luaL_error(L, "Expected decimal as %d argument", index);
+ = return res;
+}
+
+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 =3D lua_gettop(L) + index + = 1;
+ decimal_t *res =3D = lua_pushdecimal(L);
+ switch(lua_type(L, index))
+ = {
+ case LUA_TNUMBER:
+ = {
+ double n =3D lua_tonumber(L, = index);
+ if (decimal_from_double(res, n) = =3D=3D NULL)
+ goto err;
+ break;
+ = }
+ case LUA_TSTRING:
+ = {
+ const char *str =3D = lua_tostring(L, index);
+ if (decimal_from_string(res, str) = =3D=3D NULL)
+ goto err;
+ break;
+ = }
+ case LUA_TCDATA:
+ = {
+ uint32_t ctypeid;
+ = = void *cdata =3D luaL_checkcdata(L, index, &ctypeid);
+ = = int64_t ival;
+ uint64_t uval;
+ double = d;
+ if (ctypeid =3D=3D 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 =3D *(int8_t *) cdata;
+ if = (decimal_from_int64(res, ival) =3D=3D NULL)
+ goto = err;
+ break;
+ case = CTID_INT16:
+ ival =3D *(int16_t *) cdata;
+ = = = if (decimal_from_int64(res, ival) =3D=3D NULL)
+ goto = err;
+ break;
+ case = CTID_INT32:
+ ival =3D *(int32_t *) cdata;
+ = = = if (decimal_from_int64(res, ival) =3D=3D NULL)
+ goto = err;
+ break;
+ case = CTID_INT64:
+ ival =3D *(int64_t *) cdata;
+ = = = if (decimal_from_int64(res, ival) =3D=3D NULL)
+ goto = err;
+ break;
+ case = CTID_UINT8:
+ uval =3D *(uint8_t *) cdata;
+ = = = if (decimal_from_uint64(res, uval) =3D=3D NULL)
+ = = = = goto err;
+ break;
+ case = CTID_UINT16:
+ uval =3D *(uint16_t *) cdata;
+ = = = if (decimal_from_uint64(res, uval) =3D=3D NULL)
+ = = = = goto err;
+ break;
+ case = CTID_UINT32:
+ uval =3D *(uint32_t *) cdata;
+ = = = if (decimal_from_uint64(res, uval) =3D=3D NULL)
+ = = = = goto err;
+ break;
+ case = CTID_UINT64:
+ uval =3D *(uint64_t *) cdata;
+ = = = if (decimal_from_uint64(res, uval) =3D=3D NULL)
+ = = = = goto err;
+ break;
+ case = CTID_FLOAT:
+ d =3D *(float *) cdata;
+ = = = if (decimal_from_double(res, d) =3D=3D NULL)
+ goto = err;
+ break;
+ case = CTID_DOUBLE:
+ d =3D *(double *) cdata;
+ = = = if (decimal_from_double(res, d) =3D=3D 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);

Too long = lines. Please split.

Also, sometimes you = start an error message with a lower-case letter,
sometimes = with an upper-case. Please be consistent.

+ /* = luaL_error never returns, this is to silence compiler warning. */
+ = return NULL;
+}
+
+LDECIMAL_OP2(add, add);
+LDECIMAL_OP2(sub, = sub);
+LDECIMAL_OP2(mul, mul);
+LDECIMAL_OP2(div, div);
+LDECIMAL_OP2(pow, = pow);
+
+LDECIMAL_UNOP(log10, log10);
+LDECIMAL_UNOP(ln, ln);
+LDECIMAL_UNOP(exp, = exp);
+LDECIMAL_UNOP(sqrt, sqrt);
+LDECIMAL_UNOP(minus, minus);
+LDECIMAL_UNOP(abs,= abs);
+
+LDECIMAL_CMPOP(eq, =3D=3D);
+LDECIMAL_CMPOP(lt, <);
+LDECIMAL_CMPOP(le, = <=3D);

Semicolons (;) are = not needed here, as these macros define functions.

+
+static = int
+ldecimal_new(struct lua_State *L)
+{
+ = if (lua_gettop(L) < 1)
+ = luaL_error(L, "Usage: decimal.new(value)");
+ = lua_todecimal(L, 1);
+ decimal_t *lhs =3D = lua_checkdecimal(L, 1);

Why = not simply

decimal_t lhs =3D = lua_todecimal(L, 1);

?

+ decimal_t = *res =3D lua_pushdecimal(L);
+ *res =3D *lhs;
+ return = 1;
+}

diff --git a/src/lua/decimal.h = b/src/lua/decimal.h
new file mode 100644
index= 00000000..12b29d39
--- /dev/null
+++ = b/src/lua/decimal.h
@@ -0,0 +1,39 @@

+#ifndef TARANTOOL_LUA_DECIMAL_H_INCLUDED
+#define TARANTOOL_LUA_DECIMAL_H_INCLUDED

You forgot to add extern "C".

+
+struct lua_State;
+
+void
+tarantool_lua_decimal_init(struct lua_State *L);
+
+#endif /* TARANTOOL_LUA_DECIMAL_H_INCLUDED = */

diff --git a/test/app/decimal.test.lua = b/test/app/decimal.test.lua
new file mode 100644
index 00000000..b101f07b
--- /dev/null
+++ b/test/app/decimal.test.lua
@@ -0,0 +1,42 = @@
+decimal =3D require('decimal')
+test_run = =3D require('test_run').new()
+
+-- check = various constructors
+decimal.new('1234.5678')
+decimal.new('1e6')
+decimal.new('-6.234612e2')
+decimal.new(tonumber64(2^63))
+decimal.new(12345678ULL)
+decimal.new(-12345678LL)
+decimal.new(1)
+decimal.new(-1)
+decimal.new(2^64)
+decimal.new(2^(-20))
+
+a =3D = decimal.new('100')
+decimal.log10(a)
+decimal.ln(a)
+-- overflow, e^100 > = 10^38
+decimal.exp(a)
+-- e^87 < 10^38, = no overflow, but rounds
+-- to 0 digits after the decimal = point.
+decimal.exp(decimal.new(87))
+decimal.sqrt(a)
+a
+a =3D = decimal.new('-123.456')
+decimal.round(a, 2)
+decimal.abs(a)
+a
+-a
+a / 10
+a * 5
+a + 17
+a - 0.0001
+a ^ 2
+decimal.abs(a) = ^ 0.5
+decimal.abs(a) ^ 0.5 =3D=3D = decimal.sqrt(decimal.abs(a))
+a - 2 < a - 1
+a + 1e-10 > a
+a <=3D a
+a = >=3D a
+a =3D=3D a

Please add more tests:

- All = possible errors thrown by the code checking arguments.
- = All overflow errors, resulting from both unary and binary operators.
- Creating a decimal from all kinds of objects = (UINT/INT/DOUBLE...)

In other words, I want = to see that every single line of you code is
covered:

https://coveralls.io/builds/24193215/source?filename=3Dsrc/lua/= decimal.c

Also, it might be worth = checking that none of the functions occasionally
modifies = the decimal passed to it (i.e. it does create a new object).


= --Apple-Mail=_BEC54B79-D16B-4629-BC23-D29405219B5E--