
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


/*
 * 2^53 - 1 is the largest integer n such that n and n + 1
 * as well as -n and -n - 1 are all exactly representable
 * in the IEEE-754 format.
 */
#define NJS_MAX_SAFE_INTEGER  ((1LL << 53) - 1)


static njs_int_t njs_number_to_string_radix(njs_vm_t *vm, njs_value_t *string,
    double number, uint32_t radix);


double
njs_key_to_index(const njs_value_t *value)
{
    njs_array_t  *array;

    if (njs_fast_path(njs_is_numeric(value))) {
        return njs_number(value);

    } else if (njs_is_string(value)) {
        return njs_string_to_index(value);

    } else if (njs_is_array(value)) {

        array = njs_array(value);

        if (njs_lvlhsh_is_empty(&array->object.hash)) {

            if (array->length == 0) {
                /* An empty array value is zero. */
                return 0;
            }

            if (array->length == 1 && njs_is_valid(&array->start[0])) {
                /* A single value array is the zeroth array value. */
                return njs_key_to_index(&array->start[0]);
            }
        }
    }

    return NAN;
}


double
njs_number_dec_parse(const u_char **start, const u_char *end,
    njs_bool_t literal)
{
    return njs_strtod(start, end, literal);
}


uint64_t
njs_number_oct_parse(const u_char **start, const u_char *end)
{
    u_char        c;
    uint64_t      num;
    const u_char  *p, *_;

    p = *start;

    num = 0;
    _ = p - 1;

    for (; p < end; p++) {
        /* Values less than '0' become >= 208. */
        c = *p - '0';

        if (njs_slow_path(c > 7)) {
            if (*p == '_' && (p - _) > 1) {
                _ = p;
                continue;
            }

            break;
        }

        num = num * 8 + c;
    }

    *start = p;

    return num;
}


uint64_t
njs_number_bin_parse(const u_char **start, const u_char *end)
{
    u_char        c;
    uint64_t      num;
    const u_char  *p, *_;

    p = *start;

    num = 0;
    _ = p - 1;

    for (; p < end; p++) {
        /* Values less than '0' become >= 208. */
        c = *p - '0';

        if (njs_slow_path(c > 1)) {
            if (*p == '_' && (p - _) > 1) {
                _ = p;
                continue;
            }

            break;
        }

        num = num * 2 + c;
    }

    *start = p;

    return num;
}


uint64_t
njs_number_hex_parse(const u_char **start, const u_char *end,
    njs_bool_t literal)
{
    uint64_t      num;
    njs_int_t     n;
    const u_char  *p, *_;

    p = *start;

    num = 0;
    _ = p - 1;

    for (; p < end; p++) {
        n = njs_char_to_hex(*p);

        if (njs_slow_path(n < 0)) {
            if (literal && *p == '_' && (p - _) > 1) {
                _ = p;
                continue;
            }

            break;
        }

        num = num * 16 + n;
    }

    *start = p;

    return num;
}


int64_t
njs_number_radix_parse(const u_char **start, const u_char *end, uint8_t radix)
{
    uint8_t       d;
    int64_t       num;
    uint64_t      n;
    const u_char  *p;

    static const int8_t  digits[256]
        njs_aligned(32) =
    {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    };

    num = -1;
    n = 0;

    for (p = *start; p < end; p++) {
        d = digits[*p];

        if (njs_slow_path(d >= radix)) {
            break;
        }

        n = (n * radix) + d;
        num = n;
    }

    *start = p;

    return num;
}


njs_int_t
njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
    const njs_value_t *number)
{
    double             num;
    size_t             size;
    const njs_value_t  *value;
    u_char             buf[128];

    num = njs_number(number);

    if (isnan(num)) {
        value = &njs_string_nan;

    } else if (isinf(num)) {

        if (num < 0) {
            value = &njs_string_minus_infinity;

        } else {
            value = &njs_string_plus_infinity;
        }

    } else {
        size = njs_dtoa(num, (char *) buf);

        return njs_string_new(vm, string, buf, size, size);
    }

    *string = *value;

    return NJS_OK;
}


njs_int_t
njs_int64_to_string(njs_vm_t *vm, njs_value_t *value, int64_t i64)
{
    size_t  size;
    u_char  *dst, *p;
    u_char  buf[128];

    if (njs_fast_path(i64 >= 0 && i64 < 0x3fffffffffffLL)) {
        /* Fits to short_string. */
        dst = njs_string_short_start(value);

        p = njs_sprintf(dst, dst + NJS_STRING_SHORT, "%L", i64);

        njs_string_short_set(value, p - dst, p - dst);

        return NJS_OK;
    }

    size = njs_dtoa(i64, (char *) buf);

    return njs_string_new(vm, value, buf, size, size);
}


njs_int_t
njs_number_to_chain(njs_vm_t *vm, njs_chb_t *chain, double num)
{
    size_t  size;
    u_char  *p;

    if (isnan(num)) {
        njs_chb_append_literal(chain, "NaN");
        return njs_length("NaN");

    }

    if (isinf(num)) {
        if (num < 0) {
            njs_chb_append_literal(chain, "-Infinity");
            return njs_length("-Infinity");

        } else {
            njs_chb_append_literal(chain, "Infinity");
            return njs_length("Infinity");
        }
    }

    p = njs_chb_reserve(chain, 64);
    if (njs_slow_path(p == NULL)) {
        return NJS_ERROR;
    }

    size = njs_dtoa(num, (char *) p);

    njs_chb_written(chain, size);

    return size;
}


static njs_int_t
njs_number_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_int_t     ret;
    njs_value_t   *value;
    njs_object_t  *object;

    if (nargs == 1) {
        value = njs_value_arg(&njs_value_zero);

    } else {
        value = &args[1];

        if (njs_slow_path(!njs_is_number(value))) {
            ret = njs_value_to_numeric(vm, value, value);
            if (njs_slow_path(ret != NJS_OK)) {
                return NJS_ERROR;
            }
        }
    }

    if (vm->top_frame->ctor) {
        object = njs_object_value_alloc(vm, value, NJS_NUMBER);
        if (njs_slow_path(object == NULL)) {
            return NJS_ERROR;
        }

        njs_set_type_object(&vm->retval, object, NJS_OBJECT_NUMBER);

    } else {
        njs_set_number(&vm->retval, njs_number(value));
    }

    return NJS_OK;
}


static njs_int_t
njs_number_is_integer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double             num;
    const njs_value_t  *value;

    value = &njs_value_false;

    if (nargs > 1 && njs_is_number(&args[1])) {
        num = njs_number(&args[1]);

        if (num == trunc(num) && !isinf(num)) {
            value = &njs_value_true;
        }
    }

    vm->retval = *value;

    return NJS_OK;
}



static njs_int_t
njs_number_is_safe_integer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double             num;
    const njs_value_t  *value;

    value = &njs_value_false;

    if (nargs > 1 && njs_is_number(&args[1])) {
        num = njs_number(&args[1]);

        if (num == (int64_t) num && fabs(num) <= NJS_MAX_SAFE_INTEGER) {
            value = &njs_value_true;
        }
    }

    vm->retval = *value;

    return NJS_OK;
}


static njs_int_t
njs_number_is_nan(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    const njs_value_t  *value;

    value = &njs_value_false;

    if (nargs > 1
        && njs_is_number(&args[1])
        && isnan(njs_number(&args[1])))
    {
        value = &njs_value_true;
    }

    vm->retval = *value;

    return NJS_OK;
}


static njs_int_t
njs_number_is_finite(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double             num;
    const njs_value_t  *value;

    value = &njs_value_false;

    if (nargs > 1 && njs_is_number(&args[1])) {
        num = njs_number(&args[1]);

        if (!isnan(num) && !isinf(num)) {
            value = &njs_value_true;
        }
    }

    vm->retval = *value;

    return NJS_OK;
}


static const njs_object_prop_t  njs_number_constructor_properties[] =
{
    {
        .type = NJS_PROPERTY,
        .name = njs_string("name"),
        .value = njs_string("Number"),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("length"),
        .value = njs_value(NJS_NUMBER, 1, 1.0),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("prototype"),
        .value = njs_prop_handler(njs_object_prototype_create),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("EPSILON"),
        .value = njs_value(NJS_NUMBER, 1, DBL_EPSILON),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("MAX_SAFE_INTEGER"),
        .value = njs_value(NJS_NUMBER, 1, NJS_MAX_SAFE_INTEGER),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("MIN_SAFE_INTEGER"),
        .value = njs_value(NJS_NUMBER, 1, -NJS_MAX_SAFE_INTEGER),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("MAX_VALUE"),
        .value = njs_value(NJS_NUMBER, 1, DBL_MAX),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("MIN_VALUE"),
        .value = njs_value(NJS_NUMBER, 1, DBL_MIN),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("NaN"),
        .value = njs_value(NJS_NUMBER, 0, NAN),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("POSITIVE_INFINITY"),
        .value = njs_value(NJS_NUMBER, 1, INFINITY),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_long_string("NEGATIVE_INFINITY"),
        .value = njs_value(NJS_NUMBER, 1, -INFINITY),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("isFinite"),
        .value = njs_native_function(njs_number_is_finite, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("isInteger"),
        .value = njs_native_function(njs_number_is_integer, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("isSafeInteger"),
        .value = njs_native_function(njs_number_is_safe_integer, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("isNaN"),
        .value = njs_native_function(njs_number_is_nan, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("parseFloat"),
        .value = njs_native_function(njs_number_parse_float, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("parseInt"),
        .value = njs_native_function(njs_number_parse_int, 2),
        .writable = 1,
        .configurable = 1,
    },
};


const njs_object_init_t  njs_number_constructor_init = {
    njs_number_constructor_properties,
    njs_nitems(njs_number_constructor_properties),
};


static njs_int_t
njs_number_prototype_value_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_value_t  *value;

    value = &args[0];

    if (value->type != NJS_NUMBER) {

        if (value->type == NJS_OBJECT_NUMBER) {
            value = njs_object_value(value);

        } else {
            njs_type_error(vm, "unexpected value type:%s",
                           njs_type_string(value->type));
            return NJS_ERROR;
        }
    }

    vm->retval = *value;

    return NJS_OK;
}


static njs_int_t
njs_number_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    double       number;
    int32_t      radix;
    njs_int_t    ret;
    njs_value_t  *value;

    value = &args[0];

    if (value->type != NJS_NUMBER) {

        if (value->type == NJS_OBJECT_NUMBER) {
            value = njs_object_value(value);

        } else {
            njs_type_error(vm, "unexpected value type:%s",
                           njs_type_string(value->type));
            return NJS_ERROR;
        }
    }

    if (nargs > 1) {
        ret = njs_value_to_int32(vm, &args[1], &radix);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (radix < 2 || radix > 36 || radix != (int) radix) {
            njs_range_error(vm, NULL);
            return NJS_ERROR;
        }

        number = njs_number(value);

        if (radix != 10 && !isnan(number) && !isinf(number) && number != 0) {
            return njs_number_to_string_radix(vm, &vm->retval, number, radix);
        }
    }

    return njs_number_to_string(vm, &vm->retval, value);
}


static njs_int_t
njs_number_prototype_to_fixed(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    u_char       *p;
    int64_t      frac;
    double       number;
    size_t       length, size;
    njs_int_t    ret, point, prefix, postfix;
    njs_value_t  *value;
    u_char       buf[128], buf2[128];

    /* 128 > 100 + 21 + njs_length(".-\0"). */

    value = &args[0];

    if (value->type != NJS_NUMBER) {
        if (value->type == NJS_OBJECT_NUMBER) {
            value = njs_object_value(value);

        } else {
            njs_type_error(vm, "unexpected value type:%s",
                           njs_type_string(value->type));
            return NJS_ERROR;
        }
    }

    ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &frac);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (njs_slow_path(frac < 0 || frac > 100)) {
        njs_range_error(vm, "digits argument must be between 0 and 100");
        return NJS_ERROR;
    }

    number = njs_number(value);

    if (njs_slow_path(isnan(number) || fabs(number) >= 1e21)) {
        return njs_number_to_string(vm, &vm->retval, value);
    }

    point = 0;
    length = njs_fixed_dtoa(number, (njs_int_t) frac, (char *) buf, &point);

    prefix = 0;
    postfix = 0;

    if (point <= 0) {
        prefix = -point + 1;
        point = 1;
    }

    if (prefix + (njs_int_t) length < point + frac) {
        postfix = point + frac - length - prefix;
    }

    size = prefix + length + postfix + !!(number < 0);

    if (frac > 0) {
        size += njs_length(".");
    }

    p = buf2;

    while (--prefix >= 0) {
        *p++ = '0';
    }

    if (length != 0) {
        p = njs_cpymem(p, buf, length);
    }

    while (--postfix >= 0) {
        *p++ = '0';
    }

    p = njs_string_alloc(vm, &vm->retval, size, size);
    if (njs_slow_path(p == NULL)) {
        return NJS_ERROR;
    }

    if (number < 0) {
        *p++ = '-';
    }

    p = njs_cpymem(p, buf2, point);

    if (frac > 0) {
        *p++ = '.';

        memcpy(p, &buf2[point], frac);
    }

    return NJS_OK;
}


static njs_int_t
njs_number_prototype_to_precision(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    double       number;
    size_t       size;
    int64_t      precision;
    njs_int_t    ret;
    njs_value_t  *value;
    u_char       buf[128];

    /* 128 > 100 + 21 + njs_length(".-\0"). */

    value = &args[0];

    if (value->type != NJS_NUMBER) {
        if (value->type == NJS_OBJECT_NUMBER) {
            value = njs_object_value(value);

        } else {
            njs_type_error(vm, "unexpected value type:%s",
                           njs_type_string(value->type));
            return NJS_ERROR;
        }
    }

    if (njs_is_undefined(njs_arg(args, nargs, 1))) {
        return njs_number_to_string(vm, &vm->retval, value);
    }

    ret = njs_value_to_integer(vm, njs_argument(args, 1), &precision);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    number = njs_number(value);

    if (njs_slow_path(isnan(number) || isinf(number))) {
        return njs_number_to_string(vm, &vm->retval, value);
    }

    if (njs_slow_path(precision < 1 || precision > 100)) {
        njs_range_error(vm, "precision argument must be between 1 and 100");
        return NJS_ERROR;
    }

    size = njs_dtoa_precision(number, (char *) buf, (size_t) precision);

    return njs_string_new(vm, &vm->retval, buf, size, size);
}


static njs_int_t
njs_number_prototype_to_exponential(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    double       number;
    size_t       size;
    int64_t      frac;
    njs_int_t    ret;
    njs_value_t  *value, *value_frac;
    u_char       buf[128];

    value = &args[0];

    if (value->type != NJS_NUMBER) {
        if (value->type == NJS_OBJECT_NUMBER) {
            value = njs_object_value(value);

        } else {
            njs_type_error(vm, "unexpected value type:%s",
                           njs_type_string(value->type));
            return NJS_ERROR;
        }
    }

    value_frac = njs_arg(args, nargs, 1);

    ret = njs_value_to_integer(vm, value_frac, &frac);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    number = njs_number(value);

    if (njs_slow_path(isnan(number) || isinf(number))) {
        return njs_number_to_string(vm, &vm->retval, value);
    }

    if (njs_is_defined(value_frac)) {
        if (njs_slow_path(frac < 0 || frac > 100)) {
            njs_range_error(vm, "digits argument must be between 0 and 100");
            return NJS_ERROR;
        }

    } else {
        frac = -1;
    }

    size = njs_dtoa_exponential(number, (char *) buf, (njs_int_t) frac);

    return njs_string_new(vm, &vm->retval, buf, size, size);
}


/*
 * The radix equal to 2 produces the longest  value for a number.
 */

#define NJS_STRING_RADIX_INTERGRAL_LEN  (1 + 1024)
#define NJS_STRING_RADIX_FRACTION_LEN   (1 + 1075)
#define NJS_STRING_RADIX_LEN                                                  \
    (NJS_STRING_RADIX_INTERGRAL_LEN + NJS_STRING_RADIX_FRACTION_LEN)


njs_inline double
njs_number_next_double(double n)
{
    njs_diyfp_t  v;

    v = njs_d2diyfp(n);

    if (signbit(n)) {
        if (v.significand == 0) {
            return 0.0;
        }

        v.significand--;

    } else {
        v.significand++;
    }

    return njs_diyfp2d(v);
}


static njs_int_t
njs_number_to_string_radix(njs_vm_t *vm, njs_value_t *string,
    double number, uint32_t radix)
{
    int       digit;
    char      ch;
    double    n, remainder, integer, fraction, delta;
    u_char    *p, *end;
    uint32_t  size;
    u_char    buf[NJS_STRING_RADIX_LEN];

    static const char  *digits = "0123456789abcdefghijklmnopqrstuvwxyz";

    p = buf + NJS_STRING_RADIX_INTERGRAL_LEN;
    end = p;

    n = number;

    if (n < 0) {
        n = -n;
    }

    integer = floor(n);
    fraction = n - integer;

    delta = 0.5 * (njs_number_next_double(n) - n);
    delta = njs_max(njs_number_next_double(0.0), delta);

    if (fraction >= delta && delta != 0) {
        *p++ = '.';

        do {
            fraction *= radix;
            delta *= radix;

            digit = (int) fraction;
            *p++ = digits[digit];

            fraction -= digit;

            if ((fraction > 0.5 || (fraction == 0.5 && (digit & 1)))
                && (fraction + delta > 1))
            {
                while (p-- != buf) {
                    if (p == buf + NJS_STRING_RADIX_INTERGRAL_LEN) {
                        integer += 1;
                        break;
                    }

                    ch = *p;
                    digit = (ch > '9') ? ch - 'a' + 10 : ch - '0';

                    if ((uint32_t) (digit + 1) < radix) {
                        *p++ = digits[digit + 1];
                        break;
                    }
                }

                break;
            }

        } while (fraction >= delta);

        end = p;
    }

    p = buf + NJS_STRING_RADIX_INTERGRAL_LEN;

    while (njs_d2diyfp(integer / radix).exp > 0) {
        integer /= radix;
        *(--p) = '0';
    }

    do {
        remainder = fmod(integer, radix);
        *(--p) = digits[(int) remainder];
        integer = (integer - remainder) / radix;

    } while (integer > 0);

    if (number < 0) {
        *(--p) = '-';
    }

    size = (uint32_t) (end - p);

    return njs_string_new(vm, string, p, size, size);
}


static const njs_object_prop_t  njs_number_prototype_properties[] =
{
    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("__proto__"),
        .value = njs_prop_handler(njs_primitive_prototype_get_proto),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("constructor"),
        .value = njs_prop_handler(njs_object_prototype_create_constructor),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("valueOf"),
        .value = njs_native_function(njs_number_prototype_value_of, 0),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("toString"),
        .value = njs_native_function(njs_number_prototype_to_string, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("toFixed"),
        .value = njs_native_function(njs_number_prototype_to_fixed, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("toPrecision"),
        .value = njs_native_function(njs_number_prototype_to_precision, 1),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("toExponential"),
        .value = njs_native_function(njs_number_prototype_to_exponential, 1),
        .writable = 1,
        .configurable = 1,
    },
};


const njs_object_init_t  njs_number_prototype_init = {
    njs_number_prototype_properties,
    njs_nitems(njs_number_prototype_properties),
};


njs_int_t
njs_number_global_is_nan(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double     num;
    njs_int_t  ret;

    ret = njs_value_to_number(vm, njs_arg(args, nargs, 1), &num);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_set_boolean(&vm->retval, isnan(num));

    return NJS_OK;
}


njs_int_t
njs_number_global_is_finite(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double     num;
    njs_int_t  ret;

    ret = njs_value_to_number(vm, njs_arg(args, nargs, 1), &num);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_set_boolean(&vm->retval, !(isnan(num) || isinf(num)));

    return NJS_OK;
}


njs_int_t
njs_number_parse_int(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double        num;
    int64_t       n;
    int32_t       radix;
    njs_int_t     ret;
    njs_str_t     string;
    njs_bool_t    minus, test_prefix;
    njs_value_t   *value;
    const u_char  *p, *end;

    num = NAN;

    if (nargs < 2) {
        goto done;
    }

    value = njs_argument(args, 1);

    if (!njs_is_string(value)) {
        ret = njs_value_to_string(vm, value, value);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    njs_string_get(value, &string);

    end = string.start + string.length;

    for (p = string.start; p < end; p++) {
        if (*p != ' ') {
            goto found;
        }
    }

    goto done;

found:

    minus = 0;

    if (p[0] == '-') {
        p++;
        minus = 1;

    } else if (p[0] == '+') {
        p++;
    }

    test_prefix = (end - p > 1);
    radix = 0;

    if (nargs > 2) {
        ret = njs_value_to_int32(vm, njs_argument(args, 2), &radix);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (radix != 0) {
            if (radix < 2 || radix > 36) {
                goto done;
            }

            if (radix != 16) {
                test_prefix = 0;
            }
        }
    }

    if (radix == 0) {
        radix = 10;
    }

    if (test_prefix && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
        p += 2;
        radix = 16;
    }

    n = njs_number_radix_parse(&p, end, radix);

    if (n >= 0) {
        num = n;
        num = minus ? -num : num;
    }

done:

    njs_set_number(&vm->retval, num);

    return NJS_OK;
}


njs_int_t
njs_number_parse_float(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    double     num;
    njs_int_t  ret;

    num = NAN;

    if (nargs > 1) {
        ret = njs_value_to_string(vm, &args[1], &args[1]);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        num = njs_string_to_number(&args[1], 1);
    }

    njs_set_number(&vm->retval, num);

    return NJS_OK;
}


const njs_object_type_init_t  njs_number_type_init = {
   .constructor = njs_native_ctor(njs_number_constructor, 1, 0),
   .constructor_props = &njs_number_constructor_init,
   .prototype_props = &njs_number_prototype_init,
   .prototype_value = { .object_value = {
                            .value = njs_value(NJS_NUMBER, 0, 0.0),
                            .object = { .type = NJS_OBJECT_NUMBER } }
                      },
};
