Improve toString(radix) for doubles near zero

Currently, Number.prototype.toString(radix) often fails to produce the
least significant bit for doubles near zero. For example, for the
minimum double, 5e-324, toString(2) produces "0". This means that a
user cannot reliably get the exact binary or hexdecimal value of a
double from JavaScript using toString.

This patch makes a slight amendment to the DoubleToRadixCString
function, so that doubles where the gap to the next double is 5e-324
(i.e. doubles less than 2**-1021), are represented exactly in binary and
other power-of-two bases, and close to exactly otherwise. It results
in Number.prototype.toString producing the correct binary value for all
doubles.

R=jkummerow@chromium.org, mathias@chromium.org, yangguo@chromium.org

Bug: v8:9294
Change-Id: I71506149b7c4c0eac8c38675a1ee15fb4f36f9ef
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1631601
Commit-Queue: Mathias Bynens <mathias@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Mathias Bynens <mathias@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61925}
This commit is contained in:
Michael Mclaughlin 2019-05-29 10:46:30 +01:00 committed by Commit Bot
parent f75c90a6f6
commit 348cc6f152
4 changed files with 95 additions and 7 deletions

View File

@ -127,6 +127,7 @@ Matthew Sporleder <msporleder@gmail.com>
Maxim Mazurok <maxim@mazurok.com>
Maxim Mossienko <maxim.mossienko@gmail.com>
Michael Lutz <michi@icosahedron.de>
Michael Mclaughlin <m8ch88l@gmail.com>
Michael Smith <mike@w3.org>
Michaël Zasso <mic.besace@gmail.com>
Mike Gilbert <floppymaster@gmail.com>

View File

@ -1245,7 +1245,7 @@ char* DoubleToRadixCString(double value, int radix) {
double delta = 0.5 * (Double(value).NextDouble() - value);
delta = std::max(Double(0.0).NextDouble(), delta);
DCHECK_GT(delta, 0.0);
if (fraction > delta) {
if (fraction >= delta) {
// Insert decimal point.
buffer[fraction_cursor++] = '.';
do {
@ -1280,7 +1280,7 @@ char* DoubleToRadixCString(double value, int radix) {
break;
}
}
} while (fraction > delta);
} while (fraction >= delta);
}
// Compute integer digits. Fill unrepresented digits with zero.

View File

@ -0,0 +1,92 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Tests Number.prototype.toString for numbers within or near the subnormal
// range, when the radix argument is 2.
// A JavaScript number is a IEEE 754 binary64 (double-precision floating-point)
// value, so we should be able to provide a binary string value that is an
// exact representation.
const zeros = count => '0'.repeat(count);
const test = (binaryStringValue, double) => {
assertEquals(binaryStringValue, double.toString(2));
};
// 2**-1074
test(`0.${zeros(1073)}1`, Number.MIN_VALUE);
// Bug v8:9294
test(`0.${zeros(1022)}1101100011110111011100000100011001111101110001010111`,
1.8858070859709815e-308);
test(`0.${zeros(1021)}11110111001111000110110111011101110001000000000000001`,
4.297800585227606e-308);
// Normal doubles smaller than 2**-1021 (4.450147717014403e-308). These values
// are not in the subnormal range, but like the subnormals they have a gap of
// Number.MIN_VALUE between themselves and the next double.
test(`0.${zeros(1021)}11100001110000011100111110011010010100100010001001001`,
3.924423154449847e-308);
test(`-0.${zeros(1021)}11001101101111001101100010110110011000000011010111001`,
-3.57641826104544e-308);
test(`-0.${zeros(1021)}11101100100000100110110001000010010001001000101110001`,
-4.1113361447183043e-308);
test(`0.${zeros(1021)}11111001101000111100111001001101101001001011010001101`,
4.339587042263274e-308);
test(`0.${zeros(1021)}10111011101001010010101011100001110000000001011110001`,
3.261909352323954e-308);
test(`0.${zeros(1021)}10001001101101110110000110001011100001100111111111001`,
2.3939766453008923e-308);
test(`-0.${zeros(1021)}11101001000110010001100111110111001001000001100010011`,
-4.052034242003901e-308);
test(`-0.${zeros(1021)}10001111010010000100000110100010101101001000110010101`,
-2.4907311894031355e-308);
test(`-0.${zeros(1021)}10101100001001010011101010001110011010000001111000001`,
-2.9924709724070097e-308);
test(`-0.${zeros(1021)}11111101001111010110001011001001000100110101001010111`,
-4.402165887028534e-308);
// Subnormal doubles: numbers smaller than 2**-1022 (2.2250738585072014e-308).
test(`0.${zeros(1022)}1100111101100011000101110111111000011111110001001011`,
1.802545172319673e-308);
test(`0.${zeros(1022)}1111001000101011101110111111011111111100111101101011`,
2.104874994274149e-308);
test(`-0.${zeros(1022)}1001110011110110110010010010010001111100001111101011`,
-1.3642832344349763e-308);
test(`0.${zeros(1023)}111101010100011111101110111101011000001110011101101`,
1.0659537476238824e-308);
test(`-0.${zeros(1023)}100101011110101100111101101001110011101011110111101`,
-6.51524700064251e-309);
test(`-0.${zeros(1024)}10011100110100110010111101001001111100000101000111`,
-3.407686279964664e-309);
test(`-0.${zeros(1024)}11101001001010000001101111010101011111111011010111`,
-5.06631661953108e-309);
test(`-0.${zeros(1024)}10111100111100001100010100110011001011000011011101`,
-4.105533080656306e-309);
test(`0.${zeros(1025)}1101111100101101111110101111001010101100001100111`,
2.42476131288505e-309);
test(`-0.${zeros(1025)}1001000100011011100101010010101010101011101000111`,
-1.576540281929606e-309);
test(`0.${zeros(1023)}111101100001000000001011101011101010001110011111101`,
1.0693508455891784e-308);
test(`0.${zeros(1024)}11100010101101001101011010110001110101110100010001`,
4.926157093545696e-309);
test(`-0.${zeros(1027)}10010111100001111110000101011001010111100011011`,
-4.1158103328176e-310);
test(`0.${zeros(1030)}11111010010111010110101101000111010011100001`,
8.500372841691e-311);
test(`0.${zeros(1033)}101001010011111001100100001010001101`,
7.01292938871e-312);
test(`0.${zeros(1037)}11101010101101100111000110100111001`, 6.22574126804e-313);
test(`-0.${zeros(1040)}10100001001101011011111001111111`, -5.3451064043e-314);
test(`-0.${zeros(1043)}1001100101100100000001000011111`, -6.35731246e-315);
test(`0.${zeros(1046)}10101110110100011010110001`, 9.05676893e-316);
test(`-0.${zeros(1050)}11001010000110011100011`, -6.5438353e-317);
test(`0.${zeros(1053)}111001010000010001`, 9.269185e-318);
test(`-0.${zeros(1057)}1100001000010101`, -4.90953e-319);
test(`-0.${zeros(1059)}10011001001111`, -9.6906e-320);
test(`0.${zeros(1063)}111101111`, 9.782e-321);
test(`0.${zeros(1067)}10011`, 3.75e-322);
test(`-0.${zeros(1070)}1`, -4e-323);

View File

@ -1,5 +0,0 @@
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
assertEquals("0", Number.MIN_VALUE.toString(35));