mirror of
https://github.com/bellard/quickjs.git
synced 2025-05-10 02:25:44 +08:00
added new dtoa library to print and parse float64 numbers. It is necessary to fix corner cases (e.g. radix != 10) and to have correct behavior regardless of the libc implementation.
This commit is contained in:
parent
9f1864a268
commit
993660621a
2
Makefile
2
Makefile
@ -227,7 +227,7 @@ endif
|
||||
|
||||
all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS)
|
||||
|
||||
QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o
|
||||
QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/dtoa.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o
|
||||
|
||||
QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS)
|
||||
|
||||
|
83
dtoa.h
Normal file
83
dtoa.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Tiny float64 printing and parsing library
|
||||
*
|
||||
* Copyright (c) 2024 Fabrice Bellard
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//#define JS_DTOA_DUMP_STATS
|
||||
|
||||
/* maximum number of digits for fixed and frac formats */
|
||||
#define JS_DTOA_MAX_DIGITS 101
|
||||
|
||||
/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */
|
||||
/* use as many digits as necessary */
|
||||
#define JS_DTOA_FORMAT_FREE (0 << 0)
|
||||
/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */
|
||||
#define JS_DTOA_FORMAT_FIXED (1 << 0)
|
||||
/* force fractional format: [-]dd.dd with n_digits fractional digits.
|
||||
0 <= n_digits <= JS_DTOA_MAX_DIGITS */
|
||||
#define JS_DTOA_FORMAT_FRAC (2 << 0)
|
||||
#define JS_DTOA_FORMAT_MASK (3 << 0)
|
||||
|
||||
/* select exponential notation either in fixed or free format */
|
||||
#define JS_DTOA_EXP_AUTO (0 << 2)
|
||||
#define JS_DTOA_EXP_ENABLED (1 << 2)
|
||||
#define JS_DTOA_EXP_DISABLED (2 << 2)
|
||||
#define JS_DTOA_EXP_MASK (3 << 2)
|
||||
|
||||
#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */
|
||||
|
||||
/* only accepts integers (no dot, no exponent) */
|
||||
#define JS_ATOD_INT_ONLY (1 << 0)
|
||||
/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
|
||||
#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1)
|
||||
/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */
|
||||
#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2)
|
||||
/* accept _ between digits as a digit separator */
|
||||
#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3)
|
||||
|
||||
typedef struct {
|
||||
uint64_t mem[37];
|
||||
} JSDTOATempMem;
|
||||
|
||||
typedef struct {
|
||||
uint64_t mem[27];
|
||||
} JSATODTempMem;
|
||||
|
||||
/* return a maximum bound of the string length */
|
||||
int js_dtoa_max_len(double d, int radix, int n_digits, int flags);
|
||||
/* return the string length */
|
||||
int js_dtoa(char *buf, double d, int radix, int n_digits, int flags,
|
||||
JSDTOATempMem *tmp_mem);
|
||||
double js_atod(const char *str, const char **pnext, int radix, int flags,
|
||||
JSATODTempMem *tmp_mem);
|
||||
|
||||
#ifdef JS_DTOA_DUMP_STATS
|
||||
void js_dtoa_dump_stats(void);
|
||||
#endif
|
||||
|
||||
/* additional exported functions */
|
||||
size_t u32toa(char *buf, uint32_t n);
|
||||
size_t i32toa(char *buf, int32_t n);
|
||||
size_t u64toa(char *buf, uint64_t n);
|
||||
size_t i64toa(char *buf, int64_t n);
|
||||
size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix);
|
||||
size_t i64toa_radix(char *buf, int64_t n, unsigned int radix);
|
480
quickjs.c
480
quickjs.c
@ -45,6 +45,7 @@
|
||||
#include "quickjs.h"
|
||||
#include "libregexp.h"
|
||||
#include "libunicode.h"
|
||||
#include "dtoa.h"
|
||||
|
||||
#define OPTIMIZE 1
|
||||
#define SHORT_OPCODES 1
|
||||
@ -11129,7 +11130,9 @@ static JSBigInt *js_bigint_from_string(JSContext *ctx,
|
||||
/* 2 <= base <= 36 */
|
||||
static char const digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
static char *u64toa(char *q, int64_t n, unsigned int base)
|
||||
/* special version going backwards */
|
||||
/* XXX: use dtoa.c */
|
||||
static char *js_u64toa(char *q, int64_t n, unsigned int base)
|
||||
{
|
||||
int digit;
|
||||
if (base == 10) {
|
||||
@ -11149,23 +11152,6 @@ static char *u64toa(char *q, int64_t n, unsigned int base)
|
||||
return q;
|
||||
}
|
||||
|
||||
static char *i64toa(char *buf_end, int64_t n, unsigned int base)
|
||||
{
|
||||
char *q = buf_end;
|
||||
int is_neg;
|
||||
|
||||
is_neg = 0;
|
||||
if (n < 0) {
|
||||
is_neg = 1;
|
||||
n = -n;
|
||||
}
|
||||
*--q = '\0';
|
||||
q = u64toa(q, n, base);
|
||||
if (is_neg)
|
||||
*--q = '-';
|
||||
return q;
|
||||
}
|
||||
|
||||
/* len >= 1. 2 <= radix <= 36 */
|
||||
static char *limb_to_a(char *q, js_limb_t n, unsigned int radix, int len)
|
||||
{
|
||||
@ -11226,13 +11212,13 @@ static const js_limb_t radix_base_table[JS_RADIX_MAX - 1] = {
|
||||
static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
|
||||
{
|
||||
if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) {
|
||||
char buf[66], *q;
|
||||
|
||||
q = i64toa(buf + sizeof(buf), JS_VALUE_GET_SHORT_BIG_INT(val), radix);
|
||||
return JS_NewString(ctx, q);
|
||||
char buf[66];
|
||||
int len;
|
||||
len = i64toa_radix(buf, JS_VALUE_GET_SHORT_BIG_INT(val), radix);
|
||||
return js_new_string8(ctx, (const uint8_t *)buf, len);
|
||||
} else {
|
||||
JSBigInt *r, *tmp = NULL;
|
||||
char *buf, *q;
|
||||
char *buf, *q, *buf_end;
|
||||
int is_neg, n_bits, log2_radix, n_digits;
|
||||
BOOL is_binary_radix;
|
||||
JSValue res;
|
||||
@ -11241,7 +11227,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
|
||||
r = JS_VALUE_GET_PTR(val);
|
||||
if (r->len == 1 && r->tab[0] == 0) {
|
||||
/* '0' case */
|
||||
return JS_NewString(ctx, "0");
|
||||
return js_new_string8(ctx, (const uint8_t *)"0", 1);
|
||||
}
|
||||
is_binary_radix = ((radix & (radix - 1)) == 0);
|
||||
is_neg = js_bigint_sign(r);
|
||||
@ -11271,6 +11257,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
|
||||
}
|
||||
q = buf + n_digits + is_neg + 1;
|
||||
*--q = '\0';
|
||||
buf_end = q;
|
||||
if (!is_binary_radix) {
|
||||
int len;
|
||||
js_limb_t radix_base, v;
|
||||
@ -11283,7 +11270,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
|
||||
if (len == 1 && r->tab[0] < radix_base) {
|
||||
v = r->tab[0];
|
||||
if (v != 0) {
|
||||
q = u64toa(q, v, radix);
|
||||
q = js_u64toa(q, v, radix);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
@ -11313,7 +11300,7 @@ static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix)
|
||||
if (is_neg)
|
||||
*--q = '-';
|
||||
js_free(ctx, tmp);
|
||||
res = JS_NewString(ctx, q);
|
||||
res = js_new_string8(ctx, (const uint8_t *)q, buf_end - q);
|
||||
js_free(ctx, buf);
|
||||
return res;
|
||||
}
|
||||
@ -11333,59 +11320,6 @@ static JSValue JS_CompactBigInt(JSContext *ctx, JSBigInt *p)
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX: remove */
|
||||
static double js_strtod(const char *str, int radix, BOOL is_float)
|
||||
{
|
||||
double d;
|
||||
int c;
|
||||
|
||||
if (!is_float || radix != 10) {
|
||||
const char *p = str;
|
||||
uint64_t n_max, n;
|
||||
int int_exp, is_neg;
|
||||
|
||||
is_neg = 0;
|
||||
if (*p == '-') {
|
||||
is_neg = 1;
|
||||
p++;
|
||||
}
|
||||
|
||||
/* skip leading zeros */
|
||||
while (*p == '0')
|
||||
p++;
|
||||
n = 0;
|
||||
if (radix == 10)
|
||||
n_max = ((uint64_t)-1 - 9) / 10; /* most common case */
|
||||
else
|
||||
n_max = ((uint64_t)-1 - (radix - 1)) / radix;
|
||||
/* XXX: could be more precise */
|
||||
int_exp = 0;
|
||||
while (*p != '\0') {
|
||||
c = to_digit((uint8_t)*p);
|
||||
if (c >= radix)
|
||||
break;
|
||||
if (n <= n_max) {
|
||||
n = n * radix + c;
|
||||
} else {
|
||||
if (radix == 10)
|
||||
goto strtod_case;
|
||||
int_exp++;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
d = n;
|
||||
if (int_exp != 0) {
|
||||
d *= pow(radix, int_exp);
|
||||
}
|
||||
if (is_neg)
|
||||
d = -d;
|
||||
} else {
|
||||
strtod_case:
|
||||
d = strtod(str, NULL);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
#define ATOD_INT_ONLY (1 << 0)
|
||||
/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
|
||||
#define ATOD_ACCEPT_BIN_OCT (1 << 2)
|
||||
@ -11404,6 +11338,7 @@ static double js_strtod(const char *str, int radix, BOOL is_float)
|
||||
|
||||
/* return an exception in case of memory error. Return JS_NAN if
|
||||
invalid syntax */
|
||||
/* XXX: directly use js_atod() */
|
||||
static JSValue js_atof(JSContext *ctx, const char *str, const char **pp,
|
||||
int radix, int flags)
|
||||
{
|
||||
@ -11415,7 +11350,8 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp,
|
||||
int i, j, len;
|
||||
BOOL buf_allocated = FALSE;
|
||||
JSValue val;
|
||||
|
||||
JSATODTempMem atod_mem;
|
||||
|
||||
/* optional separator between digits */
|
||||
sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256;
|
||||
has_legacy_octal = FALSE;
|
||||
@ -11556,7 +11492,8 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp,
|
||||
case ATOD_TYPE_FLOAT64:
|
||||
{
|
||||
double d;
|
||||
d = js_strtod(buf, radix, is_float);
|
||||
d = js_atod(buf,NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY,
|
||||
&atod_mem);
|
||||
/* return int or float64 */
|
||||
val = JS_NewFloat64(ctx, d);
|
||||
}
|
||||
@ -12211,320 +12148,29 @@ static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val)
|
||||
return js_bigint_to_string1(ctx, val, 10);
|
||||
}
|
||||
|
||||
/* buf1 contains the printf result */
|
||||
static void js_ecvt1(double d, int n_digits, int *decpt, int *sign, char *buf,
|
||||
int rounding_mode, char *buf1, int buf1_size)
|
||||
static JSValue js_dtoa2(JSContext *ctx,
|
||||
double d, int radix, int n_digits, int flags)
|
||||
{
|
||||
if (rounding_mode != FE_TONEAREST)
|
||||
fesetround(rounding_mode);
|
||||
snprintf(buf1, buf1_size, "%+.*e", n_digits - 1, d);
|
||||
if (rounding_mode != FE_TONEAREST)
|
||||
fesetround(FE_TONEAREST);
|
||||
*sign = (buf1[0] == '-');
|
||||
/* mantissa */
|
||||
buf[0] = buf1[1];
|
||||
if (n_digits > 1)
|
||||
memcpy(buf + 1, buf1 + 3, n_digits - 1);
|
||||
buf[n_digits] = '\0';
|
||||
/* exponent */
|
||||
*decpt = atoi(buf1 + n_digits + 2 + (n_digits > 1)) + 1;
|
||||
}
|
||||
|
||||
/* maximum buffer size for js_dtoa */
|
||||
#define JS_DTOA_BUF_SIZE 128
|
||||
|
||||
/* needed because ecvt usually limits the number of digits to
|
||||
17. Return the number of digits. */
|
||||
static int js_ecvt(double d, int n_digits, int *decpt, int *sign, char *buf,
|
||||
BOOL is_fixed)
|
||||
{
|
||||
int rounding_mode;
|
||||
char buf_tmp[JS_DTOA_BUF_SIZE];
|
||||
|
||||
if (!is_fixed) {
|
||||
unsigned int n_digits_min, n_digits_max;
|
||||
/* find the minimum amount of digits (XXX: inefficient but simple) */
|
||||
n_digits_min = 1;
|
||||
n_digits_max = 17;
|
||||
while (n_digits_min < n_digits_max) {
|
||||
n_digits = (n_digits_min + n_digits_max) / 2;
|
||||
js_ecvt1(d, n_digits, decpt, sign, buf, FE_TONEAREST,
|
||||
buf_tmp, sizeof(buf_tmp));
|
||||
if (strtod(buf_tmp, NULL) == d) {
|
||||
/* no need to keep the trailing zeros */
|
||||
while (n_digits >= 2 && buf[n_digits - 1] == '0')
|
||||
n_digits--;
|
||||
n_digits_max = n_digits;
|
||||
} else {
|
||||
n_digits_min = n_digits + 1;
|
||||
}
|
||||
}
|
||||
n_digits = n_digits_max;
|
||||
rounding_mode = FE_TONEAREST;
|
||||
char static_buf[128], *buf, *tmp_buf;
|
||||
int len, len_max;
|
||||
JSValue res;
|
||||
JSDTOATempMem dtoa_mem;
|
||||
len_max = js_dtoa_max_len(d, radix, n_digits, flags);
|
||||
|
||||
/* longer buffer may be used if radix != 10 */
|
||||
if (len_max > sizeof(static_buf) - 1) {
|
||||
tmp_buf = js_malloc(ctx, len_max + 1);
|
||||
if (!tmp_buf)
|
||||
return JS_EXCEPTION;
|
||||
buf = tmp_buf;
|
||||
} else {
|
||||
rounding_mode = FE_TONEAREST;
|
||||
#ifdef CONFIG_PRINTF_RNDN
|
||||
{
|
||||
char buf1[JS_DTOA_BUF_SIZE], buf2[JS_DTOA_BUF_SIZE];
|
||||
int decpt1, sign1, decpt2, sign2;
|
||||
/* The JS rounding is specified as round to nearest ties away
|
||||
from zero (RNDNA), but in printf the "ties" case is not
|
||||
specified (for example it is RNDN for glibc, RNDNA for
|
||||
Windows), so we must round manually. */
|
||||
js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_TONEAREST,
|
||||
buf_tmp, sizeof(buf_tmp));
|
||||
/* XXX: could use 2 digits to reduce the average running time */
|
||||
if (buf1[n_digits] == '5') {
|
||||
js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_DOWNWARD,
|
||||
buf_tmp, sizeof(buf_tmp));
|
||||
js_ecvt1(d, n_digits + 1, &decpt2, &sign2, buf2, FE_UPWARD,
|
||||
buf_tmp, sizeof(buf_tmp));
|
||||
if (memcmp(buf1, buf2, n_digits + 1) == 0 && decpt1 == decpt2) {
|
||||
/* exact result: round away from zero */
|
||||
if (sign1)
|
||||
rounding_mode = FE_DOWNWARD;
|
||||
else
|
||||
rounding_mode = FE_UPWARD;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PRINTF_RNDN */
|
||||
tmp_buf = NULL;
|
||||
buf = static_buf;
|
||||
}
|
||||
js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode,
|
||||
buf_tmp, sizeof(buf_tmp));
|
||||
return n_digits;
|
||||
}
|
||||
|
||||
static int js_fcvt1(char (*buf)[JS_DTOA_BUF_SIZE], double d, int n_digits,
|
||||
int rounding_mode)
|
||||
{
|
||||
int n;
|
||||
if (rounding_mode != FE_TONEAREST)
|
||||
fesetround(rounding_mode);
|
||||
n = snprintf(*buf, sizeof(*buf), "%.*f", n_digits, d);
|
||||
if (rounding_mode != FE_TONEAREST)
|
||||
fesetround(FE_TONEAREST);
|
||||
assert(n < sizeof(*buf));
|
||||
return n;
|
||||
}
|
||||
|
||||
static void js_fcvt(char (*buf)[JS_DTOA_BUF_SIZE], double d, int n_digits)
|
||||
{
|
||||
int rounding_mode;
|
||||
rounding_mode = FE_TONEAREST;
|
||||
#ifdef CONFIG_PRINTF_RNDN
|
||||
{
|
||||
int n1, n2;
|
||||
char buf1[JS_DTOA_BUF_SIZE];
|
||||
char buf2[JS_DTOA_BUF_SIZE];
|
||||
|
||||
/* The JS rounding is specified as round to nearest ties away from
|
||||
zero (RNDNA), but in printf the "ties" case is not specified
|
||||
(for example it is RNDN for glibc, RNDNA for Windows), so we
|
||||
must round manually. */
|
||||
n1 = js_fcvt1(&buf1, d, n_digits + 1, FE_TONEAREST);
|
||||
rounding_mode = FE_TONEAREST;
|
||||
/* XXX: could use 2 digits to reduce the average running time */
|
||||
if (buf1[n1 - 1] == '5') {
|
||||
n1 = js_fcvt1(&buf1, d, n_digits + 1, FE_DOWNWARD);
|
||||
n2 = js_fcvt1(&buf2, d, n_digits + 1, FE_UPWARD);
|
||||
if (n1 == n2 && memcmp(buf1, buf2, n1) == 0) {
|
||||
/* exact result: round away from zero */
|
||||
if (buf1[0] == '-')
|
||||
rounding_mode = FE_DOWNWARD;
|
||||
else
|
||||
rounding_mode = FE_UPWARD;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PRINTF_RNDN */
|
||||
js_fcvt1(buf, d, n_digits, rounding_mode);
|
||||
}
|
||||
|
||||
/* radix != 10 is only supported with flags = JS_DTOA_VAR_FORMAT */
|
||||
/* use as many digits as necessary */
|
||||
#define JS_DTOA_VAR_FORMAT (0 << 0)
|
||||
/* use n_digits significant digits (1 <= n_digits <= 101) */
|
||||
#define JS_DTOA_FIXED_FORMAT (1 << 0)
|
||||
/* force fractional format: [-]dd.dd with n_digits fractional digits */
|
||||
#define JS_DTOA_FRAC_FORMAT (2 << 0)
|
||||
/* force exponential notation either in fixed or variable format */
|
||||
#define JS_DTOA_FORCE_EXP (1 << 2)
|
||||
|
||||
/* XXX: slow and maybe not fully correct. Use libbf when it is fast enough.
|
||||
XXX: radix != 10 is only supported for small integers
|
||||
*/
|
||||
static void js_dtoa1(char (*buf)[JS_DTOA_BUF_SIZE], double d,
|
||||
int radix, int n_digits, int flags)
|
||||
{
|
||||
char *q;
|
||||
|
||||
if (!isfinite(d)) {
|
||||
if (isnan(d)) {
|
||||
pstrcpy(*buf, sizeof(*buf), "NaN");
|
||||
} else if (d < 0) {
|
||||
pstrcpy(*buf, sizeof(*buf), "-Infinity");
|
||||
} else {
|
||||
pstrcpy(*buf, sizeof(*buf), "Infinity");
|
||||
}
|
||||
} else if (flags == JS_DTOA_VAR_FORMAT) {
|
||||
int64_t i64;
|
||||
char buf1[70], *ptr;
|
||||
if (d > (double)MAX_SAFE_INTEGER || d < (double)-MAX_SAFE_INTEGER)
|
||||
goto generic_conv;
|
||||
i64 = (int64_t)d;
|
||||
if (d != i64)
|
||||
goto generic_conv;
|
||||
/* fast path for integers */
|
||||
ptr = i64toa(buf1 + sizeof(buf1), i64, radix);
|
||||
pstrcpy(*buf, sizeof(*buf), ptr);
|
||||
} else {
|
||||
if (d == 0.0)
|
||||
d = 0.0; /* convert -0 to 0 */
|
||||
if (flags == JS_DTOA_FRAC_FORMAT) {
|
||||
js_fcvt(buf, d, n_digits);
|
||||
} else {
|
||||
char buf1[JS_DTOA_BUF_SIZE];
|
||||
int sign, decpt, k, n, i, p, n_max;
|
||||
BOOL is_fixed;
|
||||
generic_conv:
|
||||
is_fixed = ((flags & 3) == JS_DTOA_FIXED_FORMAT);
|
||||
if (is_fixed) {
|
||||
n_max = n_digits;
|
||||
} else {
|
||||
n_max = 21;
|
||||
}
|
||||
/* the number has k digits (k >= 1) */
|
||||
k = js_ecvt(d, n_digits, &decpt, &sign, buf1, is_fixed);
|
||||
n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */
|
||||
q = *buf;
|
||||
if (sign)
|
||||
*q++ = '-';
|
||||
if (flags & JS_DTOA_FORCE_EXP)
|
||||
goto force_exp;
|
||||
if (n >= 1 && n <= n_max) {
|
||||
if (k <= n) {
|
||||
memcpy(q, buf1, k);
|
||||
q += k;
|
||||
for(i = 0; i < (n - k); i++)
|
||||
*q++ = '0';
|
||||
*q = '\0';
|
||||
} else {
|
||||
/* k > n */
|
||||
memcpy(q, buf1, n);
|
||||
q += n;
|
||||
*q++ = '.';
|
||||
for(i = 0; i < (k - n); i++)
|
||||
*q++ = buf1[n + i];
|
||||
*q = '\0';
|
||||
}
|
||||
} else if (n >= -5 && n <= 0) {
|
||||
*q++ = '0';
|
||||
*q++ = '.';
|
||||
for(i = 0; i < -n; i++)
|
||||
*q++ = '0';
|
||||
memcpy(q, buf1, k);
|
||||
q += k;
|
||||
*q = '\0';
|
||||
} else {
|
||||
force_exp:
|
||||
/* exponential notation */
|
||||
*q++ = buf1[0];
|
||||
if (k > 1) {
|
||||
*q++ = '.';
|
||||
for(i = 1; i < k; i++)
|
||||
*q++ = buf1[i];
|
||||
}
|
||||
*q++ = 'e';
|
||||
p = n - 1;
|
||||
if (p >= 0)
|
||||
*q++ = '+';
|
||||
snprintf(q, *buf + sizeof(*buf) - q, "%d", p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue js_dtoa(JSContext *ctx,
|
||||
double d, int radix, int n_digits, int flags)
|
||||
{
|
||||
char buf[JS_DTOA_BUF_SIZE];
|
||||
js_dtoa1(&buf, d, radix, n_digits, flags);
|
||||
return JS_NewString(ctx, buf);
|
||||
}
|
||||
|
||||
static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
|
||||
{
|
||||
char buf[2200], *ptr, *ptr2;
|
||||
/* d is finite */
|
||||
int sign = d < 0;
|
||||
int digit;
|
||||
double frac, d0;
|
||||
int64_t n0 = 0;
|
||||
d = fabs(d);
|
||||
d0 = trunc(d);
|
||||
frac = d - d0;
|
||||
ptr = buf + 1100;
|
||||
*ptr = '\0';
|
||||
if (d0 <= MAX_SAFE_INTEGER) {
|
||||
int64_t n = n0 = (int64_t)d0;
|
||||
while (n >= radix) {
|
||||
digit = n % radix;
|
||||
n = n / radix;
|
||||
*--ptr = digits[digit];
|
||||
}
|
||||
*--ptr = digits[(int)n];
|
||||
} else {
|
||||
/* no decimals */
|
||||
while (d0 >= radix) {
|
||||
digit = fmod(d0, radix);
|
||||
d0 = trunc(d0 / radix);
|
||||
if (d0 >= MAX_SAFE_INTEGER)
|
||||
digit = 0;
|
||||
*--ptr = digits[digit];
|
||||
}
|
||||
*--ptr = digits[(int)d0];
|
||||
goto done;
|
||||
}
|
||||
if (frac != 0) {
|
||||
double log2_radix = log2(radix);
|
||||
double prec = 1023 + 51; // handle subnormals
|
||||
ptr2 = buf + 1100;
|
||||
*ptr2++ = '.';
|
||||
while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) {
|
||||
frac *= radix;
|
||||
digit = trunc(frac);
|
||||
frac -= digit;
|
||||
*ptr2++ = digits[digit];
|
||||
n0 = n0 * radix + digit;
|
||||
prec -= log2_radix;
|
||||
}
|
||||
*ptr2 = '\0';
|
||||
if (frac * radix >= radix / 2) {
|
||||
char nine = digits[radix - 1];
|
||||
// round to closest
|
||||
while (ptr2[-1] == nine)
|
||||
*--ptr2 = '\0';
|
||||
if (ptr2[-1] == '.') {
|
||||
*--ptr2 = '\0';
|
||||
while (ptr2[-1] == nine)
|
||||
*--ptr2 = '0';
|
||||
}
|
||||
if (ptr2 - 1 == ptr)
|
||||
*--ptr = '1';
|
||||
else
|
||||
ptr2[-1] += 1;
|
||||
} else {
|
||||
while (ptr2[-1] == '0')
|
||||
*--ptr2 = '\0';
|
||||
if (ptr2[-1] == '.')
|
||||
*--ptr2 = '\0';
|
||||
}
|
||||
}
|
||||
done:
|
||||
ptr[-1] = '-';
|
||||
ptr -= sign;
|
||||
return JS_NewString(ctx, ptr);
|
||||
len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem);
|
||||
res = js_new_string8(ctx, (const uint8_t *)buf, len);
|
||||
js_free(ctx, tmp_buf);
|
||||
return res;
|
||||
}
|
||||
|
||||
JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey)
|
||||
@ -12538,9 +12184,12 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToProperty
|
||||
case JS_TAG_STRING:
|
||||
return JS_DupValue(ctx, val);
|
||||
case JS_TAG_INT:
|
||||
snprintf(buf, sizeof(buf), "%d", JS_VALUE_GET_INT(val));
|
||||
str = buf;
|
||||
goto new_string;
|
||||
{
|
||||
int len;
|
||||
len = i32toa(buf, JS_VALUE_GET_INT(val));
|
||||
return js_new_string8(ctx, (const uint8_t *)buf, len);
|
||||
}
|
||||
break;
|
||||
case JS_TAG_BOOL:
|
||||
return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ?
|
||||
JS_ATOM_true : JS_ATOM_false);
|
||||
@ -12571,8 +12220,8 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToProperty
|
||||
return JS_ThrowTypeError(ctx, "cannot convert symbol to string");
|
||||
}
|
||||
case JS_TAG_FLOAT64:
|
||||
return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0,
|
||||
JS_DTOA_VAR_FORMAT);
|
||||
return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0,
|
||||
JS_DTOA_FORMAT_FREE);
|
||||
case JS_TAG_SHORT_BIG_INT:
|
||||
case JS_TAG_BIG_INT:
|
||||
return js_bigint_to_string(ctx, val);
|
||||
@ -40444,7 +40093,7 @@ static JSValue js_number_toString(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv, int magic)
|
||||
{
|
||||
JSValue val;
|
||||
int base;
|
||||
int base, flags;
|
||||
double d;
|
||||
|
||||
val = js_thisNumberValue(ctx, this_val);
|
||||
@ -40458,16 +40107,17 @@ static JSValue js_number_toString(JSContext *ctx, JSValueConst this_val,
|
||||
goto fail;
|
||||
}
|
||||
if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) {
|
||||
char buf1[70], *ptr;
|
||||
ptr = i64toa(buf1 + sizeof(buf1), JS_VALUE_GET_INT(val), base);
|
||||
return JS_NewString(ctx, ptr);
|
||||
char buf1[70];
|
||||
int len;
|
||||
len = i64toa_radix(buf1, JS_VALUE_GET_INT(val), base);
|
||||
return js_new_string8(ctx, (const uint8_t *)buf1, len);
|
||||
}
|
||||
if (JS_ToFloat64Free(ctx, &d, val))
|
||||
return JS_EXCEPTION;
|
||||
if (base != 10 && isfinite(d)) {
|
||||
return js_dtoa_radix(ctx, d, base);
|
||||
}
|
||||
return js_dtoa(ctx, d, base, 0, JS_DTOA_VAR_FORMAT);
|
||||
flags = JS_DTOA_FORMAT_FREE;
|
||||
if (base != 10)
|
||||
flags |= JS_DTOA_EXP_DISABLED;
|
||||
return js_dtoa2(ctx, d, base, 0, flags);
|
||||
fail:
|
||||
JS_FreeValue(ctx, val);
|
||||
return JS_EXCEPTION;
|
||||
@ -40477,7 +40127,7 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
JSValue val;
|
||||
int f;
|
||||
int f, flags;
|
||||
double d;
|
||||
|
||||
val = js_thisNumberValue(ctx, this_val);
|
||||
@ -40489,11 +40139,11 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val,
|
||||
return JS_EXCEPTION;
|
||||
if (f < 0 || f > 100)
|
||||
return JS_ThrowRangeError(ctx, "invalid number of digits");
|
||||
if (fabs(d) >= 1e21) {
|
||||
return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d));
|
||||
} else {
|
||||
return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT);
|
||||
}
|
||||
if (fabs(d) >= 1e21)
|
||||
flags = JS_DTOA_FORMAT_FREE;
|
||||
else
|
||||
flags = JS_DTOA_FORMAT_FRAC;
|
||||
return js_dtoa2(ctx, d, 10, f, flags);
|
||||
}
|
||||
|
||||
static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val,
|
||||
@ -40514,15 +40164,15 @@ static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val,
|
||||
return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d));
|
||||
}
|
||||
if (JS_IsUndefined(argv[0])) {
|
||||
flags = 0;
|
||||
flags = JS_DTOA_FORMAT_FREE;
|
||||
f = 0;
|
||||
} else {
|
||||
if (f < 0 || f > 100)
|
||||
return JS_ThrowRangeError(ctx, "invalid number of digits");
|
||||
f++;
|
||||
flags = JS_DTOA_FIXED_FORMAT;
|
||||
flags = JS_DTOA_FORMAT_FIXED;
|
||||
}
|
||||
return js_dtoa(ctx, d, 10, f, flags | JS_DTOA_FORCE_EXP);
|
||||
return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED);
|
||||
}
|
||||
|
||||
static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val,
|
||||
@ -40547,7 +40197,7 @@ static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val,
|
||||
}
|
||||
if (p < 1 || p > 100)
|
||||
return JS_ThrowRangeError(ctx, "invalid number of digits");
|
||||
return js_dtoa(ctx, d, 10, p, JS_DTOA_FIXED_FORMAT);
|
||||
return js_dtoa2(ctx, d, 10, p, JS_DTOA_FORMAT_FIXED);
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_number_proto_funcs[] = {
|
||||
|
@ -390,8 +390,12 @@ function test_number()
|
||||
assert((-25).toExponential(0), "-3e+1");
|
||||
assert((2.5).toPrecision(1), "3");
|
||||
assert((-2.5).toPrecision(1), "-3");
|
||||
assert((25).toPrecision(1) === "3e+1");
|
||||
assert((1.125).toFixed(2), "1.13");
|
||||
assert((-1.125).toFixed(2), "-1.13");
|
||||
|
||||
assert((1.3).toString(7), "1.2046204620462046205");
|
||||
assert((1.3).toString(35), "1.ahhhhhhhhhm");
|
||||
}
|
||||
|
||||
function test_eval2()
|
||||
|
Loading…
x
Reference in New Issue
Block a user