From: Artem S. Povalyukhin Date: Sat, 9 Nov 2019 22:35:02 +0000 (+0300) Subject: Fixed Math.round() according to the specification. X-Git-Tag: 0.3.7~8 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/%7B@url%7D?a=commitdiff_plain;h=7d66b663c0739709a32f30ac0c91df5786511320;p=njs.git Fixed Math.round() according to the specification. This closes #246 issue on Github. --- diff --git a/src/njs_diyfp.h b/src/njs_diyfp.h index bcfa6384..3a91c432 100644 --- a/src/njs_diyfp.h +++ b/src/njs_diyfp.h @@ -18,25 +18,34 @@ typedef struct { } njs_diyfp_t; +typedef union { + double d; + uint64_t u64; +} njs_diyfp_conv_t; + + #define njs_diyfp(_s, _e) (njs_diyfp_t) \ { .significand = (_s), .exp = (_e) } #define njs_uint64(h, l) (((uint64_t) (h) << 32) + (l)) #define NJS_DBL_SIGNIFICAND_SIZE 52 -#define NJS_DBL_EXPONENT_BIAS (0x3FF + NJS_DBL_SIGNIFICAND_SIZE) +#define NJS_DBL_EXPONENT_OFFSET ((int64_t) 0x3ff) +#define NJS_DBL_EXPONENT_BIAS (NJS_DBL_EXPONENT_OFFSET \ + + NJS_DBL_SIGNIFICAND_SIZE) #define NJS_DBL_EXPONENT_MIN (-NJS_DBL_EXPONENT_BIAS) -#define NJS_DBL_EXPONENT_MAX (0x7FF - NJS_DBL_EXPONENT_BIAS) +#define NJS_DBL_EXPONENT_MAX (0x7ff - NJS_DBL_EXPONENT_BIAS) #define NJS_DBL_EXPONENT_DENORMAL (-NJS_DBL_EXPONENT_BIAS + 1) #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) #define NJS_DIYFP_SIGNIFICAND_SIZE 64 #define NJS_SIGNIFICAND_SIZE 53 -#define NJS_SIGNIFICAND_SHIFT (NJS_DIYFP_SIGNIFICAND_SIZE \ +#define NJS_SIGNIFICAND_SHIFT (NJS_DIYFP_SIGNIFICAND_SIZE \ - NJS_DBL_SIGNIFICAND_SIZE) #define NJS_DECIMAL_EXPONENT_OFF 348 @@ -78,13 +87,9 @@ njs_d2diyfp(double d) njs_inline double njs_diyfp2d(njs_diyfp_t v) { - int exp; - uint64_t significand, biased_exp; - - union { - double d; - uint64_t u64; - } u; + int exp; + uint64_t significand, biased_exp; + njs_diyfp_conv_t conv; exp = v.exp; significand = v.significand; @@ -118,10 +123,10 @@ njs_diyfp2d(njs_diyfp_t v) biased_exp = (uint64_t) (exp + NJS_DBL_EXPONENT_BIAS); } - u.u64 = (significand & NJS_DBL_SIGNIFICAND_MASK) - | (biased_exp << NJS_DBL_SIGNIFICAND_SIZE); + conv.u64 = (significand & NJS_DBL_SIGNIFICAND_MASK) + | (biased_exp << NJS_DBL_SIGNIFICAND_SIZE); - return u.d; + return conv.d; } diff --git a/src/njs_main.h b/src/njs_main.h index cd2c9663..7ecb8b7d 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/src/njs_math.c b/src/njs_math.c index 42201ff7..edeb1422 100644 --- a/src/njs_math.c +++ b/src/njs_math.c @@ -746,21 +746,62 @@ static njs_int_t njs_object_math_round(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { - njs_int_t ret; + uint8_t sign; + uint64_t one, fraction_mask; + njs_int_t ret, biased_exp; + njs_diyfp_conv_t conv; if (njs_slow_path(nargs < 2)) { njs_set_number(&vm->retval, NAN); return NJS_OK; } - if (njs_slow_path(!njs_is_number(&args[1]))) { + if (njs_slow_path(!njs_is_numeric(&args[1]))) { ret = njs_value_to_numeric(vm, &args[1], &args[1]); if (njs_slow_path(ret != NJS_OK)) { return ret; } } - njs_set_number(&vm->retval, round(njs_number(&args[1]))); + conv.d = njs_number(&args[1]); + biased_exp = (conv.u64 & NJS_DBL_EXPONENT_MASK) >> NJS_DBL_SIGNIFICAND_SIZE; + + if (biased_exp < NJS_DBL_EXPONENT_OFFSET) { + + /* |v| < 1. */ + + if (biased_exp == (NJS_DBL_EXPONENT_OFFSET - 1) + && conv.u64 != njs_uint64(0xbfe00000, 0x00000000)) + { + /* (|v| > 0.5 || v == 0.5) => +-1.0 */ + + conv.u64 = (conv.u64 & NJS_DBL_SIGN_MASK) + | (NJS_DBL_EXPONENT_OFFSET << NJS_DBL_SIGNIFICAND_SIZE); + + } else { + + /* (|v| < 0.5 || v == -0.5) => +-0. */ + + conv.u64 &= ((uint64_t) 1) << 63; + } + + } else if (biased_exp < NJS_DBL_EXPONENT_BIAS) { + + /* |v| <= 2^52 - 1 (largest safe integer). */ + + one = ((uint64_t) 1) << (NJS_DBL_EXPONENT_BIAS - biased_exp); + fraction_mask = one - 1; + + /* truncation. */ + + sign = conv.u64 >> 63; + conv.u64 += (one >> 1) - sign; + conv.u64 &= ~fraction_mask; + } + + /* |v| >= 2^52, Infinity and NaNs => v. */ + + njs_set_number(&vm->retval, conv.d); return NJS_OK; } diff --git a/src/njs_number.c b/src/njs_number.c index 48183b70..175fa212 100644 --- a/src/njs_number.c +++ b/src/njs_number.c @@ -6,7 +6,6 @@ #include -#include /* diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 4412f658..da46420f 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -12995,8 +12995,26 @@ static njs_unit_test_t njs_test[] = njs_str("-0") }, { njs_str("Math.round(-0.5)"), + njs_str("-0") }, + + { njs_str("Math.round(-0.50000000000000001)"), + njs_str("-0") }, + + { njs_str("Math.round(-0.5000000000000001)"), njs_str("-1") }, + { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round((2**32) + v) - 2**32)"), + njs_str("1,1,1") }, + + { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round(-(2**32) + v) + 2**32)"), + njs_str("1,1,1") }, + + { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round((2**32) - v) - 2**32)"), + njs_str("-1,0,0") }, + + { njs_str("[0.500001, 0.5000001,0.50000001].map((v)=>Math.round(-(2**32) - v) + 2**32)"), + njs_str("-1,0,0") }, + { njs_str("Math.sign(5)"), njs_str("1") },