
/*
 * 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);
}


double
njs_number_oct_parse(const u_char **start, const u_char *end,
    njs_bool_t literal)
{
    u_char        c;
    double        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 (literal && *p == '_' && (p - _) > 1) {
                _ = p;
                continue;
            }

            break;
        }

        num = num * 8 + c;
    }

    *start = p;

    return num;
}


double
njs_number_bin_parse(const u_char **start, const u_char *end,
    njs_bool_t literal)
{
    u_char        c;
    double        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 (literal && *p == '_' && (p - _) > 1) {
                _ = p;
                continue;
            }

            break;
        }

        num = num * 2 + c;
    }

    *start = p;

    return num;
}


double
njs_number_hex_parse(const u_char **start, const u_char *end,
    njs_bool_t literal)
{
    double        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;
}


static double
njs_number_radix_parse(const u_char **start, const u_char *end, uint8_t radix)
{
    uint8_t       d;
    double        num, 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 = NAN;
    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;
    u_char  buf[128];

    num = njs_number(number);

    if (isnan(num)) {
        njs_atom_to_value(vm, string, NJS_ATOM_STRING_NaN);

    } else if (isinf(num)) {

        if (num < 0) {
            njs_atom_to_value(vm, string, NJS_ATOM_STRING__Infinity);

        } else {
            njs_atom_to_value(vm, string, NJS_ATOM_STRING_Infinity);
        }

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

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

    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  buf[128];

    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_value_t *retval)
{
    njs_int_t           ret;
    njs_value_t         *value;
    njs_object_value_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, NJS_OBJ_TYPE_NUMBER, 0, value);
        if (njs_slow_path(object == NULL)) {
            return NJS_ERROR;
        }

        njs_set_object_value(retval, object);

    } else {
        njs_set_number(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, njs_value_t *retval)
{
    double      num;
    njs_bool_t  integer;

    integer = 0;

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

        if (num == trunc(num) && !isinf(num)) {
            integer = 1;
        }
    }

    njs_set_boolean(retval, integer);

    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, njs_value_t *retval)
{
    double      num;
    njs_bool_t  integer;

    integer = 0;

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

        if (num == njs_unsafe_cast_double_to_int64(num)
            && fabs(num) <= NJS_MAX_SAFE_INTEGER)
        {
            integer = 1;
        }
    }

    njs_set_boolean(retval, integer);

    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, njs_value_t *retval)
{
    njs_bool_t  nan;

    nan = 0;

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

    njs_set_boolean(retval, nan);

    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, njs_value_t *retval)
{
    double      num;
    njs_bool_t  finite;

    finite = 0;

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

        if (!isnan(num) && !isinf(num)) {
            finite = 1;
        }
    }

    njs_set_boolean(retval, finite);

    return NJS_OK;
}


static const njs_object_prop_init_t  njs_number_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("Number"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),

    NJS_DECLARE_PROP_VALUE(STRING_EPSILON,
                           njs_value(NJS_NUMBER, 1, DBL_EPSILON), 0),

    NJS_DECLARE_PROP_VALUE(STRING_MAX_SAFE_INTEGER,
                           njs_value(NJS_NUMBER, 1, NJS_MAX_SAFE_INTEGER), 0),

    NJS_DECLARE_PROP_VALUE(STRING_MIN_SAFE_INTEGER,
                           njs_value(NJS_NUMBER, 1, -NJS_MAX_SAFE_INTEGER), 0),

    NJS_DECLARE_PROP_VALUE(STRING_MAX_VALUE,
                           njs_value(NJS_NUMBER, 1, DBL_MAX), 0),

    NJS_DECLARE_PROP_VALUE(STRING_MIN_VALUE,
                           njs_value(NJS_NUMBER, 1, DBL_MIN), 0),

    NJS_DECLARE_PROP_VALUE(STRING_NaN, njs_value(NJS_NUMBER, 0, NAN), 0),

    NJS_DECLARE_PROP_VALUE(STRING_POSITIVE_INFINITY,
                           njs_value(NJS_NUMBER, 1, INFINITY), 0),

    NJS_DECLARE_PROP_VALUE(STRING_NEGATIVE_INFINITY,
                           njs_value(NJS_NUMBER, 1, -INFINITY), 0),

    NJS_DECLARE_PROP_NATIVE(STRING_isFinite, njs_number_is_finite, 1, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_isInteger, njs_number_is_integer, 1, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_isSafeInteger,
                            njs_number_is_safe_integer, 1, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_isNaN, njs_number_is_nan, 1, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_parseFloat, njs_number_parse_float, 1,
                            0),

    NJS_DECLARE_PROP_NATIVE(STRING_parseInt, njs_number_parse_int, 2, 0),
};


static 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 *retval)
{
    njs_value_t  *value;

    value = njs_argument(args, 0);

    if (value->type != NJS_NUMBER) {

        if (njs_is_object_number(value)) {
            value = njs_object_value(value);

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

    njs_value_assign(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, njs_value_t *retval)
{
    double       number;
    int32_t      radix;
    njs_int_t    ret;
    njs_value_t  *value;

    value = &args[0];

    if (value->type != NJS_NUMBER) {

        if (njs_is_object_number(value)) {
            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, "radix argument must be between 2 and 36");
            return NJS_ERROR;
        }

        number = njs_number(value);

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

    return njs_number_to_string(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, njs_value_t *retval)
{
    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 (njs_is_object_number(value)) {
            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, 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, 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, njs_value_t *retval)
{
    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 (njs_is_object_number(value)) {
            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, 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, 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, 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, njs_value_t *retval)
{
    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 (njs_is_object_number(value)) {
            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, 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, 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_init_t  njs_number_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING___proto__,
                             njs_primitive_prototype_get_proto, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_NATIVE(STRING_valueOf, njs_number_prototype_value_of,
                            0, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_toString,
                            njs_number_prototype_to_string, 1, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_toFixed, njs_number_prototype_to_fixed,
                            1, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_toPrecision,
                            njs_number_prototype_to_precision, 1, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_toExponential,
                            njs_number_prototype_to_exponential, 1, 0),
};


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, njs_value_t *retval)
{
    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(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, njs_value_t *retval)
{
    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(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, njs_value_t *retval)
{
    double             num;
    int32_t            radix;
    njs_int_t          ret;
    njs_bool_t         minus, test_prefix;
    njs_value_t        *value, lvalue;
    const u_char       *p, *end;
    njs_string_prop_t  string;

    num = NAN;

    value = njs_lvalue_arg(&lvalue, args, nargs, 1);

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

    (void) njs_string_trim(vm, value, &string, NJS_TRIM_START);

    if (string.size == 0) {
        goto done;
    }

    p = string.start;
    end = p + string.size;

    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;
    }

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

    num = minus ? -num : num;

done:

    njs_set_number(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, njs_value_t *retval)
{
    double             num;
    njs_int_t          ret;
    njs_value_t        *value, lvalue;
    njs_bool_t         minus;
    const u_char       *p, *start, *end;
    njs_string_prop_t  string;

    value = njs_lvalue_arg(&lvalue, args, nargs, 1);

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

    (void) njs_string_trim(vm, value, &string, NJS_TRIM_START);

    p = string.start;
    end = p + string.size;

    minus = 0;

    if (p == end) {
        num = NAN;
        goto done;
    }

    if (*p == '+') {
        p++;

    } else if (*p == '-') {
        p++;
        minus = 1;
    }

    start = p;
    num = njs_number_dec_parse(&p, end, 0);

    if (p == start) {
        if (p + njs_length("Infinity") > end
            || memcmp(p, "Infinity", njs_length("Infinity")) != 0)
        {
            num = NAN;
            goto done;
        }

        num = INFINITY;
        p += njs_length("Infinity");
    }

done:

    njs_set_number(retval, minus ? -num : 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_VALUE } }
                      },
};
