cosmopolitan/third_party/quickjs/bigdecimal.c
Justine Tunney c1d99676c4 Revert "Unbloat build config"
This reverts commit ae5d06dc53.
2022-08-10 12:44:56 -07:00

723 lines
21 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "third_party/quickjs/internal.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */
JSValue JS_NewBigDecimal(JSContext *ctx)
{
JSBigDecimal *p;
p = js_malloc(ctx, sizeof(*p));
if (!p)
return JS_EXCEPTION;
p->header.ref_count = 1;
bfdec_init(ctx->bf_ctx, &p->num);
return JS_MKPTR(JS_TAG_BIG_DECIMAL, p);
}
/* return NULL if invalid type */
bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val)
{
uint32_t tag;
JSBigDecimal *p;
bfdec_t *r;
tag = JS_VALUE_GET_NORM_TAG(val);
switch(tag) {
case JS_TAG_BIG_DECIMAL:
p = JS_VALUE_GET_PTR(val);
r = &p->num;
break;
default:
JS_ThrowTypeError(ctx, "bigdecimal expected");
r = NULL;
break;
}
return r;
}
static JSValue JS_ToBigDecimalFree(JSContext *ctx, JSValue val,
BOOL allow_null_or_undefined)
{
redo:
switch(JS_VALUE_GET_NORM_TAG(val)) {
case JS_TAG_BIG_DECIMAL:
break;
case JS_TAG_NULL:
if (!allow_null_or_undefined)
goto fail;
/* fall thru */
case JS_TAG_BOOL:
case JS_TAG_INT:
{
bfdec_t *r;
int32_t v = JS_VALUE_GET_INT(val);
val = JS_NewBigDecimal(ctx);
if (JS_IsException(val))
break;
r = JS_GetBigDecimal(val);
if (bfdec_set_si(r, v)) {
JS_FreeValue(ctx, val);
val = JS_EXCEPTION;
break;
}
}
break;
case JS_TAG_FLOAT64:
case JS_TAG_BIG_INT:
case JS_TAG_BIG_FLOAT:
val = JS_ToStringFree(ctx, val);
if (JS_IsException(val))
break;
goto redo;
case JS_TAG_STRING:
{
const char *str, *p;
size_t len;
int err;
str = JS_ToCStringLen(ctx, &len, val);
JS_FreeValue(ctx, val);
if (!str)
return JS_EXCEPTION;
p = str;
p += skip_spaces(p);
if ((p - str) == len) {
bfdec_t *r;
val = JS_NewBigDecimal(ctx);
if (JS_IsException(val))
break;
r = JS_GetBigDecimal(val);
bfdec_set_zero(r, 0);
err = 0;
} else {
val = js_atof(ctx, p, &p, 0, ATOD_TYPE_BIG_DECIMAL);
if (JS_IsException(val)) {
JS_FreeCString(ctx, str);
return JS_EXCEPTION;
}
p += skip_spaces(p);
err = ((p - str) != len);
}
JS_FreeCString(ctx, str);
if (err) {
JS_FreeValue(ctx, val);
return JS_ThrowSyntaxError(ctx, "invalid bigdecimal literal");
}
}
break;
case JS_TAG_OBJECT:
val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER);
if (JS_IsException(val))
break;
goto redo;
case JS_TAG_UNDEFINED:
{
bfdec_t *r;
if (!allow_null_or_undefined)
goto fail;
val = JS_NewBigDecimal(ctx);
if (JS_IsException(val))
break;
r = JS_GetBigDecimal(val);
bfdec_set_nan(r);
}
break;
default:
fail:
JS_FreeValue(ctx, val);
return JS_ThrowTypeError(ctx, "cannot convert to bigdecimal");
}
return val;
}
static JSValue js_bigdecimal_constructor(JSContext *ctx,
JSValueConst new_target,
int argc, JSValueConst *argv)
{
JSValue val;
if (!JS_IsUndefined(new_target))
return JS_ThrowTypeError(ctx, "not a constructor");
if (argc == 0) {
bfdec_t *r;
val = JS_NewBigDecimal(ctx);
if (JS_IsException(val))
return val;
r = JS_GetBigDecimal(val);
bfdec_set_zero(r, 0);
} else {
val = JS_ToBigDecimalFree(ctx, JS_DupValue(ctx, argv[0]), FALSE);
}
return val;
}
static JSValue js_thisBigDecimalValue(JSContext *ctx, JSValueConst this_val)
{
if (JS_IsBigDecimal(this_val))
return JS_DupValue(ctx, this_val);
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
JSObject *p = JS_VALUE_GET_OBJ(this_val);
if (p->class_id == JS_CLASS_BIG_DECIMAL) {
if (JS_IsBigDecimal(p->u.object_data))
return JS_DupValue(ctx, p->u.object_data);
}
}
return JS_ThrowTypeError(ctx, "not a bigdecimal");
}
static JSValue js_bigdecimal_toString(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue val;
val = js_thisBigDecimalValue(ctx, this_val);
if (JS_IsException(val))
return val;
return JS_ToStringFree(ctx, val);
}
static JSValue js_bigdecimal_valueOf(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
return js_thisBigDecimalValue(ctx, this_val);
}
static int js_bigdecimal_get_rnd_mode(JSContext *ctx, JSValueConst obj)
{
const char *str;
size_t size;
int rnd_mode;
str = JS_ToCStringLen(ctx, &size, obj);
if (!str)
return -1;
if (strlen(str) != size)
goto invalid_rounding_mode;
if (!strcmp(str, "floor")) {
rnd_mode = BF_RNDD;
} else if (!strcmp(str, "ceiling")) {
rnd_mode = BF_RNDU;
} else if (!strcmp(str, "down")) {
rnd_mode = BF_RNDZ;
} else if (!strcmp(str, "up")) {
rnd_mode = BF_RNDA;
} else if (!strcmp(str, "half-even")) {
rnd_mode = BF_RNDN;
} else if (!strcmp(str, "half-up")) {
rnd_mode = BF_RNDNA;
} else {
invalid_rounding_mode:
JS_FreeCString(ctx, str);
JS_ThrowTypeError(ctx, "invalid rounding mode");
return -1;
}
JS_FreeCString(ctx, str);
return rnd_mode;
}
typedef struct {
int64_t prec;
bf_flags_t flags;
} BigDecimalEnv;
static int js_bigdecimal_get_env(JSContext *ctx, BigDecimalEnv *fe,
JSValueConst obj)
{
JSValue prop;
int64_t val;
BOOL has_prec;
int rnd_mode;
if (!JS_IsObject(obj)) {
JS_ThrowTypeErrorNotAnObject(ctx);
return -1;
}
prop = JS_GetProperty(ctx, obj, JS_ATOM_roundingMode);
if (JS_IsException(prop))
return -1;
rnd_mode = js_bigdecimal_get_rnd_mode(ctx, prop);
JS_FreeValue(ctx, prop);
if (rnd_mode < 0)
return -1;
fe->flags = rnd_mode;
prop = JS_GetProperty(ctx, obj, JS_ATOM_maximumSignificantDigits);
if (JS_IsException(prop))
return -1;
has_prec = FALSE;
if (!JS_IsUndefined(prop)) {
if (JS_ToInt64SatFree(ctx, &val, prop))
return -1;
if (val < 1 || val > BF_PREC_MAX)
goto invalid_precision;
fe->prec = val;
has_prec = TRUE;
}
prop = JS_GetProperty(ctx, obj, JS_ATOM_maximumFractionDigits);
if (JS_IsException(prop))
return -1;
if (!JS_IsUndefined(prop)) {
if (has_prec) {
JS_FreeValue(ctx, prop);
JS_ThrowTypeError(ctx, "cannot provide both maximumSignificantDigits and maximumFractionDigits");
return -1;
}
if (JS_ToInt64SatFree(ctx, &val, prop))
return -1;
if (val < 0 || val > BF_PREC_MAX) {
invalid_precision:
JS_ThrowTypeError(ctx, "invalid precision");
return -1;
}
fe->prec = val;
fe->flags |= BF_FLAG_RADPNT_PREC;
has_prec = TRUE;
}
if (!has_prec) {
JS_ThrowTypeError(ctx, "precision must be present");
return -1;
}
return 0;
}
static JSValue js_bigdecimal_fop(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic)
{
bfdec_t *a, *b, r_s, *r = &r_s;
JSValue op1, op2, res;
BigDecimalEnv fe_s, *fe = &fe_s;
int op_count, ret;
if (magic == MATH_OP_SQRT ||
magic == MATH_OP_ROUND)
op_count = 1;
else
op_count = 2;
op1 = JS_ToNumeric(ctx, argv[0]);
if (JS_IsException(op1))
return op1;
a = JS_ToBigDecimal(ctx, op1);
if (!a) {
JS_FreeValue(ctx, op1);
return JS_EXCEPTION;
}
if (op_count >= 2) {
op2 = JS_ToNumeric(ctx, argv[1]);
if (JS_IsException(op2)) {
JS_FreeValue(ctx, op1);
return op2;
}
b = JS_ToBigDecimal(ctx, op2);
if (!b)
goto fail;
} else {
op2 = JS_UNDEFINED;
b = NULL;
}
fe->flags = BF_RNDZ;
fe->prec = BF_PREC_INF;
if (op_count < argc) {
if (js_bigdecimal_get_env(ctx, fe, argv[op_count]))
goto fail;
}
res = JS_NewBigDecimal(ctx);
if (JS_IsException(res)) {
fail:
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
return JS_EXCEPTION;
}
r = JS_GetBigDecimal(res);
switch (magic) {
case MATH_OP_ADD:
ret = bfdec_add(r, a, b, fe->prec, fe->flags);
break;
case MATH_OP_SUB:
ret = bfdec_sub(r, a, b, fe->prec, fe->flags);
break;
case MATH_OP_MUL:
ret = bfdec_mul(r, a, b, fe->prec, fe->flags);
break;
case MATH_OP_DIV:
ret = bfdec_div(r, a, b, fe->prec, fe->flags);
break;
case MATH_OP_FMOD:
ret = bfdec_rem(r, a, b, fe->prec, fe->flags, BF_RNDZ);
break;
case MATH_OP_SQRT:
ret = bfdec_sqrt(r, a, fe->prec, fe->flags);
break;
case MATH_OP_ROUND:
ret = bfdec_set(r, a);
if (!(ret & BF_ST_MEM_ERROR))
ret = bfdec_round(r, fe->prec, fe->flags);
break;
default:
abort();
}
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
ret &= BF_ST_MEM_ERROR | BF_ST_DIVIDE_ZERO | BF_ST_INVALID_OP |
BF_ST_OVERFLOW;
if (ret != 0) {
JS_FreeValue(ctx, res);
return throw_bf_exception(ctx, ret);
} else {
return res;
}
}
static JSValue js_bigdecimal_toFixed(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue val, ret;
int64_t f;
int rnd_mode;
val = js_thisBigDecimalValue(ctx, this_val);
if (JS_IsException(val))
return val;
if (JS_ToInt64Sat(ctx, &f, argv[0]))
goto fail;
if (f < 0 || f > BF_PREC_MAX) {
JS_ThrowRangeError(ctx, "invalid number of digits");
goto fail;
}
rnd_mode = BF_RNDNA;
if (argc > 1) {
rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv[1]);
if (rnd_mode < 0)
goto fail;
}
ret = js_bigdecimal_to_string1(ctx, val, f, rnd_mode | BF_FTOA_FORMAT_FRAC);
JS_FreeValue(ctx, val);
return ret;
fail:
JS_FreeValue(ctx, val);
return JS_EXCEPTION;
}
static JSValue js_bigdecimal_toExponential(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue val, ret;
int64_t f;
int rnd_mode;
val = js_thisBigDecimalValue(ctx, this_val);
if (JS_IsException(val))
return val;
if (JS_ToInt64Sat(ctx, &f, argv[0]))
goto fail;
if (JS_IsUndefined(argv[0])) {
ret = js_bigdecimal_to_string1(ctx, val, 0,
BF_RNDN | BF_FTOA_FORMAT_FREE_MIN | BF_FTOA_FORCE_EXP);
} else {
if (f < 0 || f > BF_PREC_MAX) {
JS_ThrowRangeError(ctx, "invalid number of digits");
goto fail;
}
rnd_mode = BF_RNDNA;
if (argc > 1) {
rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv[1]);
if (rnd_mode < 0)
goto fail;
}
ret = js_bigdecimal_to_string1(ctx, val, f + 1,
rnd_mode | BF_FTOA_FORMAT_FIXED | BF_FTOA_FORCE_EXP);
}
JS_FreeValue(ctx, val);
return ret;
fail:
JS_FreeValue(ctx, val);
return JS_EXCEPTION;
}
static JSValue js_bigdecimal_toPrecision(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue val, ret;
int64_t p;
int rnd_mode;
val = js_thisBigDecimalValue(ctx, this_val);
if (JS_IsException(val))
return val;
if (JS_IsUndefined(argv[0])) {
return JS_ToStringFree(ctx, val);
}
if (JS_ToInt64Sat(ctx, &p, argv[0]))
goto fail;
if (p < 1 || p > BF_PREC_MAX) {
JS_ThrowRangeError(ctx, "invalid number of digits");
goto fail;
}
rnd_mode = BF_RNDNA;
if (argc > 1) {
rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv[1]);
if (rnd_mode < 0)
goto fail;
}
ret = js_bigdecimal_to_string1(ctx, val, p,
rnd_mode | BF_FTOA_FORMAT_FIXED);
JS_FreeValue(ctx, val);
return ret;
fail:
JS_FreeValue(ctx, val);
return JS_EXCEPTION;
}
static const JSCFunctionListEntry js_bigdecimal_proto_funcs[] = {
JS_CFUNC_DEF("toString", 0, js_bigdecimal_toString ),
JS_CFUNC_DEF("valueOf", 0, js_bigdecimal_valueOf ),
JS_CFUNC_DEF("toPrecision", 1, js_bigdecimal_toPrecision ),
JS_CFUNC_DEF("toFixed", 1, js_bigdecimal_toFixed ),
JS_CFUNC_DEF("toExponential", 1, js_bigdecimal_toExponential ),
};
static const JSCFunctionListEntry js_bigdecimal_funcs[] = {
JS_CFUNC_MAGIC_DEF("add", 2, js_bigdecimal_fop, MATH_OP_ADD ),
JS_CFUNC_MAGIC_DEF("sub", 2, js_bigdecimal_fop, MATH_OP_SUB ),
JS_CFUNC_MAGIC_DEF("mul", 2, js_bigdecimal_fop, MATH_OP_MUL ),
JS_CFUNC_MAGIC_DEF("div", 2, js_bigdecimal_fop, MATH_OP_DIV ),
JS_CFUNC_MAGIC_DEF("mod", 2, js_bigdecimal_fop, MATH_OP_FMOD ),
JS_CFUNC_MAGIC_DEF("round", 1, js_bigdecimal_fop, MATH_OP_ROUND ),
JS_CFUNC_MAGIC_DEF("sqrt", 1, js_bigdecimal_fop, MATH_OP_SQRT ),
};
static JSValue js_string_to_bigdecimal(JSContext *ctx, const char *buf,
int radix, int flags, slimb_t *pexponent)
{
bfdec_t *a;
int ret;
JSValue val;
val = JS_NewBigDecimal(ctx);
if (JS_IsException(val))
return val;
a = JS_GetBigDecimal(val);
ret = bfdec_atof(a, buf, NULL, BF_PREC_INF,
BF_RNDZ | BF_ATOF_NO_NAN_INF);
if (ret & BF_ST_MEM_ERROR) {
JS_FreeValue(ctx, val);
return JS_ThrowOutOfMemory(ctx);
}
return val;
}
static int js_unary_arith_bigdecimal(JSContext *ctx,
JSValue *pres, OPCodeEnum op, JSValue op1)
{
bfdec_t *r, *a;
int ret, v;
JSValue res;
if (op == OP_plus && !is_math_mode(ctx)) {
JS_ThrowTypeError(ctx, "bigdecimal argument with unary +");
JS_FreeValue(ctx, op1);
return -1;
}
res = JS_NewBigDecimal(ctx);
if (JS_IsException(res)) {
JS_FreeValue(ctx, op1);
return -1;
}
r = JS_GetBigDecimal(res);
a = JS_ToBigDecimal(ctx, op1);
ret = 0;
switch(op) {
case OP_inc:
case OP_dec:
v = 2 * (op - OP_dec) - 1;
ret = bfdec_add_si(r, a, v, BF_PREC_INF, BF_RNDZ);
break;
case OP_plus:
ret = bfdec_set(r, a);
break;
case OP_neg:
ret = bfdec_set(r, a);
bfdec_neg(r);
break;
default:
abort();
}
JS_FreeValue(ctx, op1);
if (UNLIKELY(ret)) {
JS_FreeValue(ctx, res);
throw_bf_exception(ctx, ret);
return -1;
}
*pres = res;
return 0;
}
/* b must be a positive integer */
static int js_bfdec_pow(bfdec_t *r, const bfdec_t *a, const bfdec_t *b)
{
bfdec_t b1;
int32_t b2;
int ret;
bfdec_init(b->ctx, &b1);
ret = bfdec_set(&b1, b);
if (ret) {
bfdec_delete(&b1);
return ret;
}
ret = bfdec_rint(&b1, BF_RNDZ);
if (ret) {
bfdec_delete(&b1);
return BF_ST_INVALID_OP; /* must be an integer */
}
ret = bfdec_get_int32(&b2, &b1);
bfdec_delete(&b1);
if (ret)
return ret; /* overflow */
if (b2 < 0)
return BF_ST_INVALID_OP; /* must be positive */
return bfdec_pow_ui(r, a, b2);
}
static int js_compare_bigdecimal(JSContext *ctx, OPCodeEnum op,
JSValue op1, JSValue op2)
{
bfdec_t *a, *b;
int res;
/* Note: binary floats are converted to bigdecimal with
toString(). It is not mathematically correct but is consistent
with the BigDecimal() constructor behavior */
op1 = JS_ToBigDecimalFree(ctx, op1, TRUE);
if (JS_IsException(op1)) {
JS_FreeValue(ctx, op2);
return -1;
}
op2 = JS_ToBigDecimalFree(ctx, op2, TRUE);
if (JS_IsException(op2)) {
JS_FreeValue(ctx, op1);
return -1;
}
a = JS_ToBigDecimal(ctx, op1);
b = JS_ToBigDecimal(ctx, op2);
switch(op) {
case OP_lt:
res = bfdec_cmp_lt(a, b); /* if NaN return false */
break;
case OP_lte:
res = bfdec_cmp_le(a, b); /* if NaN return false */
break;
case OP_gt:
res = bfdec_cmp_lt(b, a); /* if NaN return false */
break;
case OP_gte:
res = bfdec_cmp_le(b, a); /* if NaN return false */
break;
case OP_eq:
res = bfdec_cmp_eq(a, b); /* if NaN return false */
break;
default:
abort();
}
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
return res;
}
static int js_binary_arith_bigdecimal(JSContext *ctx, OPCodeEnum op,
JSValue *pres, JSValue op1, JSValue op2)
{
bfdec_t *r, *a, *b;
int ret;
JSValue res;
res = JS_NewBigDecimal(ctx);
if (JS_IsException(res))
goto fail;
r = JS_GetBigDecimal(res);
a = JS_ToBigDecimal(ctx, op1);
if (!a)
goto fail;
b = JS_ToBigDecimal(ctx, op2);
if (!b)
goto fail;
switch(op) {
case OP_add:
ret = bfdec_add(r, a, b, BF_PREC_INF, BF_RNDZ);
break;
case OP_sub:
ret = bfdec_sub(r, a, b, BF_PREC_INF, BF_RNDZ);
break;
case OP_mul:
ret = bfdec_mul(r, a, b, BF_PREC_INF, BF_RNDZ);
break;
case OP_div:
ret = bfdec_div(r, a, b, BF_PREC_INF, BF_RNDZ);
break;
case OP_math_mod:
/* Euclidian remainder */
ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_DIVREM_EUCLIDIAN);
break;
case OP_mod:
ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ);
break;
case OP_pow:
ret = js_bfdec_pow(r, a, b);
break;
default:
abort();
}
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
if (UNLIKELY(ret)) {
JS_FreeValue(ctx, res);
throw_bf_exception(ctx, ret);
return -1;
}
*pres = res;
return 0;
fail:
JS_FreeValue(ctx, res);
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
return -1;
}
void JS_AddIntrinsicBigDecimal(JSContext *ctx)
{
JSRuntime *rt = ctx->rt;
JSValueConst obj1;
rt->bigdecimal_ops.to_string = js_bigdecimal_to_string;
rt->bigdecimal_ops.from_string = js_string_to_bigdecimal;
rt->bigdecimal_ops.unary_arith = js_unary_arith_bigdecimal;
rt->bigdecimal_ops.binary_arith = js_binary_arith_bigdecimal;
rt->bigdecimal_ops.compare = js_compare_bigdecimal;
ctx->class_proto[JS_CLASS_BIG_DECIMAL] = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_DECIMAL],
js_bigdecimal_proto_funcs,
countof(js_bigdecimal_proto_funcs));
obj1 = JS_NewGlobalCConstructor(ctx, "BigDecimal",
js_bigdecimal_constructor, 1,
ctx->class_proto[JS_CLASS_BIG_DECIMAL]);
JS_SetPropertyFunctionList(ctx, obj1, js_bigdecimal_funcs,
countof(js_bigdecimal_funcs));
}
void JS_EnableBignumExt(JSContext *ctx, BOOL enable)
{
ctx->bignum_ext = enable;
}