Commit 21b0dbed authored by yangguo's avatar yangguo Committed by Commit bot

Reimplement Number.prototype.toString with non-default radix.

The old algorithm produces unnecessary decimal digits. The new one
converts the significand of the input double into an uint64_t to be
just as precise as necessary.

R=tebbi@chromium.org
BUG=chromium:658712,chromium:666376

Review-Url: https://codereview.chromium.org/2520363002
Cr-Commit-Position: refs/heads/master@{#41255}
parent 244dd002
...@@ -545,7 +545,8 @@ BUILTIN(NumberPrototypeToString) { ...@@ -545,7 +545,8 @@ BUILTIN(NumberPrototypeToString) {
} }
// Fast case where the result is a one character string. // Fast case where the result is a one character string.
if (IsUint32Double(value_number) && value_number < radix_number) { if ((IsUint32Double(value_number) && value_number < radix_number) ||
value_number == -0.0) {
// Character array used for conversion. // Character array used for conversion.
static const char kCharTable[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static const char kCharTable[] = "0123456789abcdefghijklmnopqrstuvwxyz";
return *isolate->factory()->LookupSingleCharacterStringFromCode( return *isolate->factory()->LookupSingleCharacterStringFromCode(
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <stdarg.h> #include <stdarg.h>
#include <cmath> #include <cmath>
#include "src/allocation.h"
#include "src/assert-scope.h" #include "src/assert-scope.h"
#include "src/char-predicates-inl.h" #include "src/char-predicates-inl.h"
#include "src/codegen.h" #include "src/codegen.h"
...@@ -411,76 +412,83 @@ char* DoubleToPrecisionCString(double value, int p) { ...@@ -411,76 +412,83 @@ char* DoubleToPrecisionCString(double value, int p) {
return result; return result;
} }
char* DoubleToRadixCString(double value, int radix) { char* DoubleToRadixCString(double value, int radix) {
DCHECK(radix >= 2 && radix <= 36); DCHECK(radix >= 2 && radix <= 36);
DCHECK(std::isfinite(value));
DCHECK_NE(0.0, value);
// Character array used for conversion. // Character array used for conversion.
static const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
// Buffer for the integer part of the result. 1024 chars is enough // Temporary buffer for the result. We start with the decimal point in the
// for max integer value in radix 2. We need room for a sign too. // middle and write to the left for the integer part and to the right for the
static const int kBufferSize = 1100; // fractional part. 1024 characters either way with additional space for sign
char integer_buffer[kBufferSize]; // and decimal point should be sufficient.
integer_buffer[kBufferSize - 1] = '\0'; static const int kBufferSize = 2100;
char buffer[kBufferSize];
// Buffer for the decimal part of the result. We only generate up int integer_cursor = kBufferSize / 2;
// to kBufferSize - 1 chars for the decimal part. int fraction_cursor = integer_cursor;
char decimal_buffer[kBufferSize];
decimal_buffer[kBufferSize - 1] = '\0'; bool negative = value < 0;
if (negative) value = -value;
// Make sure the value is positive.
bool is_negative = value < 0.0; // Split the value into an integer part and a fractional part.
if (is_negative) value = -value; double integer = std::floor(value);
double fraction = value - integer;
// Get the integer part and the decimal part. // We only compute fractional digits up to the input double's precision.
double integer_part = std::floor(value); double delta = 0.5 * (Double(value).NextDouble() - value);
double decimal_part = value - integer_part; if (fraction > delta) {
// Insert decimal point.
buffer[fraction_cursor++] = '.';
do {
// Shift up by one digit.
fraction *= radix;
delta *= radix;
// Write digit.
int digit = static_cast<int>(fraction);
buffer[fraction_cursor++] = chars[digit];
// Calculate remainder.
fraction -= digit;
// Round to even.
if (fraction > 0.5 || (fraction == 0.5 && (digit & 1))) {
if (fraction + delta > 1) {
// We need to back trace already written digits in case of carry-over.
while (true) {
fraction_cursor--;
if (fraction_cursor == kBufferSize / 2) {
CHECK_EQ('.', buffer[fraction_cursor]);
// Carry over to the integer part.
integer += 1;
break;
}
char c = buffer[fraction_cursor];
// Reconstruct digit.
int digit = c > '9' ? (c - 'a' + 10) : (c - '0');
if (digit + 1 < radix) {
buffer[fraction_cursor++] = chars[digit + 1];
break;
}
}
break;
}
}
} while (fraction > delta);
}
// Convert the integer part starting from the back. Always generate // Compute integer digits.
// at least one digit.
int integer_pos = kBufferSize - 2;
do { do {
double remainder = modulo(integer_part, radix); double multiple = std::floor(integer / radix);
integer_buffer[integer_pos--] = chars[static_cast<int>(remainder)]; int digit = static_cast<int>(integer - multiple * radix);
integer_part -= remainder; buffer[--integer_cursor] = chars[digit];
integer_part /= radix; integer = multiple;
} while (integer_part >= 1.0); } while (integer > 0);
// Sanity check.
DCHECK(integer_pos > 0); // Add sign and terminate string.
// Add sign if needed. if (negative) buffer[--integer_cursor] = '-';
if (is_negative) integer_buffer[integer_pos--] = '-'; buffer[fraction_cursor++] = '\0';
// Allocate new string as return value.
// Convert the decimal part. Repeatedly multiply by the radix to char* result = NewArray<char>(fraction_cursor - integer_cursor);
// generate the next char. Never generate more than kBufferSize - 1 memcpy(result, buffer + integer_cursor, fraction_cursor - integer_cursor);
// chars. return result;
//
// TODO(1093998): We will often generate a full decimal_buffer of
// chars because hitting zero will often not happen. The right
// solution would be to continue until the string representation can
// be read back and yield the original value. To implement this
// efficiently, we probably have to modify dtoa.
int decimal_pos = 0;
while ((decimal_part > 0.0) && (decimal_pos < kBufferSize - 1)) {
decimal_part *= radix;
decimal_buffer[decimal_pos++] =
chars[static_cast<int>(std::floor(decimal_part))];
decimal_part -= std::floor(decimal_part);
}
decimal_buffer[decimal_pos] = '\0';
// Compute the result size.
int integer_part_size = kBufferSize - 2 - integer_pos;
// Make room for zero termination.
unsigned result_size = integer_part_size + decimal_pos;
// If the number has a decimal part, leave room for the period.
if (decimal_pos > 0) result_size++;
// Allocate result and fill in the parts.
SimpleStringBuilder builder(result_size + 1);
builder.AddSubstring(integer_buffer + integer_pos + 1, integer_part_size);
if (decimal_pos > 0) builder.AddCharacter('.');
builder.AddSubstring(decimal_buffer, decimal_pos);
return builder.Finalize();
} }
......
...@@ -144,6 +144,13 @@ assertEquals("1e-7", (0.0000001).toString()); ...@@ -144,6 +144,13 @@ assertEquals("1e-7", (0.0000001).toString());
assertEquals("8.8", (8.5).toString(16)); assertEquals("8.8", (8.5).toString(16));
assertEquals("-8.8", (-8.5).toString(16)); assertEquals("-8.8", (-8.5).toString(16));
assertEquals("1.1", (4/3).toString(3));
assertEquals("11.1", (13/3).toString(3));
assertEquals("0.01", (1/9).toString(3));
assertEquals("10000", (81).toString(3));
assertEquals("10000.01", (81 + 1/9).toString(3));
assertEquals("0.0212010212010212010212010212010212", (2/7).toString(3));
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// toFixed // toFixed
assertEquals("NaN", (NaN).toFixed(2)); assertEquals("NaN", (NaN).toFixed(2));
......
...@@ -232,9 +232,9 @@ PASS Number(0.1).toString(10) is "0.1" ...@@ -232,9 +232,9 @@ PASS Number(0.1).toString(10) is "0.1"
PASS Number(0.1).toString(2) is "0.0001100110011001100110011001100110011001100110011001101" PASS Number(0.1).toString(2) is "0.0001100110011001100110011001100110011001100110011001101"
PASS Number.prototype.toString.call(0.1, 2) is "0.0001100110011001100110011001100110011001100110011001101" PASS Number.prototype.toString.call(0.1, 2) is "0.0001100110011001100110011001100110011001100110011001101"
PASS Number.prototype.toString.call(new Number(0.1), 2) is "0.0001100110011001100110011001100110011001100110011001101" PASS Number.prototype.toString.call(new Number(0.1), 2) is "0.0001100110011001100110011001100110011001100110011001101"
FAIL Number(0.1).toString(36) should be 0.3lllllllllm. Was 0.3llllllllllqsn8td1p464unmi. PASS Number(0.1).toString(36) is "0.3lllllllllm"
FAIL Number.prototype.toString.call(0.1, 36) should be 0.3lllllllllm. Was 0.3llllllllllqsn8td1p464unmi. PASS Number.prototype.toString.call(0.1, 36) is "0.3lllllllllm"
FAIL Number.prototype.toString.call(new Number(0.1), 36) should be 0.3lllllllllm. Was 0.3llllllllllqsn8td1p464unmi. PASS Number.prototype.toString.call(new Number(0.1), 36) is "0.3lllllllllm"
PASS Number(-1.1).toString() is "-1.1" PASS Number(-1.1).toString() is "-1.1"
PASS Number.prototype.toString.call(-1.1) is "-1.1" PASS Number.prototype.toString.call(-1.1) is "-1.1"
PASS Number.prototype.toString.call(new Number(-1.1)) is "-1.1" PASS Number.prototype.toString.call(new Number(-1.1)) is "-1.1"
...@@ -243,9 +243,9 @@ PASS Number(-1.1).toString(10) is "-1.1" ...@@ -243,9 +243,9 @@ PASS Number(-1.1).toString(10) is "-1.1"
PASS Number(-1.1).toString(2) is "-1.000110011001100110011001100110011001100110011001101" PASS Number(-1.1).toString(2) is "-1.000110011001100110011001100110011001100110011001101"
PASS Number.prototype.toString.call(-1.1, 2) is "-1.000110011001100110011001100110011001100110011001101" PASS Number.prototype.toString.call(-1.1, 2) is "-1.000110011001100110011001100110011001100110011001101"
PASS Number.prototype.toString.call(new Number(-1.1), 2) is "-1.000110011001100110011001100110011001100110011001101" PASS Number.prototype.toString.call(new Number(-1.1), 2) is "-1.000110011001100110011001100110011001100110011001101"
FAIL Number(-1.1).toString(36) should be -1.3llllllllm. Was -1.3lllllllllxagau2ctidswz5mi. PASS Number(-1.1).toString(36) is "-1.3llllllllm"
FAIL Number.prototype.toString.call(-1.1, 36) should be -1.3llllllllm. Was -1.3lllllllllxagau2ctidswz5mi. PASS Number.prototype.toString.call(-1.1, 36) is "-1.3llllllllm"
FAIL Number.prototype.toString.call(new Number(-1.1), 36) should be -1.3llllllllm. Was -1.3lllllllllxagau2ctidswz5mi. PASS Number.prototype.toString.call(new Number(-1.1), 36) is "-1.3llllllllm"
PASS Number(1.1).toString() is "1.1" PASS Number(1.1).toString() is "1.1"
PASS Number.prototype.toString.call(1.1) is "1.1" PASS Number.prototype.toString.call(1.1) is "1.1"
PASS Number.prototype.toString.call(new Number(1.1)) is "1.1" PASS Number.prototype.toString.call(new Number(1.1)) is "1.1"
...@@ -254,9 +254,9 @@ PASS Number(1.1).toString(10) is "1.1" ...@@ -254,9 +254,9 @@ PASS Number(1.1).toString(10) is "1.1"
PASS Number(1.1).toString(2) is "1.000110011001100110011001100110011001100110011001101" PASS Number(1.1).toString(2) is "1.000110011001100110011001100110011001100110011001101"
PASS Number.prototype.toString.call(1.1, 2) is "1.000110011001100110011001100110011001100110011001101" PASS Number.prototype.toString.call(1.1, 2) is "1.000110011001100110011001100110011001100110011001101"
PASS Number.prototype.toString.call(new Number(1.1), 2) is "1.000110011001100110011001100110011001100110011001101" PASS Number.prototype.toString.call(new Number(1.1), 2) is "1.000110011001100110011001100110011001100110011001101"
FAIL Number(1.1).toString(36) should be 1.3llllllllm. Was 1.3lllllllllxagau2ctidswz5mi. PASS Number(1.1).toString(36) is "1.3llllllllm"
FAIL Number.prototype.toString.call(1.1, 36) should be 1.3llllllllm. Was 1.3lllllllllxagau2ctidswz5mi. PASS Number.prototype.toString.call(1.1, 36) is "1.3llllllllm"
FAIL Number.prototype.toString.call(new Number(1.1), 36) should be 1.3llllllllm. Was 1.3lllllllllxagau2ctidswz5mi. PASS Number.prototype.toString.call(new Number(1.1), 36) is "1.3llllllllm"
PASS Number(1984.1).toString() is "1984.1" PASS Number(1984.1).toString() is "1984.1"
PASS Number.prototype.toString.call(1984.1) is "1984.1" PASS Number.prototype.toString.call(1984.1) is "1984.1"
PASS Number.prototype.toString.call(new Number(1984.1)) is "1984.1" PASS Number.prototype.toString.call(new Number(1984.1)) is "1984.1"
...@@ -265,9 +265,9 @@ PASS Number(1984.1).toString(10) is "1984.1" ...@@ -265,9 +265,9 @@ PASS Number(1984.1).toString(10) is "1984.1"
PASS Number(1984.1).toString(2) is "11111000000.00011001100110011001100110011001100110011" PASS Number(1984.1).toString(2) is "11111000000.00011001100110011001100110011001100110011"
PASS Number.prototype.toString.call(1984.1, 2) is "11111000000.00011001100110011001100110011001100110011" PASS Number.prototype.toString.call(1984.1, 2) is "11111000000.00011001100110011001100110011001100110011"
PASS Number.prototype.toString.call(new Number(1984.1), 2) is "11111000000.00011001100110011001100110011001100110011" PASS Number.prototype.toString.call(new Number(1984.1), 2) is "11111000000.00011001100110011001100110011001100110011"
FAIL Number(1984.1).toString(36) should be 1j4.3lllllllc. Was 1j4.3lllllllcd2obsszcl3di. PASS Number(1984.1).toString(36) is "1j4.3lllllllc"
FAIL Number.prototype.toString.call(1984.1, 36) should be 1j4.3lllllllc. Was 1j4.3lllllllcd2obsszcl3di. PASS Number.prototype.toString.call(1984.1, 36) is "1j4.3lllllllc"
FAIL Number.prototype.toString.call(new Number(1984.1), 36) should be 1j4.3lllllllc. Was 1j4.3lllllllcd2obsszcl3di. PASS Number.prototype.toString.call(new Number(1984.1), 36) is "1j4.3lllllllc"
PASS Number(-1984.1).toString() is "-1984.1" PASS Number(-1984.1).toString() is "-1984.1"
PASS Number.prototype.toString.call(-1984.1) is "-1984.1" PASS Number.prototype.toString.call(-1984.1) is "-1984.1"
PASS Number.prototype.toString.call(new Number(-1984.1)) is "-1984.1" PASS Number.prototype.toString.call(new Number(-1984.1)) is "-1984.1"
...@@ -276,9 +276,9 @@ PASS Number(-1984.1).toString(10) is "-1984.1" ...@@ -276,9 +276,9 @@ PASS Number(-1984.1).toString(10) is "-1984.1"
PASS Number(-1984.1).toString(2) is "-11111000000.00011001100110011001100110011001100110011" PASS Number(-1984.1).toString(2) is "-11111000000.00011001100110011001100110011001100110011"
PASS Number.prototype.toString.call(-1984.1, 2) is "-11111000000.00011001100110011001100110011001100110011" PASS Number.prototype.toString.call(-1984.1, 2) is "-11111000000.00011001100110011001100110011001100110011"
PASS Number.prototype.toString.call(new Number(-1984.1), 2) is "-11111000000.00011001100110011001100110011001100110011" PASS Number.prototype.toString.call(new Number(-1984.1), 2) is "-11111000000.00011001100110011001100110011001100110011"
FAIL Number(-1984.1).toString(36) should be -1j4.3lllllllc. Was -1j4.3lllllllcd2obsszcl3di. PASS Number(-1984.1).toString(36) is "-1j4.3lllllllc"
FAIL Number.prototype.toString.call(-1984.1, 36) should be -1j4.3lllllllc. Was -1j4.3lllllllcd2obsszcl3di. PASS Number.prototype.toString.call(-1984.1, 36) is "-1j4.3lllllllc"
FAIL Number.prototype.toString.call(new Number(-1984.1), 36) should be -1j4.3lllllllc. Was -1j4.3lllllllcd2obsszcl3di. PASS Number.prototype.toString.call(new Number(-1984.1), 36) is "-1j4.3lllllllc"
PASS Number(2147483647.1).toString() is "2147483647.1" PASS Number(2147483647.1).toString() is "2147483647.1"
PASS Number.prototype.toString.call(2147483647.1) is "2147483647.1" PASS Number.prototype.toString.call(2147483647.1) is "2147483647.1"
PASS Number.prototype.toString.call(new Number(2147483647.1)) is "2147483647.1" PASS Number.prototype.toString.call(new Number(2147483647.1)) is "2147483647.1"
...@@ -287,9 +287,9 @@ PASS Number(2147483647.1).toString(10) is "2147483647.1" ...@@ -287,9 +287,9 @@ PASS Number(2147483647.1).toString(10) is "2147483647.1"
PASS Number(2147483647.1).toString(2) is "1111111111111111111111111111111.000110011001100110011" PASS Number(2147483647.1).toString(2) is "1111111111111111111111111111111.000110011001100110011"
PASS Number.prototype.toString.call(2147483647.1, 2) is "1111111111111111111111111111111.000110011001100110011" PASS Number.prototype.toString.call(2147483647.1, 2) is "1111111111111111111111111111111.000110011001100110011"
PASS Number.prototype.toString.call(new Number(2147483647.1), 2) is "1111111111111111111111111111111.000110011001100110011" PASS Number.prototype.toString.call(new Number(2147483647.1), 2) is "1111111111111111111111111111111.000110011001100110011"
FAIL Number(2147483647.1).toString(36) should be zik0zj.3lllg. Was zik0zj.3lllfu07ldi. PASS Number(2147483647.1).toString(36) is "zik0zj.3lllg"
FAIL Number.prototype.toString.call(2147483647.1, 36) should be zik0zj.3lllg. Was zik0zj.3lllfu07ldi. PASS Number.prototype.toString.call(2147483647.1, 36) is "zik0zj.3lllg"
FAIL Number.prototype.toString.call(new Number(2147483647.1), 36) should be zik0zj.3lllg. Was zik0zj.3lllfu07ldi. PASS Number.prototype.toString.call(new Number(2147483647.1), 36) is "zik0zj.3lllg"
PASS Number(-2147483648.1).toString() is "-2147483648.1" PASS Number(-2147483648.1).toString() is "-2147483648.1"
PASS Number.prototype.toString.call(-2147483648.1) is "-2147483648.1" PASS Number.prototype.toString.call(-2147483648.1) is "-2147483648.1"
PASS Number.prototype.toString.call(new Number(-2147483648.1)) is "-2147483648.1" PASS Number.prototype.toString.call(new Number(-2147483648.1)) is "-2147483648.1"
...@@ -298,9 +298,9 @@ PASS Number(-2147483648.1).toString(10) is "-2147483648.1" ...@@ -298,9 +298,9 @@ PASS Number(-2147483648.1).toString(10) is "-2147483648.1"
PASS Number(-2147483648.1).toString(2) is "-10000000000000000000000000000000.000110011001100110011" PASS Number(-2147483648.1).toString(2) is "-10000000000000000000000000000000.000110011001100110011"
PASS Number.prototype.toString.call(-2147483648.1, 2) is "-10000000000000000000000000000000.000110011001100110011" PASS Number.prototype.toString.call(-2147483648.1, 2) is "-10000000000000000000000000000000.000110011001100110011"
PASS Number.prototype.toString.call(new Number(-2147483648.1), 2) is "-10000000000000000000000000000000.000110011001100110011" PASS Number.prototype.toString.call(new Number(-2147483648.1), 2) is "-10000000000000000000000000000000.000110011001100110011"
FAIL Number(-2147483648.1).toString(36) should be -zik0zk.3lllg. Was -zik0zk.3lllfu07ldi. PASS Number(-2147483648.1).toString(36) is "-zik0zk.3lllg"
FAIL Number.prototype.toString.call(-2147483648.1, 36) should be -zik0zk.3lllg. Was -zik0zk.3lllfu07ldi. PASS Number.prototype.toString.call(-2147483648.1, 36) is "-zik0zk.3lllg"
FAIL Number.prototype.toString.call(new Number(-2147483648.1), 36) should be -zik0zk.3lllg. Was -zik0zk.3lllfu07ldi. PASS Number.prototype.toString.call(new Number(-2147483648.1), 36) is "-zik0zk.3lllg"
PASS Number(9007199254740992).toString() is "9007199254740992" PASS Number(9007199254740992).toString() is "9007199254740992"
PASS Number.prototype.toString.call(9007199254740992) is "9007199254740992" PASS Number.prototype.toString.call(9007199254740992) is "9007199254740992"
PASS Number.prototype.toString.call(new Number(9007199254740992)) is "9007199254740992" PASS Number.prototype.toString.call(new Number(9007199254740992)) is "9007199254740992"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment