Ignore:
Timestamp:
Nov 23, 2011, 1:22:55 AM (14 years ago)
Author:
Nikolas Zimmermann
Message:

Add flags/precision arguments to String::number(double) to allow fine-grained control over the result string
https://bugs.webkit.org/show_bug.cgi?id=72793

Reviewed by Zoltan Herczeg.

Source/JavaScriptCore:

This new code will be used in follow-up patches to replace the String::format("%.2f") usage in
platform/text/TextStream.cpp, and String::format("%.6lg") usage in svg/SVGPathStringBuilder.cpp.

The String::number(double) currently calls String::format("%.6lg") in trunk. In order to replace
this by a variant that properly rounds to six significant figures, JSC code could be refactored.
JSCs Number.toPrecision/toFixed uses wtf/dtoa/double-conversion which provides all features we need,
except truncating trailing zeros, needed to mimic the "g" format, which is either f or e but with
trailing zeros removed, producing shorter results. Changed the default signature to:

"static String number(double, unsigned = ShouldRoundSignificantFigures | ShouldTruncateTrailingZeros, unsigned precision = 6);".

In WebCore we can now replace String::format() calls like this:
String::format("%.2f", f) -> String::number(f, ShouldRoundDecimalPlaces, 2)
String::format("%.6lg", f) -> String::number(f)

The default parameters for precison & flags exactly match the format of the string produced now, except that the result
is rounded according to the rounding mode / formatting mode and precision. This paves the way towards reliable results
in the d="" attribute dumps of SVG paths across platforms. The dtoa rounding code enforces a unique zero, resolving
all 0.0 vs. -0.0 issues currently seen on Windows, and some Gtk/Qt bots.

This patch needs a rebaseline of svg/dom/length-list-parser.html as we don't perfecly mimic the String::format() "lg" mode
result for exponentials, we used to return eg. "e-7" and now return "e-07" - the trailing zero truncation hasn't been
implemented for exponentials, as this really affects only this test and thus wasn't worth the trouble - in contrary the
trailing zero truncation is needed for thousands of other results in "f" notation, and thus needed to match the DRT results.

Here's a performance comparision using a JSC release build and some arbitary numbers:
Converting 123.456 using old approach took 95.527100ms. avg 0.000955ms/call.
Converting 123.456 using new approach took 28.126953ms. avg 0.000281ms/call.

Converting 123 using old approach took 85.411133ms. avg 0.000854ms/call.
Converting 123 using new approach took 24.190186ms. avg 0.000242ms/call.

Converting 0.1 using old approach took 92.622803ms. avg 0.000926ms/call.
Converting 0.1 using new approach took 23.317871ms. avg 0.000233ms/call.

Converting 1/i using old approach took 106.893066ms. avg 0.001069ms/call.
Converting 1/i using new approach took 27.164062ms. avg 0.000272ms/call.

For all numbers I've tested in RoundingSignificantFigures mode and 6 digit precision the speedup was at least 250%.

  • JavaScriptCore.exp: Change String::number(double) signature.
  • JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.def: Ditto.
  • runtime/NumberPrototype.cpp:

(JSC::numberProtoFuncToFixed): Refactor this into numberToFixedPrecisionString(), move to wtf/dtoa.cpp.
(JSC::numberProtoFuncToPrecision): Ditto, refactor this into numberToFixedWidthString.

  • wtf/dtoa.cpp: Moved fixedWidth/Precision helpers into dtoa, extend numberToFixedPrecisionString(). Add a mode which allows to truncate trailing zeros/decimal point.

to make it possible to use them to generate strings that match the output from String::format("%6.lg"), while using our dtoas rounding facilities.

  • wtf/dtoa.h:
  • wtf/dtoa/utils.h: Expose new helper method, which allows us to truncate the result, before generating the output const char*.

(WTF::double_conversion::StringBuilder::SetPosition):

  • wtf/text/WTFString.cpp:

(WTF::String::number): Remove String::format("%6.lg") usage! Switch to rounding to six significant figures, while matching the output of String::format.

  • wtf/text/WTFString.h:

LayoutTests:

Rebaseline one test result, after the String::number(double) changes. Trailing zeros are no longer removed
in the exponential form of the string: e-07 is reported instead of e-7 now. It was decided to leave it as-is
and not introduce trailing zero removal for the exponential formatting, as it only affects this test.

I'll rebaseline other platforms once results are available.

  • svg/dom/length-list-parser-expected.txt:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/wtf/dtoa.cpp

    r95511 r101056  
    17931793}
    17941794
    1795 
    1796 const char *numberToString(double d, NumberToStringBuffer buffer)
     1795const char* numberToString(double d, NumberToStringBuffer buffer)
    17971796{
    17981797    double_conversion::StringBuilder builder(buffer, NumberToStringBufferLength);
     
    18021801}
    18031802
     1803static inline const char* formatStringTruncatingTrailingZerosIfNeeded(NumberToStringBuffer buffer, double_conversion::StringBuilder& builder)
     1804{
     1805    size_t length = builder.position();
     1806    size_t decimalPointPosition = 0;
     1807    for (; decimalPointPosition < length; ++decimalPointPosition) {
     1808        if (buffer[decimalPointPosition] == '.')
     1809            break;
     1810    }
     1811
     1812    // No decimal seperator found, early exit.
     1813    if (decimalPointPosition == length)
     1814        return builder.Finalize();
     1815
     1816    size_t truncatedLength = length - 1;
     1817    for (; truncatedLength > decimalPointPosition; --truncatedLength) {
     1818        if (buffer[truncatedLength] != '0')
     1819            break;
     1820    }
     1821
     1822    // No trailing zeros found to strip.
     1823    if (truncatedLength == length - 1)
     1824        return builder.Finalize();
     1825
     1826    // If we removed all trailing zeros, remove the decimal point as well.
     1827    if (truncatedLength == decimalPointPosition) {
     1828        ASSERT(truncatedLength > 0);
     1829        --truncatedLength;
     1830    }
     1831
     1832    // Truncate the StringBuilder, and return the final result.
     1833    builder.SetPosition(truncatedLength + 1);
     1834    return builder.Finalize();
     1835}
     1836
     1837const char* numberToFixedPrecisionString(double d, unsigned significantFigures, NumberToStringBuffer buffer, bool truncateTrailingZeros)
     1838{
     1839    // Mimic String::format("%.[precision]g", ...), but use dtoas rounding facilities.
     1840    // "g": Signed value printed in f or e format, whichever is more compact for the given value and precision.
     1841    // The e format is used only when the exponent of the value is less than –4 or greater than or equal to the
     1842    // precision argument. Trailing zeros are truncated, and the decimal point appears only if one or more digits follow it.
     1843    // "precision": The precision specifies the maximum number of significant digits printed.
     1844    double_conversion::StringBuilder builder(buffer, NumberToStringBufferLength);
     1845    const double_conversion::DoubleToStringConverter& converter = double_conversion::DoubleToStringConverter::EcmaScriptConverter();
     1846    converter.ToPrecision(d, significantFigures, &builder);
     1847    if (!truncateTrailingZeros)
     1848        return builder.Finalize();
     1849    return formatStringTruncatingTrailingZerosIfNeeded(buffer, builder);
     1850}
     1851
     1852const char* numberToFixedWidthString(double d, unsigned decimalPlaces, NumberToStringBuffer buffer)
     1853{
     1854    // Mimic String::format("%.[precision]f", ...), but use dtoas rounding facilities.
     1855    // "f": Signed value having the form [ – ]dddd.dddd, where dddd is one or more decimal digits.
     1856    // The number of digits before the decimal point depends on the magnitude of the number, and
     1857    // the number of digits after the decimal point depends on the requested precision.
     1858    // "precision": The precision value specifies the number of digits after the decimal point.
     1859    // If a decimal point appears, at least one digit appears before it.
     1860    // The value is rounded to the appropriate number of digits.   
     1861    double_conversion::StringBuilder builder(buffer, NumberToStringBufferLength);
     1862    const double_conversion::DoubleToStringConverter& converter = double_conversion::DoubleToStringConverter::EcmaScriptConverter();
     1863    converter.ToFixed(d, decimalPlaces, &builder);
     1864    return builder.Finalize();
     1865}
     1866
    18041867} // namespace WTF
Note: See TracChangeset for help on using the changeset viewer.