Skip to content

Commit f9ecc1a

Browse files
authored
Fix encoding bug in js_dtoa_radix (#399)
- fix radix conversion rounding code: incrementing the digit does not work for '9'. We can assume ASCII so it works for all other digits, especially all letters - also avoid recomputing the string length
1 parent 6cb1301 commit f9ecc1a

File tree

2 files changed

+33
-19
lines changed

2 files changed

+33
-19
lines changed

quickjs.c

+32-19
Original file line numberDiff line numberDiff line change
@@ -11261,9 +11261,10 @@ static JSValue js_dtoa(JSContext *ctx,
1126111261
return JS_NewStringLen(ctx, buf, len);
1126211262
}
1126311263

11264+
/* d is guaranteed to be finite */
1126411265
static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
1126511266
{
11266-
char buf[2200], *ptr, *ptr2;
11267+
char buf[2200], *ptr, *ptr2, *ptr3;
1126711268
/* d is finite */
1126811269
int sign = d < 0;
1126911270
int digit;
@@ -11272,8 +11273,8 @@ static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
1127211273
d = fabs(d);
1127311274
d0 = trunc(d);
1127411275
frac = d - d0;
11275-
ptr = buf + 1100;
11276-
*ptr = '\0';
11276+
ptr2 = buf + 1100; /* ptr2 points to the end of the string */
11277+
ptr = ptr2; /* ptr points to the beginning of the string */
1127711278
if (d0 <= MAX_SAFE_INTEGER) {
1127811279
int64_t n = n0 = (int64_t)d0;
1127911280
while (n >= radix) {
@@ -11297,7 +11298,6 @@ static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
1129711298
if (frac != 0) {
1129811299
double log2_radix = log2(radix);
1129911300
double prec = 1023 + 51; // handle subnormals
11300-
ptr2 = buf + 1100;
1130111301
*ptr2++ = '.';
1130211302
while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) {
1130311303
frac *= radix;
@@ -11307,32 +11307,45 @@ static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix)
1130711307
n0 = n0 * radix + digit;
1130811308
prec -= log2_radix;
1130911309
}
11310-
*ptr2 = '\0';
1131111310
if (frac * radix >= radix / 2) {
11311+
/* round up the string representation manually */
1131211312
char nine = digits36[radix - 1];
11313-
// round to closest
11314-
while (ptr2[-1] == nine)
11315-
*--ptr2 = '\0';
11313+
while (ptr2[-1] == nine) {
11314+
/* strip trailing '9' or equivalent digits */
11315+
ptr2--;
11316+
}
1131611317
if (ptr2[-1] == '.') {
11317-
*--ptr2 = '\0';
11318-
while (ptr2[-1] == nine)
11319-
*--ptr2 = '0';
11318+
/* strip the 'decimal' point */
11319+
ptr2--;
11320+
/* increment the integral part */
11321+
for (ptr3 = ptr2;;) {
11322+
if (ptr3[-1] != nine) {
11323+
ptr3[-1] = (ptr3[-1] == '9') ? 'a' : ptr3[-1] + 1;
11324+
break;
11325+
}
11326+
*--ptr3 = '0';
11327+
if (ptr3 <= ptr) {
11328+
/* prepend a '1' if number was all nines */
11329+
*--ptr = '1';
11330+
break;
11331+
}
11332+
}
11333+
} else {
11334+
/* increment the last fractional digit */
11335+
ptr2[-1] = (ptr2[-1] == '9') ? 'a' : ptr2[-1] + 1;
1132011336
}
11321-
if (ptr2 - 1 == ptr)
11322-
*--ptr = '1';
11323-
else
11324-
ptr2[-1] += 1;
1132511337
} else {
11338+
/* strip trailing fractional zeroes */
1132611339
while (ptr2[-1] == '0')
11327-
*--ptr2 = '\0';
11328-
if (ptr2[-1] == '.')
11329-
*--ptr2 = '\0';
11340+
ptr2--;
11341+
/* strip the 'decimal' point if last */
11342+
ptr2 -= (ptr2[-1] == '.');
1133011343
}
1133111344
}
1133211345
done:
1133311346
ptr[-1] = '-';
1133411347
ptr -= sign;
11335-
return JS_NewString(ctx, ptr);
11348+
return js_new_string8(ctx, (uint8_t *)ptr, ptr2 - ptr);
1133611349
}
1133711350

1133811351
JSValue JS_ToStringInternal(JSContext *ctx, JSValue val, BOOL is_ToPropertyKey)

tests/test_builtin.js

+1
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ function test_number()
439439
return;
440440
}
441441

442+
assert((1-2**-53).toString(12), "0.bbbbbbbbbbbbbba");
442443
assert((25).toExponential(0), "3e+1");
443444
assert((-25).toExponential(0), "-3e+1");
444445
assert((2.5).toPrecision(1), "3");

0 commit comments

Comments
 (0)