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

#ifndef _NJS_NUMBER_H_INCLUDED_
#define _NJS_NUMBER_H_INCLUDED_


typedef union {
    double      d;
    uint64_t    u64;
} njs_diyfp_conv_t;


#define NJS_MAX_LENGTH      (0x1fffffffffffffLL)
#define NJS_INT64_DBL_MIN   (-9.223372036854776e+18) /* closest to INT64_MIN */
#define NJS_INT64_DBL_MAX   (9.223372036854776e+18) /* closest to INT64_MAX */
#define njs_uint64(h, l)            (((uint64_t) (h) << 32) + (l))

#define NJS_DBL_SIGNIFICAND_SIZE    52
#define NJS_DBL_EXPONENT_OFFSET     ((int64_t) 0x3ff)
#define NJS_DBL_EXPONENT_BIAS       (NJS_DBL_EXPONENT_OFFSET                  \
                                     + NJS_DBL_SIGNIFICAND_SIZE)

#define NJS_DBL_SIGNIFICAND_MASK    njs_uint64(0x000FFFFF, 0xFFFFFFFF)
#define NJS_DBL_HIDDEN_BIT          njs_uint64(0x00100000, 0x00000000)
#define NJS_DBL_EXPONENT_MASK       njs_uint64(0x7FF00000, 0x00000000)
#define NJS_DBL_SIGN_MASK           njs_uint64(0x80000000, 0x00000000)

double njs_key_to_index(const njs_value_t *value);
double njs_number_hex_parse(const u_char **start, const u_char *end,
    njs_bool_t literal);
njs_int_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
    const njs_value_t *number);
njs_int_t njs_number_to_chain(njs_vm_t *vm, njs_chb_t *chain,
    double number);
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);
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);
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);
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);


njs_inline NJS_NOSANITIZE("float-cast-overflow") njs_bool_t
njs_number_is_integer_index(double num)
{
    uint32_t  u32;

    u32 = num;

    return (u32 == num && u32 != 0xffffffff);
}


njs_inline njs_bool_t
njs_key_is_integer_index(double num, const njs_value_t *value)
{
    return (njs_number_is_integer_index(num))
            && !(njs_is_string(value) && num == 0 && signbit(num));
}


njs_inline int64_t
njs_number_to_integer(double num)
{
    if (njs_fast_path(!isnan(num))) {
        if (num < NJS_INT64_DBL_MIN) {
            return INT64_MIN;

        } else if (num > NJS_INT64_DBL_MAX) {
            return INT64_MAX;
        }

        return num;
    }

    return 0;
}


njs_inline int32_t
njs_number_to_int32(double num)
{
    uint32_t          r;
    uint64_t          v;
    njs_int_t         exp;
    njs_diyfp_conv_t  conv;

    conv.d = num;

    exp = (conv.u64 & NJS_DBL_EXPONENT_MASK) >> NJS_DBL_SIGNIFICAND_SIZE;

    if (njs_fast_path(exp < (NJS_DBL_EXPONENT_OFFSET + 31))) {
        /* |num| < 2**31. */
        return num;
    }

    if (exp < (NJS_DBL_EXPONENT_OFFSET + 31 + 53)) {
        v = (conv.u64 & NJS_DBL_SIGNIFICAND_MASK) | NJS_DBL_HIDDEN_BIT;
        v <<= (exp - NJS_DBL_EXPONENT_BIAS + 32);
        r = v >> 32;

        if (conv.u64 & NJS_DBL_SIGN_MASK) {
            r = -r;
        }

        return r;
    }

    /*
     * ES5.1: integer must be modulo 2^32.
     * The distance between larger doubles
     * (exp >= NJS_DBL_EXPONENT_OFFSET + 31 + 53) is a multiple of 2**32 => 0.
     * This also handles NaN and Inf.
     */

    return 0;
}


njs_inline uint32_t
njs_number_to_uint32(double num)
{
    return (uint32_t) njs_number_to_int32(num);
}


njs_inline uint16_t
njs_number_to_uint16(double num)
{
    return (uint16_t) njs_number_to_int32(num);
}


njs_inline uint64_t
njs_number_to_length(double num)
{
    if (isnan(num)) {
        return 0;
    }

    if (num > NJS_MAX_LENGTH) {
        return NJS_MAX_LENGTH;

    } else if (num < 0.0) {
        return 0;
    }

    return (uint64_t) num;
}


njs_inline njs_int_t
njs_char_to_hex(u_char c)
{
    c |= 0x20;

    /* Values less than '0' become >= 208. */
    c = c - '0';

    if (c > 9) {
        /* Values less than 'a' become >= 159. */
        c = c - ('a' - '0');

        if (njs_slow_path(c > 5)) {
            return -1;
        }

        c += 10;
    }

    return c;
}


njs_inline njs_int_t
njs_uint32_to_string(njs_vm_t *vm, njs_value_t *value, uint32_t u32)
{
    size_t  size;
    u_char  *p;

    if (!(u32 & 0x80000000)) {
        value->type = NJS_STRING;
        value->data.truth = (u32 != 0);
        value->atom_id = njs_number_atom(u32);
        value->string.data = NULL;
        return NJS_OK;
    }

    p = njs_string_alloc(vm, value, 10, 10);
    if (njs_slow_path(p == NULL)) {
        return NJS_ERROR;
    }

    size = njs_u32toa((char *) p, u32);
    value->string.data->length = size;
    value->string.data->size = size;

    return NJS_OK;
}


extern const njs_object_type_init_t  njs_number_type_init;


#endif /* _NJS_NUMBER_H_INCLUDED_ */
