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