[tarantool-patches] [PATCH v3 5/5] decimal: expose decimal type to lua.

Serge Petrenko sergepetrenko at tarantool.org
Wed Jul 3 09:50:06 MSK 2019


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

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


--
Serge Petrenko
sergepetrenko at tarantool.org




> 2 июля 2019 г., в 20:27, Serge Petrenko <sergepetrenko at 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)
> 
> 




More information about the Tarantool-patches mailing list