From: Serge Petrenko <sergepetrenko@tarantool.org> To: vdavydov.dev@gmail.com Cc: tarantool-patches@freelists.org, Serge Petrenko <sergepetrenko@tarantool.org> Subject: [PATCH v3 5/5] decimal: expose decimal type to lua. Date: Tue, 2 Jul 2019 20:27:52 +0300 [thread overview] Message-ID: <4a6a31d9502db71c7aaea05ff12249843a7544af.1562087728.git.sergepetrenko@tarantool.org> (raw) In-Reply-To: <cover.1562087728.git.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)
next prev parent reply other threads:[~2019-07-02 17:27 UTC|newest] Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top 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 ` Serge Petrenko [this message] 2019-07-03 6:50 ` [tarantool-patches] [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
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=4a6a31d9502db71c7aaea05ff12249843a7544af.1562087728.git.sergepetrenko@tarantool.org \ --to=sergepetrenko@tarantool.org \ --cc=tarantool-patches@freelists.org \ --cc=vdavydov.dev@gmail.com \ --subject='Re: [PATCH v3 5/5] decimal: expose decimal type to lua.' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox