Improve number to string conversions (#400)
integer conversions: - improve `u32toa_radix` and `u64toa_radix`, add `i32toa_radix` - use `i32toa_radix` for small ints in `js_number_toString` floating point conversions (`js_dtoa`): - complete rewrite with fewer calls to `snprintf` - remove `JS_DTOA_FORMAT`, define 4 possible modes for `js_dtoa` - remove the radix argument in `js_dtoa` - merge `js_dtoa1` into `js_dtoa` - add `js_dtoa_infinite` for non finite values - simplify sign handling - handle locale specific decimal point transparently helper function `js_fcvt`: - simplify `js_fcvt`, remove `js_fcvt1`, reduce overhead - round up manually instead of using `fesetround(FE_UPWARD)`. helper function `js_ecvt`: - document `js_ecvt` and `js_ecvt1` behavior - avoid redundant `js_ecvt1` calls in `js_ecvt` - fixed buffer contents, no buffer copies - simplify decimal point handling - round up manually instead of using `fesetround(FE_UPWARD)`. miscellaneous: - remove `CONFIG_PRINTF_RNDN`. This fixes some of the conversion errors on Windows. Updated the tests accordingly - this fixes a v8.sh bug on macOS: `0.5.toFixed(0)` used to produce `0` instead of `1` - add regression tests, update test_conv unit tests - add benchmarks for `toFixed`, `toPrecision` and `toExponential` number methods - benchmarks show all conversions are now 40 to 45% faster (M2)
This commit is contained in:
parent
139b51fe4b
commit
9e67b47c0d
6 changed files with 474 additions and 325 deletions
31
cutils.c
31
cutils.c
|
@ -575,6 +575,9 @@ overflow:
|
|||
/* 2 <= base <= 36 */
|
||||
char const digits36[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
#define USE_SPECIAL_RADIX_10 1 // special case base 10 radix conversions
|
||||
#define USE_SINGLE_CASE_FAST 1 // special case single digit numbers
|
||||
|
||||
/* using u32toa_shift variant */
|
||||
|
||||
#define gen_digit(buf, c) if (is_be()) \
|
||||
|
@ -613,11 +616,13 @@ size_t u07toa_shift(char dest[minimum_length(8)], uint32_t n, size_t len)
|
|||
|
||||
size_t u32toa(char buf[minimum_length(11)], uint32_t n)
|
||||
{
|
||||
#ifdef USE_SINGLE_CASE_FAST /* 10% */
|
||||
if (n < 10) {
|
||||
buf[0] = (char)('0' + n);
|
||||
buf[1] = '\0';
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
#define TEN_POW_7 10000000
|
||||
if (n >= TEN_POW_7) {
|
||||
uint32_t quo = n / TEN_POW_7;
|
||||
|
@ -679,6 +684,8 @@ static uint8_t const radix_shift[64] = {
|
|||
|
||||
size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned base)
|
||||
{
|
||||
int shift;
|
||||
|
||||
#ifdef USE_SPECIAL_RADIX_10
|
||||
if (likely(base == 10))
|
||||
return u32toa(buf, n);
|
||||
|
@ -688,13 +695,13 @@ size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned base)
|
|||
buf[1] = '\0';
|
||||
return 1;
|
||||
}
|
||||
int shift = radix_shift[base & 63];
|
||||
shift = radix_shift[base & 63];
|
||||
if (shift) {
|
||||
uint32_t mask = (1 << shift) - 1;
|
||||
size_t len = (32 - clz32(n) + shift - 1) / shift;
|
||||
size_t last = n & mask;
|
||||
n /= base;
|
||||
char *end = buf + len;
|
||||
n >>= shift;
|
||||
*end-- = '\0';
|
||||
*end-- = digits36[last];
|
||||
while (n >= base) {
|
||||
|
@ -728,11 +735,13 @@ size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned base)
|
|||
|
||||
size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned base)
|
||||
{
|
||||
int shift;
|
||||
|
||||
#ifdef USE_SPECIAL_RADIX_10
|
||||
if (likely(base == 10))
|
||||
return u64toa(buf, n);
|
||||
#endif
|
||||
int shift = radix_shift[base & 63];
|
||||
shift = radix_shift[base & 63];
|
||||
if (shift) {
|
||||
if (n < base) {
|
||||
buf[0] = digits36[n];
|
||||
|
@ -742,8 +751,8 @@ size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned base)
|
|||
uint64_t mask = (1 << shift) - 1;
|
||||
size_t len = (64 - clz64(n) + shift - 1) / shift;
|
||||
size_t last = n & mask;
|
||||
n /= base;
|
||||
char *end = buf + len;
|
||||
n >>= shift;
|
||||
*end-- = '\0';
|
||||
*end-- = digits36[last];
|
||||
while (n >= base) {
|
||||
|
@ -777,6 +786,15 @@ size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned base)
|
|||
}
|
||||
}
|
||||
|
||||
size_t i32toa_radix(char buf[minimum_length(34)], int32_t n, unsigned int base)
|
||||
{
|
||||
if (likely(n >= 0))
|
||||
return u32toa_radix(buf, n, base);
|
||||
|
||||
buf[0] = '-';
|
||||
return 1 + u32toa_radix(buf + 1, -(uint32_t)n, base);
|
||||
}
|
||||
|
||||
size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base)
|
||||
{
|
||||
if (likely(n >= 0))
|
||||
|
@ -786,6 +804,11 @@ size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base)
|
|||
return 1 + u64toa_radix(buf + 1, -(uint64_t)n, base);
|
||||
}
|
||||
|
||||
#undef gen_digit
|
||||
#undef TEN_POW_7
|
||||
#undef USE_SPECIAL_RADIX_10
|
||||
#undef USE_SINGLE_CASE_FAST
|
||||
|
||||
/*---- sorting with opaque argument ----*/
|
||||
|
||||
typedef void (*exchange_f)(void *a, void *b, size_t size);
|
||||
|
|
1
cutils.h
1
cutils.h
|
@ -463,6 +463,7 @@ size_t i32toa(char buf[minimum_length(12)], int32_t n);
|
|||
size_t u64toa(char buf[minimum_length(21)], uint64_t n);
|
||||
size_t i64toa(char buf[minimum_length(22)], int64_t n);
|
||||
size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned int base);
|
||||
size_t i32toa_radix(char buf[minimum_length(34)], int32_t n, unsigned base);
|
||||
size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned int base);
|
||||
size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base);
|
||||
|
||||
|
|
628
quickjs.c
628
quickjs.c
|
@ -58,11 +58,6 @@
|
|||
#define MALLOC_OVERHEAD 8
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(__wasi__)
|
||||
/* define it if printf uses the RNDN rounding mode instead of RNDNA */
|
||||
#define CONFIG_PRINTF_RNDN
|
||||
#endif
|
||||
|
||||
#if defined(__NEWLIB__)
|
||||
#define NO_TM_GMTOFF
|
||||
#endif
|
||||
|
@ -10965,262 +10960,354 @@ static JSValue js_bigint_to_string(JSContext *ctx, JSValue 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)
|
||||
/*---- floating point number to string conversions ----*/
|
||||
|
||||
/* JavaScript rounding is specified as round to nearest tie away
|
||||
from zero (RNDNA), but in `printf` the "ties" case is not
|
||||
specified (in most cases it is RNDN, round to nearest, tie to even),
|
||||
so we must round manually. We generate 2 extra places and make
|
||||
an extra call to snprintf if these are exactly '50'.
|
||||
We set the current rounding mode to FE_DOWNWARD to check if the
|
||||
last 2 places become '49'. If not, we must round up, which is
|
||||
performed in place using the string digits.
|
||||
|
||||
Note that we cannot rely on snprintf for rounding up:
|
||||
the code below fails on macOS for `0.5.toFixed(0)`: gives `0` expected `1`
|
||||
fesetround(FE_UPWARD);
|
||||
snprintf(dest, size, "%.*f", n_digits, d);
|
||||
fesetround(FE_TONEAREST);
|
||||
*/
|
||||
|
||||
/* `js_fcvt` minimum buffer length:
|
||||
- up to 21 digits in integral part
|
||||
- 1 potential decimal point
|
||||
- up to 102 decimals
|
||||
- 1 null terminator
|
||||
*/
|
||||
#define JS_FCVT_BUF_SIZE (21+1+102+1)
|
||||
|
||||
/* `js_ecvt` minimum buffer length:
|
||||
- 1 leading digit
|
||||
- 1 potential decimal point
|
||||
- up to 102 decimals
|
||||
- 5 exponent characters (from 'e-324' to 'e+308')
|
||||
- 1 null terminator
|
||||
*/
|
||||
#define JS_ECVT_BUF_SIZE (1+1+102+5+1)
|
||||
|
||||
/* `js_dtoa` minimum buffer length:
|
||||
- 8 byte prefix
|
||||
- either JS_FCVT_BUF_SIZE or JS_ECVT_BUF_SIZE
|
||||
- JS_FCVT_BUF_SIZE is larger than JS_ECVT_BUF_SIZE
|
||||
*/
|
||||
#define JS_DTOA_BUF_SIZE (8+JS_FCVT_BUF_SIZE)
|
||||
|
||||
/* `js_ecvt1`: compute the digits and decimal point spot for a double
|
||||
- `d` is finite, positive or zero
|
||||
- `n_digits` number of significant digits in range 1..103
|
||||
- `buf` receives the printf result
|
||||
- `buf` has a fixed format: n_digits with a decimal point at offset 1
|
||||
and exponent 'e{+/-}xx[x]' at offset n_digits+1
|
||||
Return n_digits
|
||||
Store the position of the decimal point into `*decpt`
|
||||
*/
|
||||
static int js_ecvt1(double d, int n_digits,
|
||||
char dest[minimum_length(JS_ECVT_BUF_SIZE)],
|
||||
size_t size, int *decpt)
|
||||
{
|
||||
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;
|
||||
} 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 */
|
||||
}
|
||||
js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode,
|
||||
buf_tmp, sizeof(buf_tmp));
|
||||
/* d is positive, ensure decimal point is always present */
|
||||
snprintf(dest, size, "%#.*e", n_digits - 1, d);
|
||||
/* dest contents:
|
||||
0: first digit
|
||||
1: '.' decimal point (locale specific)
|
||||
2..n_digits: (n_digits-1) additional digits
|
||||
n_digits+1: 'e' exponent mark
|
||||
n_digits+2..: exponent sign, value and null terminator
|
||||
*/
|
||||
/* extract the exponent (actually the position of the decimal point) */
|
||||
*decpt = 1 + atoi(dest + n_digits + 2);
|
||||
return n_digits;
|
||||
}
|
||||
|
||||
static size_t js_fcvt1(char (*buf)[JS_DTOA_BUF_SIZE], double d, int n_digits,
|
||||
int rounding_mode)
|
||||
/* `js_ecvt`: compute the digits and decimal point spot for a double
|
||||
with proper javascript rounding. We cannot use `ecvt` for multiple
|
||||
resasons: portability, because of the number of digits is typically
|
||||
limited to 17, finally because the default rounding is inadequate.
|
||||
`d` is finite and positive or zero.
|
||||
`n_digits` number of significant digits in range 1..101
|
||||
or 0 for automatic (only as many digits as necessary)
|
||||
Return the number of digits produced in `dest`.
|
||||
Store the position of the decimal point into `*decpt`
|
||||
*/
|
||||
static int js_ecvt(double d, int n_digits,
|
||||
char dest[minimum_length(JS_ECVT_BUF_SIZE)],
|
||||
size_t size, int *decpt)
|
||||
{
|
||||
size_t 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;
|
||||
}
|
||||
int i;
|
||||
|
||||
static size_t 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;
|
||||
if (n_digits == 0) {
|
||||
/* find the minimum number of digits (XXX: inefficient but simple) */
|
||||
// TODO(chqrlie) use direct method from quickjs-printf
|
||||
unsigned int n_digits_min = 1;
|
||||
unsigned int n_digits_max = 17;
|
||||
for (;;) {
|
||||
n_digits = (n_digits_min + n_digits_max) / 2;
|
||||
js_ecvt1(d, n_digits, dest, size, decpt);
|
||||
if (n_digits_min == n_digits_max)
|
||||
return n_digits;
|
||||
/* dest contents:
|
||||
0: first digit
|
||||
1: '.' decimal point (locale specific)
|
||||
2..n_digits: (n_digits-1) additional digits
|
||||
n_digits+1: 'e' exponent mark
|
||||
n_digits+2..: exponent sign, value and null terminator
|
||||
*/
|
||||
if (strtod(dest, NULL) == d) {
|
||||
unsigned int n0 = n_digits;
|
||||
/* enough digits */
|
||||
/* strip the trailing zeros */
|
||||
while (dest[n_digits] == '0')
|
||||
n_digits--;
|
||||
if (n_digits == n_digits_min)
|
||||
return n_digits;
|
||||
/* done if trailing zeros and not denormal or huge */
|
||||
if (n_digits < n0 && d > 3e-308 && d < 8e307)
|
||||
return n_digits;
|
||||
n_digits_max = n_digits;
|
||||
} else {
|
||||
/* need at least one more digit */
|
||||
n_digits_min = n_digits + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PRINTF_RNDN */
|
||||
return 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 size_t 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)) {
|
||||
memcpy(*buf, "NaN", sizeof "NaN");
|
||||
return sizeof("NaN") - 1;
|
||||
} else if (d < 0) {
|
||||
memcpy(*buf, "-Infinity", sizeof "-Infinity");
|
||||
return sizeof("-Infinity") - 1;
|
||||
} else {
|
||||
memcpy(*buf, "Infinity", sizeof "Infinity");
|
||||
return sizeof("Infinity") - 1;
|
||||
}
|
||||
} else if (flags == JS_DTOA_VAR_FORMAT) {
|
||||
int64_t i64;
|
||||
char buf1[72], *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 */
|
||||
return i64toa_radix(*buf, i64, radix);
|
||||
} else {
|
||||
if (d == 0.0)
|
||||
d = 0.0; /* convert -0 to 0 */
|
||||
if (flags == JS_DTOA_FRAC_FORMAT) {
|
||||
return 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++ = '+';
|
||||
q += snprintf(q, *buf + sizeof(*buf) - q, "%d", p);
|
||||
}
|
||||
return q - *buf;
|
||||
#if defined(FE_DOWNWARD) && defined(FE_TONEAREST)
|
||||
/* generate 2 extra digits: 99% chances to avoid 2 calls */
|
||||
js_ecvt1(d, n_digits + 2, dest, size, decpt);
|
||||
if (dest[n_digits + 1] < '5')
|
||||
return n_digits; /* truncate the 2 extra digits */
|
||||
if (dest[n_digits + 1] == '5' && dest[n_digits + 2] == '0') {
|
||||
/* close to half-way: try rounding toward 0 */
|
||||
fesetround(FE_DOWNWARD);
|
||||
js_ecvt1(d, n_digits + 2, dest, size, decpt);
|
||||
fesetround(FE_TONEAREST);
|
||||
if (dest[n_digits + 1] < '5')
|
||||
return n_digits; /* truncate the 2 extra digits */
|
||||
}
|
||||
/* round up in the string */
|
||||
for(i = n_digits;; i--) {
|
||||
/* ignore the locale specific decimal point */
|
||||
if (is_digit(dest[i])) {
|
||||
if (dest[i]++ < '9')
|
||||
break;
|
||||
dest[i] = '0';
|
||||
if (i == 0) {
|
||||
dest[0] = '1';
|
||||
(*decpt)++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return n_digits; /* truncate the 2 extra digits */
|
||||
#else
|
||||
/* No disambiguation available, eg: __wasi__ targets */
|
||||
return js_ecvt1(d, n_digits, dest, size, decpt);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue js_dtoa(JSContext *ctx,
|
||||
double d, int radix, int n_digits, int flags)
|
||||
/* `js_fcvt`: convert a floating point value to %f format using RNDNA
|
||||
`d` is finite and positive or zero.
|
||||
`n_digits` number of decimal places in range 0..100
|
||||
Return the number of characters produced in `dest`.
|
||||
*/
|
||||
static size_t js_fcvt(double d, int n_digits,
|
||||
char dest[minimum_length(JS_FCVT_BUF_SIZE)], size_t size)
|
||||
{
|
||||
#if defined(FE_DOWNWARD) && defined(FE_TONEAREST)
|
||||
int i, n1, n2;
|
||||
/* generate 2 extra digits: 99% chances to avoid 2 calls */
|
||||
n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2;
|
||||
if (dest[n1] >= '5') {
|
||||
if (dest[n1] == '5' && dest[n1 + 1] == '0') {
|
||||
/* close to half-way: try rounding toward 0 */
|
||||
fesetround(FE_DOWNWARD);
|
||||
n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2;
|
||||
fesetround(FE_TONEAREST);
|
||||
}
|
||||
if (dest[n1] >= '5') { /* number should be rounded up */
|
||||
/* d is either exactly half way or greater: round the string manually */
|
||||
for (i = n1 - 1;; i--) {
|
||||
/* ignore the locale specific decimal point */
|
||||
if (is_digit(dest[i])) {
|
||||
if (dest[i]++ < '9')
|
||||
break;
|
||||
dest[i] = '0';
|
||||
if (i == 0) {
|
||||
dest[0] = '1';
|
||||
dest[n1] = '0';
|
||||
dest[n1 - n_digits - 1] = '0';
|
||||
dest[n1 - n_digits] = '.';
|
||||
n1++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* truncate the extra 2 digits and the decimal point if !n_digits */
|
||||
n1 -= !n_digits;
|
||||
//dest[n1] = '\0'; // optional
|
||||
return n1;
|
||||
#else
|
||||
/* No disambiguation available, eg: __wasi__ targets */
|
||||
return snprintf(dest, size, "%.*f", n_digits, d);
|
||||
#endif
|
||||
}
|
||||
|
||||
static JSValue js_dtoa_infinite(JSContext *ctx, double d)
|
||||
{
|
||||
// TODO(chqrlie) use atoms for NaN and Infinite?
|
||||
if (isnan(d))
|
||||
return js_new_string8(ctx, "NaN");
|
||||
if (d < 0)
|
||||
return js_new_string8(ctx, "-Infinity");
|
||||
else
|
||||
return js_new_string8(ctx, "Infinity");
|
||||
}
|
||||
|
||||
#define JS_DTOA_TOSTRING 0 /* use as many digits as necessary */
|
||||
#define JS_DTOA_EXPONENTIAL 1 /* use exponential notation either fixed or variable digits */
|
||||
#define JS_DTOA_FIXED 2 /* force fixed number of fractional digits */
|
||||
#define JS_DTOA_PRECISION 3 /* use n_digits significant digits (1 <= n_digits <= 101) */
|
||||
|
||||
/* `js_dtoa`: convert a floating point number to a string
|
||||
- `mode`: one of the 4 supported formats
|
||||
- `n_digits`: digit number according to mode
|
||||
- TOSTRING: 0 only. As many digits as necessary
|
||||
- EXPONENTIAL: 0 as many decimals as necessary
|
||||
- 1..101 number of significant digits
|
||||
- FIXED: 0..100 number of decimal places
|
||||
- PRECISION: 1..101 number of significant digits
|
||||
*/
|
||||
// XXX: should use libbf or quickjs-printf.
|
||||
static JSValue js_dtoa(JSContext *ctx, double d, int n_digits, int mode)
|
||||
{
|
||||
char buf[JS_DTOA_BUF_SIZE];
|
||||
size_t len = js_dtoa1(&buf, d, radix, n_digits, flags);
|
||||
return js_new_string8_len(ctx, buf, len);
|
||||
size_t len;
|
||||
char *start;
|
||||
int sign, decpt, exp, i, k, n, n_max;
|
||||
|
||||
if (!isfinite(d))
|
||||
return js_dtoa_infinite(ctx, d);
|
||||
|
||||
sign = (d < 0);
|
||||
start = buf + 8;
|
||||
d = fabs(d); /* also converts -0 to 0 */
|
||||
|
||||
if (mode != JS_DTOA_EXPONENTIAL && n_digits == 0) {
|
||||
/* fast path for exact integers in variable format:
|
||||
clip to MAX_SAFE_INTEGER because to ensure insignificant
|
||||
digits are generated as 0.
|
||||
used for JS_DTOA_TOSTRING and JS_DTOA_FIXED without decimals.
|
||||
*/
|
||||
if (d <= (double)MAX_SAFE_INTEGER) {
|
||||
uint64_t u64 = (uint64_t)d;
|
||||
if (d == u64) {
|
||||
len = u64toa(start, u64);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode == JS_DTOA_FIXED) {
|
||||
len = js_fcvt(d, n_digits, start, sizeof(buf) - 8);
|
||||
// TODO(chqrlie) patch the locale specific decimal point
|
||||
goto done;
|
||||
}
|
||||
|
||||
n_max = (n_digits > 0) ? n_digits : 21;
|
||||
/* the number has k digits (1 <= k <= n_max) */
|
||||
k = js_ecvt(d, n_digits, start, sizeof(buf) - 8, &decpt);
|
||||
/* buffer contents:
|
||||
0: first digit
|
||||
1: '.' decimal point
|
||||
2..k: (k-1) additional digits
|
||||
*/
|
||||
n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */
|
||||
if (mode != JS_DTOA_EXPONENTIAL) {
|
||||
/* mode is JS_DTOA_PRECISION or JS_DTOA_TOSTRING */
|
||||
if (n >= 1 && n <= n_max) {
|
||||
/* between 1 and n_max digits before the decimal point */
|
||||
if (k <= n) {
|
||||
/* all digits before the point, append zeros */
|
||||
start[1] = start[0];
|
||||
start++;
|
||||
for(i = k; i < n; i++)
|
||||
start[i] = '0';
|
||||
len = n;
|
||||
} else {
|
||||
/* k > n: move digits before the point */
|
||||
for(i = 1; i < n; i++)
|
||||
start[i] = start[i + 1];
|
||||
start[i] = '.';
|
||||
len = 1 + k;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
if (n >= -5 && n <= 0) {
|
||||
/* insert -n leading 0 decimals and a '0.' prefix */
|
||||
n = -n;
|
||||
start[1] = start[0];
|
||||
start -= n + 1;
|
||||
start[0] = '0';
|
||||
start[1] = '.';
|
||||
for(i = 0; i < n; i++)
|
||||
start[2 + i] = '0';
|
||||
len = 2 + k + n;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* exponential notation */
|
||||
exp = n - 1;
|
||||
/* count the digits and the decimal point if at least one decimal */
|
||||
len = k + (k > 1);
|
||||
start[1] = '.'; /* patch the locale specific decimal point */
|
||||
start[len] = 'e';
|
||||
start[len + 1] = '+';
|
||||
if (exp < 0) {
|
||||
start[len + 1] = '-';
|
||||
exp = -exp;
|
||||
}
|
||||
len += 2 + 1 + (exp > 9) + (exp > 99);
|
||||
for (i = len - 1; exp > 9;) {
|
||||
int quo = exp / 10;
|
||||
start[i--] = (char)('0' + exp % 10);
|
||||
exp = quo;
|
||||
}
|
||||
start[i] = (char)('0' + exp);
|
||||
|
||||
done:
|
||||
start[-1] = '-'; /* prepend the sign if negative */
|
||||
return js_new_string8_len(ctx, start - sign, len + sign);
|
||||
}
|
||||
|
||||
/* d is guaranteed to be finite */
|
||||
/* `js_dtoa_radix`: convert a floating point number using a specific base
|
||||
- `d` must be finite
|
||||
- `radix` must be in range 2..36
|
||||
*/
|
||||
static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
|
||||
{
|
||||
char buf[2200], *ptr, *ptr2, *ptr3;
|
||||
/* d is finite */
|
||||
int sign = d < 0;
|
||||
int digit;
|
||||
int sign, digit;
|
||||
double frac, d0;
|
||||
int64_t n0 = 0;
|
||||
int64_t n0;
|
||||
|
||||
if (!isfinite(d))
|
||||
return js_dtoa_infinite(ctx, d);
|
||||
|
||||
sign = (d < 0);
|
||||
d = fabs(d);
|
||||
d0 = trunc(d);
|
||||
n0 = 0;
|
||||
frac = d - d0;
|
||||
ptr2 = buf + 1100; /* ptr2 points to the end of the string */
|
||||
ptr = ptr2; /* ptr points to the beginning of the string */
|
||||
|
@ -11284,7 +11371,7 @@ static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
|
|||
ptr2[-1] = (ptr2[-1] == '9') ? 'a' : ptr2[-1] + 1;
|
||||
}
|
||||
} else {
|
||||
/* strip trailing fractional zeroes */
|
||||
/* strip trailing fractional zeros */
|
||||
while (ptr2[-1] == '0')
|
||||
ptr2--;
|
||||
/* strip the 'decimal' point if last */
|
||||
|
@ -11340,8 +11427,7 @@ JSValue JS_ToStringInternal(JSContext *ctx, JSValue val, BOOL is_ToPropertyKey)
|
|||
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_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 0, JS_DTOA_TOSTRING);
|
||||
case JS_TAG_BIG_INT:
|
||||
return js_bigint_to_string(ctx, val);
|
||||
default:
|
||||
|
@ -38961,7 +39047,7 @@ static int js_get_radix(JSContext *ctx, JSValue val)
|
|||
if (JS_ToInt32Sat(ctx, &radix, val))
|
||||
return -1;
|
||||
if (radix < 2 || radix > 36) {
|
||||
JS_ThrowRangeError(ctx, "radix must be between 2 and 36");
|
||||
JS_ThrowRangeError(ctx, "toString() radix argument must be between 2 and 36");
|
||||
return -1;
|
||||
}
|
||||
return radix;
|
||||
|
@ -38986,22 +39072,21 @@ static JSValue js_number_toString(JSContext *ctx, JSValue this_val,
|
|||
base = 10;
|
||||
} else {
|
||||
base = js_get_radix(ctx, argv[0]);
|
||||
if (base < 0)
|
||||
goto fail;
|
||||
if (base < 0) {
|
||||
JS_FreeValue(ctx, val);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
}
|
||||
if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) {
|
||||
size_t len = i64toa_radix(buf, JS_VALUE_GET_INT(val), base);
|
||||
size_t len = i32toa_radix(buf, JS_VALUE_GET_INT(val), base);
|
||||
return js_new_string8_len(ctx, buf, len);
|
||||
}
|
||||
if (JS_ToFloat64Free(ctx, &d, val))
|
||||
return JS_EXCEPTION;
|
||||
if (base != 10 && isfinite(d)) {
|
||||
if (base != 10)
|
||||
return js_dtoa_radix(ctx, d, base);
|
||||
}
|
||||
return js_dtoa(ctx, d, base, 0, JS_DTOA_VAR_FORMAT);
|
||||
fail:
|
||||
JS_FreeValue(ctx, val);
|
||||
return JS_EXCEPTION;
|
||||
|
||||
return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING);
|
||||
}
|
||||
|
||||
static JSValue js_number_toFixed(JSContext *ctx, JSValue this_val,
|
||||
|
@ -39019,13 +39104,13 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValue this_val,
|
|||
if (JS_ToInt32Sat(ctx, &f, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
if (f < 0 || f > 100) {
|
||||
return JS_ThrowRangeError(ctx, "%s() argument must be between 1 and 100",
|
||||
"toFixed");
|
||||
return JS_ThrowRangeError(ctx, "toFixed() digits argument must be between 0 and 100");
|
||||
}
|
||||
if (fabs(d) >= 1e21) {
|
||||
return JS_ToStringFree(ctx, js_float64(d));
|
||||
// use ToString(d)
|
||||
return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING);
|
||||
} else {
|
||||
return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT);
|
||||
return js_dtoa(ctx, d, f, JS_DTOA_FIXED);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39033,8 +39118,8 @@ static JSValue js_number_toExponential(JSContext *ctx, JSValue this_val,
|
|||
int argc, JSValue *argv)
|
||||
{
|
||||
JSValue val;
|
||||
int f, flags;
|
||||
double d;
|
||||
int f;
|
||||
|
||||
val = js_thisNumberValue(ctx, this_val);
|
||||
if (JS_IsException(val))
|
||||
|
@ -39043,21 +39128,15 @@ static JSValue js_number_toExponential(JSContext *ctx, JSValue this_val,
|
|||
return JS_EXCEPTION;
|
||||
if (JS_ToInt32Sat(ctx, &f, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
if (!isfinite(d)) {
|
||||
return JS_ToStringFree(ctx, js_float64(d));
|
||||
}
|
||||
if (JS_IsUndefined(argv[0])) {
|
||||
flags = 0;
|
||||
f = 0;
|
||||
} else {
|
||||
if (!isfinite(d))
|
||||
return js_dtoa_infinite(ctx, d);
|
||||
if (!JS_IsUndefined(argv[0])) {
|
||||
if (f < 0 || f > 100) {
|
||||
return JS_ThrowRangeError(ctx, "%s() argument must be between 1 and 100",
|
||||
"toExponential");
|
||||
return JS_ThrowRangeError(ctx, "toExponential() argument must be between 0 and 100");
|
||||
}
|
||||
f++;
|
||||
flags = JS_DTOA_FIXED_FORMAT;
|
||||
f += 1; /* number of significant digits between 1 and 101 */
|
||||
}
|
||||
return js_dtoa(ctx, d, 10, f, flags | JS_DTOA_FORCE_EXP);
|
||||
return js_dtoa(ctx, d, f, JS_DTOA_EXPONENTIAL);
|
||||
}
|
||||
|
||||
static JSValue js_number_toPrecision(JSContext *ctx, JSValue this_val,
|
||||
|
@ -39073,18 +39152,15 @@ static JSValue js_number_toPrecision(JSContext *ctx, JSValue this_val,
|
|||
if (JS_ToFloat64Free(ctx, &d, val))
|
||||
return JS_EXCEPTION;
|
||||
if (JS_IsUndefined(argv[0]))
|
||||
goto to_string;
|
||||
return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING);
|
||||
if (JS_ToInt32Sat(ctx, &p, argv[0]))
|
||||
return JS_EXCEPTION;
|
||||
if (!isfinite(d)) {
|
||||
to_string:
|
||||
return JS_ToStringFree(ctx, js_float64(d));
|
||||
}
|
||||
if (!isfinite(d))
|
||||
return js_dtoa_infinite(ctx, d);
|
||||
if (p < 1 || p > 100) {
|
||||
return JS_ThrowRangeError(ctx, "%s() argument must be between 1 and 100",
|
||||
"toPrecision");
|
||||
return JS_ThrowRangeError(ctx, "toPrecision() argument must be between 1 and 100");
|
||||
}
|
||||
return js_dtoa(ctx, d, 10, p, JS_DTOA_FIXED_FORMAT);
|
||||
return js_dtoa(ctx, d, p, JS_DTOA_PRECISION);
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_number_proto_funcs[] = {
|
||||
|
|
|
@ -919,6 +919,42 @@ function float_toString(n)
|
|||
return n * 3;
|
||||
}
|
||||
|
||||
function float_toFixed(n)
|
||||
{
|
||||
var s, r, j;
|
||||
r = 0;
|
||||
for(j = 0; j < n; j++) {
|
||||
s = (j % 10 + 0.1).toFixed(j % 16);
|
||||
s = (j + 0.1).toFixed(j % 16);
|
||||
s = (j * 12345678 + 0.1).toFixed(j % 16);
|
||||
}
|
||||
return n * 3;
|
||||
}
|
||||
|
||||
function float_toPrecision(n)
|
||||
{
|
||||
var s, r, j;
|
||||
r = 0;
|
||||
for(j = 0; j < n; j++) {
|
||||
s = (j % 10 + 0.1).toPrecision(j % 16 + 1);
|
||||
s = (j + 0.1).toPrecision(j % 16 + 1);
|
||||
s = (j * 12345678 + 0.1).toPrecision(j % 16 + 1);
|
||||
}
|
||||
return n * 3;
|
||||
}
|
||||
|
||||
function float_toExponential(n)
|
||||
{
|
||||
var s, r, j;
|
||||
r = 0;
|
||||
for(j = 0; j < n; j++) {
|
||||
s = (j % 10 + 0.1).toExponential(j % 16);
|
||||
s = (j + 0.1).toExponential(j % 16);
|
||||
s = (j * 12345678 + 0.1).toExponential(j % 16);
|
||||
}
|
||||
return n * 3;
|
||||
}
|
||||
|
||||
function string_to_int(n)
|
||||
{
|
||||
var s, r, j;
|
||||
|
@ -1014,11 +1050,14 @@ function main(argc, argv, g)
|
|||
int_toString,
|
||||
float_to_string,
|
||||
float_toString,
|
||||
float_toFixed,
|
||||
float_toPrecision,
|
||||
float_toExponential,
|
||||
string_to_int,
|
||||
string_to_float,
|
||||
];
|
||||
var tests = [];
|
||||
var i, j, n, f, name;
|
||||
var i, j, n, f, name, found;
|
||||
|
||||
if (typeof BigInt == "function") {
|
||||
/* BigInt test */
|
||||
|
@ -1045,14 +1084,14 @@ function main(argc, argv, g)
|
|||
sort_bench.array_size = +argv[i++];
|
||||
continue;
|
||||
}
|
||||
for (j = 0; j < test_list.length; j++) {
|
||||
for (j = 0, found = false; j < test_list.length; j++) {
|
||||
f = test_list[j];
|
||||
if (name === f.name) {
|
||||
if (f.name.startsWith(name)) {
|
||||
tests.push(f);
|
||||
break;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (j == test_list.length) {
|
||||
if (!found) {
|
||||
console.log("unknown benchmark: " + name);
|
||||
return 1;
|
||||
}
|
||||
|
@ -1080,6 +1119,9 @@ function main(argc, argv, g)
|
|||
save_result("microbench-new.txt", log_data);
|
||||
}
|
||||
|
||||
if (!scriptArgs)
|
||||
if (typeof scriptArgs === "undefined") {
|
||||
scriptArgs = [];
|
||||
if (typeof process.argv === "object")
|
||||
scriptArgs = process.argv.slice(1);
|
||||
}
|
||||
main(scriptArgs.length, scriptArgs, this);
|
||||
|
|
|
@ -434,18 +434,17 @@ function test_number()
|
|||
assert(Number.isNaN(Number("-")));
|
||||
assert(Number.isNaN(Number("\x00a")));
|
||||
|
||||
// TODO: Fix rounding errors on Windows/Cygwin.
|
||||
if (['win32', 'cygwin'].includes(os.platform)) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert((1-2**-53).toString(12), "0.bbbbbbbbbbbbbba");
|
||||
assert((1000000000000000128).toString(), "1000000000000000100");
|
||||
assert((1000000000000000128).toFixed(0), "1000000000000000128");
|
||||
assert((25).toExponential(0), "3e+1");
|
||||
assert((-25).toExponential(0), "-3e+1");
|
||||
assert((2.5).toPrecision(1), "3");
|
||||
assert((-2.5).toPrecision(1), "-3");
|
||||
assert((1.125).toFixed(2), "1.13");
|
||||
assert((-1.125).toFixed(2), "-1.13");
|
||||
assert((0.5).toFixed(0), "1");
|
||||
assert((-0.5).toFixed(0), "-1");
|
||||
}
|
||||
|
||||
function test_eval2()
|
||||
|
|
|
@ -864,6 +864,8 @@ static uint8_t const radix_shift[64] = {
|
|||
|
||||
size_t u32toa_radix_length(char buf[minimum_length(33)], uint32_t n, unsigned base)
|
||||
{
|
||||
int shift;
|
||||
|
||||
#ifdef USE_SPECIAL_RADIX_10
|
||||
if (likely(base == 10))
|
||||
return u32toa_length_loop(buf, n);
|
||||
|
@ -873,13 +875,13 @@ size_t u32toa_radix_length(char buf[minimum_length(33)], uint32_t n, unsigned ba
|
|||
buf[1] = '\0';
|
||||
return 1;
|
||||
}
|
||||
int shift = radix_shift[base & 63];
|
||||
shift = radix_shift[base & 63];
|
||||
if (shift) {
|
||||
uint32_t mask = (1 << shift) - 1;
|
||||
size_t len = (32 - clz32(n) + shift - 1) / shift;
|
||||
size_t last = n & mask;
|
||||
n /= base;
|
||||
char *end = buf + len;
|
||||
n >>= shift;
|
||||
*end-- = '\0';
|
||||
*end-- = digits36[last];
|
||||
while (n >= base) {
|
||||
|
@ -913,11 +915,13 @@ size_t u32toa_radix_length(char buf[minimum_length(33)], uint32_t n, unsigned ba
|
|||
|
||||
size_t u64toa_radix_length(char buf[minimum_length(65)], uint64_t n, unsigned base)
|
||||
{
|
||||
int shift;
|
||||
|
||||
#ifdef USE_SPECIAL_RADIX_10
|
||||
if (likely(base == 10))
|
||||
return u64toa_length_loop(buf, n);
|
||||
#endif
|
||||
int shift = radix_shift[base & 63];
|
||||
shift = radix_shift[base & 63];
|
||||
if (shift) {
|
||||
if (n < base) {
|
||||
buf[0] = digits36[n];
|
||||
|
@ -927,8 +931,8 @@ size_t u64toa_radix_length(char buf[minimum_length(65)], uint64_t n, unsigned ba
|
|||
uint64_t mask = (1 << shift) - 1;
|
||||
size_t len = (64 - clz64(n) + shift - 1) / shift;
|
||||
size_t last = n & mask;
|
||||
n /= base;
|
||||
char *end = buf + len;
|
||||
n >>= shift;
|
||||
*end-- = '\0';
|
||||
*end-- = digits36[last];
|
||||
while (n >= base) {
|
||||
|
@ -1511,6 +1515,9 @@ int main(int argc, char *argv[]) {
|
|||
clock_t times1[countof(impl1)][4][37];
|
||||
char buf[100];
|
||||
uint64_t bases = 0;
|
||||
#define set_base(bases, b) (*(bases) |= (1ULL << (b)))
|
||||
#define has_base(bases, b) ((bases) & (1ULL << (b)))
|
||||
#define single_base(bases) (!((bases) & ((bases) - 1)))
|
||||
int verbose = 0;
|
||||
int average = 1;
|
||||
int enabled = 3;
|
||||
|
@ -1521,14 +1528,13 @@ int main(int argc, char *argv[]) {
|
|||
for (int a = 1; a < argc; a++) {
|
||||
char *arg = argv[a];
|
||||
if (isdigit((unsigned char)*arg)) {
|
||||
verbose = 1;
|
||||
while (isdigit((unsigned char)*arg)) {
|
||||
int b1 = strtol(arg, &arg, 10);
|
||||
bases |= (1ULL << b1);
|
||||
set_base(&bases, b1);
|
||||
if (*arg == '-') {
|
||||
int b2 = strtol(arg, &arg, 10);
|
||||
int b2 = strtol(arg + 1, &arg, 10);
|
||||
while (++b1 <= b2)
|
||||
bases |= (1ULL << b1);
|
||||
set_base(&bases, b1);
|
||||
}
|
||||
if (*arg == ',') {
|
||||
arg++;
|
||||
|
@ -1539,10 +1545,6 @@ int main(int argc, char *argv[]) {
|
|||
fprintf(stderr, "invalid option syntax: %s\n", argv[a]);
|
||||
return 2;
|
||||
}
|
||||
if (!(bases & (bases - 1))) { /* single base */
|
||||
average = 0;
|
||||
verbose = 1;
|
||||
}
|
||||
continue;
|
||||
} else if (!strcmp(arg, "-t") || !strcmp(arg, "--terse")) {
|
||||
verbose = 0;
|
||||
|
@ -1578,14 +1580,19 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
if (!bases)
|
||||
bases = -1;
|
||||
if (bases & (bases - 1)) /* multiple bases */
|
||||
if (single_base(bases)) {
|
||||
average = 0;
|
||||
verbose = 1;
|
||||
} else {
|
||||
average = 1;
|
||||
}
|
||||
|
||||
int numvariant = 0;
|
||||
int numvariant1 = 0;
|
||||
int nerrors = 0;
|
||||
|
||||
if (bases & (1ULL << 10)) {
|
||||
/* Checking for correctness */
|
||||
if (has_base(bases, 10)) {
|
||||
for (size_t i = 0; i < countof(impl); i++) {
|
||||
unsigned base = 10;
|
||||
if (impl[i].enabled & enabled) {
|
||||
|
@ -1599,7 +1606,7 @@ int main(int argc, char *argv[]) {
|
|||
for (size_t i = 0; i < countof(impl1); i++) {
|
||||
if (impl1[i].enabled & enabled) {
|
||||
for (unsigned base = 2; base <= 36; base++) {
|
||||
if (bases & (1ULL << base)) {
|
||||
if (has_base(bases, base)) {
|
||||
CHECK(impl1[i], 1000, 0, 32, u32toa_radix(buf, x, base), strtoull(buf, NULL, base));
|
||||
CHECK(impl1[i], 1000, 1, 32, i32toa_radix(buf, x, base), strtoll(buf, NULL, base));
|
||||
CHECK(impl1[i], 1000, 0, 64, u64toa_radix(buf, x, base), strtoull(buf, NULL, base));
|
||||
|
@ -1611,13 +1618,14 @@ int main(int argc, char *argv[]) {
|
|||
if (nerrors)
|
||||
return 1;
|
||||
|
||||
if (bases & (1ULL << 10)) {
|
||||
/* Timing conversions */
|
||||
if (has_base(bases, 10)) {
|
||||
for (int rep = 0; rep < 100; rep++) {
|
||||
for (size_t i = 0; i < countof(impl); i++) {
|
||||
if (impl[i].enabled & enabled) {
|
||||
numvariant++;
|
||||
#ifdef TEST_SNPRINTF
|
||||
if (strstr(impl[i].name, "snprintf")) { // avoid function call overhead
|
||||
if (strstr(impl[i].name, "snprintf")) { // avoid wrapper overhead
|
||||
TIME(times[i][0], 1000, 0, 32, snprintf(buf, 11, "%"PRIu32, x));
|
||||
TIME(times[i][1], 1000, 1, 32, snprintf(buf, 12, "%"PRIi32, x));
|
||||
TIME(times[i][2], 1000, 0, 64, snprintf(buf, 21, "%"PRIu64, x));
|
||||
|
@ -1639,42 +1647,42 @@ int main(int argc, char *argv[]) {
|
|||
if (impl1[i].enabled & enabled) {
|
||||
numvariant1++;
|
||||
#ifdef TEST_SNPRINTF
|
||||
if (strstr(impl[i].name, "snprintf")) { // avoid function call overhead
|
||||
if (strstr(impl[i].name, "snprintf")) { // avoid wrapper overhead
|
||||
#ifdef PRIb32
|
||||
if (bases & (1ULL << 1)) {
|
||||
if (has_base(bases, 2)) {
|
||||
unsigned base = 2;
|
||||
TIME(times1[i][0][2], 1000, 0, 32, snprintf(buf, 33, "%"PRIb32, x));
|
||||
TIME(times1[i][0][base], 1000, 0, 32, snprintf(buf, 33, "%"PRIb32, x));
|
||||
TIME(times1[i][1][base], 1000, 1, 32, impl1[i].i32toa_radix(buf, x, base));
|
||||
TIME(times1[i][2][2], 1000, 0, 64, snprintf(buf, 65, "%"PRIb64, x));
|
||||
TIME(times1[i][2][base], 1000, 0, 64, snprintf(buf, 65, "%"PRIb64, x));
|
||||
TIME(times1[i][3][base], 1000, 1, 64, impl1[i].i64toa_radix(buf, x, base));
|
||||
}
|
||||
#endif
|
||||
if (bases & (1ULL << 8)) {
|
||||
if (has_base(bases, 8)) {
|
||||
unsigned base = 8;
|
||||
TIME(times1[i][0][8], 1000, 0, 32, snprintf(buf, 33, "%"PRIo32, x));
|
||||
TIME(times1[i][0][base], 1000, 0, 32, snprintf(buf, 33, "%"PRIo32, x));
|
||||
TIME(times1[i][1][base], 1000, 1, 32, impl1[i].i32toa_radix(buf, x, base));
|
||||
TIME(times1[i][2][8], 1000, 0, 64, snprintf(buf, 65, "%"PRIo64, x));
|
||||
TIME(times1[i][2][base], 1000, 0, 64, snprintf(buf, 65, "%"PRIo64, x));
|
||||
TIME(times1[i][3][base], 1000, 1, 64, impl1[i].i64toa_radix(buf, x, base));
|
||||
}
|
||||
if (bases & (1ULL << 10)) {
|
||||
if (has_base(bases, 10)) {
|
||||
unsigned base = 10;
|
||||
TIME(times1[i][0][10], 1000, 0, 32, snprintf(buf, 33, "%"PRIu32, x));
|
||||
TIME(times1[i][1][10], 1000, 1, 32, snprintf(buf, 34, "%"PRIi32, x));
|
||||
TIME(times1[i][2][10], 1000, 0, 64, snprintf(buf, 64, "%"PRIu64, x));
|
||||
TIME(times1[i][3][10], 1000, 1, 64, snprintf(buf, 65, "%"PRIi64, x));
|
||||
TIME(times1[i][0][base], 1000, 0, 32, snprintf(buf, 33, "%"PRIu32, x));
|
||||
TIME(times1[i][1][base], 1000, 1, 32, snprintf(buf, 34, "%"PRIi32, x));
|
||||
TIME(times1[i][2][base], 1000, 0, 64, snprintf(buf, 64, "%"PRIu64, x));
|
||||
TIME(times1[i][3][base], 1000, 1, 64, snprintf(buf, 65, "%"PRIi64, x));
|
||||
}
|
||||
if (bases & (1ULL << 16)) {
|
||||
if (has_base(bases, 16)) {
|
||||
unsigned base = 16;
|
||||
TIME(times1[i][0][16], 1000, 0, 32, snprintf(buf, 33, "%"PRIx32, x));
|
||||
TIME(times1[i][0][base], 1000, 0, 32, snprintf(buf, 33, "%"PRIx32, x));
|
||||
TIME(times1[i][1][base], 1000, 1, 32, impl1[i].i32toa_radix(buf, x, base));
|
||||
TIME(times1[i][2][16], 1000, 0, 64, snprintf(buf, 65, "%"PRIx64, x));
|
||||
TIME(times1[i][2][base], 1000, 0, 64, snprintf(buf, 65, "%"PRIx64, x));
|
||||
TIME(times1[i][3][base], 1000, 1, 64, impl1[i].i64toa_radix(buf, x, base));
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
for (unsigned base = 2; base <= 36; base++) {
|
||||
if (bases & (1ULL << base)) {
|
||||
if (has_base(bases, base)) {
|
||||
TIME(times1[i][0][base], 1000, 0, 32, impl1[i].u32toa_radix(buf, x, base));
|
||||
TIME(times1[i][1][base], 1000, 1, 32, impl1[i].i32toa_radix(buf, x, base));
|
||||
TIME(times1[i][2][base], 1000, 0, 64, impl1[i].u64toa_radix(buf, x, base));
|
||||
|
@ -1704,7 +1712,7 @@ int main(int argc, char *argv[]) {
|
|||
for (size_t i = 0; i < countof(impl1); i++) {
|
||||
int numbases = 0;
|
||||
for (unsigned base = 2; base <= 36; base++) {
|
||||
if (bases & (1ULL << base)) {
|
||||
if (has_base(bases, base)) {
|
||||
if (times1[i][0][base]) {
|
||||
numbases++;
|
||||
for (int j = 0; j < 4; j++)
|
||||
|
|
Loading…
Reference in a new issue