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) {
}
// 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.
static const char kCharTable[] = "0123456789abcdefghijklmnopqrstuvwxyz";
return *isolate->factory()->LookupSingleCharacterStringFromCode(
......
......@@ -8,6 +8,7 @@
#include <stdarg.h>
#include <cmath>
#include "src/allocation.h"
#include "src/assert-scope.h"
#include "src/char-predicates-inl.h"
#include "src/codegen.h"
......@@ -411,76 +412,83 @@ char* DoubleToPrecisionCString(double value, int p) {
return result;
}
char* DoubleToRadixCString(double value, int radix) {
DCHECK(radix >= 2 && radix <= 36);
DCHECK(std::isfinite(value));
DCHECK_NE(0.0, value);
// Character array used for conversion.
static const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
// Buffer for the integer part of the result. 1024 chars is enough
// for max integer value in radix 2. We need room for a sign too.
static const int kBufferSize = 1100;
char integer_buffer[kBufferSize];
integer_buffer[kBufferSize - 1] = '\0';
// Buffer for the decimal part of the result. We only generate up
// to kBufferSize - 1 chars for the decimal part.
char decimal_buffer[kBufferSize];
decimal_buffer[kBufferSize - 1] = '\0';
// Make sure the value is positive.
bool is_negative = value < 0.0;
if (is_negative) value = -value;
// Get the integer part and the decimal part.
double integer_part = std::floor(value);
double decimal_part = value - integer_part;
// Temporary buffer for the result. We start with the decimal point in the
// middle and write to the left for the integer part and to the right for the
// fractional part. 1024 characters either way with additional space for sign
// and decimal point should be sufficient.
static const int kBufferSize = 2100;
char buffer[kBufferSize];
int integer_cursor = kBufferSize / 2;
int fraction_cursor = integer_cursor;
bool negative = value < 0;
if (negative) value = -value;
// Split the value into an integer part and a fractional part.
double integer = std::floor(value);
double fraction = value - integer;
// We only compute fractional digits up to the input double's precision.
double delta = 0.5 * (Double(value).NextDouble() - value);
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
// at least one digit.
int integer_pos = kBufferSize - 2;
// Compute integer digits.
do {
double remainder = modulo(integer_part, radix);
integer_buffer[integer_pos--] = chars[static_cast<int>(remainder)];
integer_part -= remainder;
integer_part /= radix;
} while (integer_part >= 1.0);
// Sanity check.
DCHECK(integer_pos > 0);
// Add sign if needed.
if (is_negative) integer_buffer[integer_pos--] = '-';
// Convert the decimal part. Repeatedly multiply by the radix to
// generate the next char. Never generate more than kBufferSize - 1
// chars.
//
// 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();
double multiple = std::floor(integer / radix);
int digit = static_cast<int>(integer - multiple * radix);
buffer[--integer_cursor] = chars[digit];
integer = multiple;
} while (integer > 0);
// Add sign and terminate string.
if (negative) buffer[--integer_cursor] = '-';
buffer[fraction_cursor++] = '\0';
// Allocate new string as return value.
char* result = NewArray<char>(fraction_cursor - integer_cursor);
memcpy(result, buffer + integer_cursor, fraction_cursor - integer_cursor);
return result;
}
......
......@@ -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("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
assertEquals("NaN", (NaN).toFixed(2));
......
......@@ -232,9 +232,9 @@ PASS Number(0.1).toString(10) is "0.1"
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(new Number(0.1), 2) is "0.0001100110011001100110011001100110011001100110011001101"
FAIL Number(0.1).toString(36) should be 0.3lllllllllm. Was 0.3llllllllllqsn8td1p464unmi.
FAIL Number.prototype.toString.call(0.1, 36) should be 0.3lllllllllm. Was 0.3llllllllllqsn8td1p464unmi.
FAIL Number.prototype.toString.call(new Number(0.1), 36) should be 0.3lllllllllm. Was 0.3llllllllllqsn8td1p464unmi.
PASS Number(0.1).toString(36) is "0.3lllllllllm"
PASS Number.prototype.toString.call(0.1, 36) is "0.3lllllllllm"
PASS Number.prototype.toString.call(new Number(0.1), 36) is "0.3lllllllllm"
PASS Number(-1.1).toString() 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"
......@@ -243,9 +243,9 @@ PASS Number(-1.1).toString(10) is "-1.1"
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(new Number(-1.1), 2) is "-1.000110011001100110011001100110011001100110011001101"
FAIL Number(-1.1).toString(36) should be -1.3llllllllm. Was -1.3lllllllllxagau2ctidswz5mi.
FAIL Number.prototype.toString.call(-1.1, 36) should be -1.3llllllllm. Was -1.3lllllllllxagau2ctidswz5mi.
FAIL Number.prototype.toString.call(new Number(-1.1), 36) should be -1.3llllllllm. Was -1.3lllllllllxagau2ctidswz5mi.
PASS Number(-1.1).toString(36) is "-1.3llllllllm"
PASS Number.prototype.toString.call(-1.1, 36) is "-1.3llllllllm"
PASS Number.prototype.toString.call(new Number(-1.1), 36) is "-1.3llllllllm"
PASS Number(1.1).toString() 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"
......@@ -254,9 +254,9 @@ PASS Number(1.1).toString(10) is "1.1"
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(new Number(1.1), 2) is "1.000110011001100110011001100110011001100110011001101"
FAIL Number(1.1).toString(36) should be 1.3llllllllm. Was 1.3lllllllllxagau2ctidswz5mi.
FAIL Number.prototype.toString.call(1.1, 36) should be 1.3llllllllm. Was 1.3lllllllllxagau2ctidswz5mi.
FAIL Number.prototype.toString.call(new Number(1.1), 36) should be 1.3llllllllm. Was 1.3lllllllllxagau2ctidswz5mi.
PASS Number(1.1).toString(36) is "1.3llllllllm"
PASS Number.prototype.toString.call(1.1, 36) is "1.3llllllllm"
PASS Number.prototype.toString.call(new Number(1.1), 36) is "1.3llllllllm"
PASS Number(1984.1).toString() 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"
......@@ -265,9 +265,9 @@ PASS Number(1984.1).toString(10) is "1984.1"
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(new Number(1984.1), 2) is "11111000000.00011001100110011001100110011001100110011"
FAIL Number(1984.1).toString(36) should be 1j4.3lllllllc. Was 1j4.3lllllllcd2obsszcl3di.
FAIL Number.prototype.toString.call(1984.1, 36) should be 1j4.3lllllllc. Was 1j4.3lllllllcd2obsszcl3di.
FAIL Number.prototype.toString.call(new Number(1984.1), 36) should be 1j4.3lllllllc. Was 1j4.3lllllllcd2obsszcl3di.
PASS Number(1984.1).toString(36) is "1j4.3lllllllc"
PASS Number.prototype.toString.call(1984.1, 36) is "1j4.3lllllllc"
PASS Number.prototype.toString.call(new Number(1984.1), 36) is "1j4.3lllllllc"
PASS Number(-1984.1).toString() 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"
......@@ -276,9 +276,9 @@ PASS Number(-1984.1).toString(10) is "-1984.1"
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(new Number(-1984.1), 2) is "-11111000000.00011001100110011001100110011001100110011"
FAIL Number(-1984.1).toString(36) should be -1j4.3lllllllc. Was -1j4.3lllllllcd2obsszcl3di.
FAIL Number.prototype.toString.call(-1984.1, 36) should be -1j4.3lllllllc. Was -1j4.3lllllllcd2obsszcl3di.
FAIL Number.prototype.toString.call(new Number(-1984.1), 36) should be -1j4.3lllllllc. Was -1j4.3lllllllcd2obsszcl3di.
PASS Number(-1984.1).toString(36) is "-1j4.3lllllllc"
PASS Number.prototype.toString.call(-1984.1, 36) is "-1j4.3lllllllc"
PASS Number.prototype.toString.call(new Number(-1984.1), 36) is "-1j4.3lllllllc"
PASS Number(2147483647.1).toString() 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"
......@@ -287,9 +287,9 @@ PASS Number(2147483647.1).toString(10) is "2147483647.1"
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(new Number(2147483647.1), 2) is "1111111111111111111111111111111.000110011001100110011"
FAIL Number(2147483647.1).toString(36) should be zik0zj.3lllg. Was zik0zj.3lllfu07ldi.
FAIL Number.prototype.toString.call(2147483647.1, 36) should be zik0zj.3lllg. Was zik0zj.3lllfu07ldi.
FAIL Number.prototype.toString.call(new Number(2147483647.1), 36) should be zik0zj.3lllg. Was zik0zj.3lllfu07ldi.
PASS Number(2147483647.1).toString(36) is "zik0zj.3lllg"
PASS Number.prototype.toString.call(2147483647.1, 36) is "zik0zj.3lllg"
PASS Number.prototype.toString.call(new Number(2147483647.1), 36) is "zik0zj.3lllg"
PASS Number(-2147483648.1).toString() 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"
......@@ -298,9 +298,9 @@ PASS Number(-2147483648.1).toString(10) is "-2147483648.1"
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(new Number(-2147483648.1), 2) is "-10000000000000000000000000000000.000110011001100110011"
FAIL Number(-2147483648.1).toString(36) should be -zik0zk.3lllg. Was -zik0zk.3lllfu07ldi.
FAIL Number.prototype.toString.call(-2147483648.1, 36) should be -zik0zk.3lllg. Was -zik0zk.3lllfu07ldi.
FAIL Number.prototype.toString.call(new Number(-2147483648.1), 36) should be -zik0zk.3lllg. Was -zik0zk.3lllfu07ldi.
PASS Number(-2147483648.1).toString(36) is "-zik0zk.3lllg"
PASS Number.prototype.toString.call(-2147483648.1, 36) is "-zik0zk.3lllg"
PASS Number.prototype.toString.call(new Number(-2147483648.1), 36) is "-zik0zk.3lllg"
PASS Number(9007199254740992).toString() is "9007199254740992"
PASS Number.prototype.toString.call(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