Bug 944846 part 4 - Use DoubleToStringConverter::ToFixed for toFixed. r=anba
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 10 Feb 2021 13:42:18 +0000
changeset 566811 86cdeb4f7d76bdd99626ea3f55e5ce95bb4ee03c
parent 566810 613260ab04e0a45849c72ea429857ab2646e59de
child 566812 569826c0fd47a0e196057500c0873fab31253b70
push id38190
push userbtara@mozilla.com
push dateWed, 10 Feb 2021 21:50:51 +0000
treeherdermozilla-central@569826c0fd47 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanba
bugs944846
milestone87.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 944846 part 4 - Use DoubleToStringConverter::ToFixed for toFixed. r=anba Differential Revision: https://phabricator.services.mozilla.com/D104522
js/src/jsnum.cpp
js/src/tests/non262/Number/toFixed-values.js
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -1115,36 +1115,16 @@ static bool ComputePrecisionInRange(JSCo
   ToCStringBuf cbuf;
   if (char* numStr = NumberToCString(cx, &cbuf, prec, 10)) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_PRECISION_RANGE, numStr);
   }
   return false;
 }
 
-static bool DToStrResult(JSContext* cx, double d, JSDToStrMode mode,
-                         int precision, const CallArgs& args) {
-  if (!EnsureDtoaState(cx)) {
-    return false;
-  }
-
-  char buf[DTOSTR_VARIABLE_BUFFER_SIZE(MAX_PRECISION + 1)];
-  char* numStr = js_dtostr(cx->dtoaState, buf, sizeof buf, mode, precision, d);
-  if (!numStr) {
-    JS_ReportOutOfMemory(cx);
-    return false;
-  }
-  JSString* str = NewStringCopyZ<CanGC>(cx, numStr);
-  if (!str) {
-    return false;
-  }
-  args.rval().setString(str);
-  return true;
-}
-
 static constexpr size_t DoubleToStrResultBufSize = 128;
 
 template <typename Op>
 [[nodiscard]] static bool DoubleToStrResult(JSContext* cx, const CallArgs& args,
                                             Op op) {
   char buf[DoubleToStrResultBufSize];
 
   const auto& converter =
@@ -1161,64 +1141,88 @@ template <typename Op>
   if (!str) {
     return false;
   }
 
   args.rval().setString(str);
   return true;
 }
 
-/*
- * In the following three implementations, we allow a larger range of precision
- * than ECMA requires; this is permitted by ECMA-262.
- */
-// ES 2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f 20.1.3.3.
+// ES 2021 draft 21.1.3.3.
 static bool num_toFixed(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   // Step 1.
   double d;
   if (!ThisNumberValue(cx, args, "toFixed", &d)) {
     return false;
   }
 
-  // Steps 2-3.
+  // Steps 2-5.
   int precision;
   if (args.length() == 0) {
     precision = 0;
   } else {
     double prec = 0;
     if (!ToInteger(cx, args[0], &prec)) {
       return false;
     }
 
     if (!ComputePrecisionInRange(cx, 0, MAX_PRECISION, prec, &precision)) {
       return false;
     }
   }
 
-  // Step 4.
+  // Step 6.
   if (mozilla::IsNaN(d)) {
     args.rval().setString(cx->names().NaN);
     return true;
   }
-
-  // Steps 5-7, 9 (optimized path for Infinity).
   if (mozilla::IsInfinite(d)) {
     if (d > 0) {
       args.rval().setString(cx->names().Infinity);
       return true;
     }
 
     args.rval().setString(cx->names().NegativeInfinity);
     return true;
   }
 
-  // Steps 5-9.
-  return DToStrResult(cx, d, DTOSTR_FIXED, precision, args);
+  // Steps 7-10 for very large numbers.
+  if (d <= -1e21 || d >= 1e+21) {
+    JSString* s = NumberToString<CanGC>(cx, d);
+    if (!s) {
+      return false;
+    }
+
+    args.rval().setString(s);
+    return true;
+  }
+
+  // Steps 7-12.
+
+  // DoubleToStringConverter::ToFixed is documented as requiring a buffer size
+  // of:
+  //
+  //   1 + kMaxFixedDigitsBeforePoint + 1 + kMaxFixedDigitsAfterPoint + 1
+  //   (one additional character for the sign, one for the decimal point,
+  //      and one for the null terminator)
+  //
+  // We already ensured there are at most 21 digits before the point, and
+  // MAX_PRECISION digits after the point.
+  static_assert(1 + 21 + 1 + MAX_PRECISION + 1 <= DoubleToStrResultBufSize);
+
+  // The double-conversion library by default has a kMaxFixedDigitsAfterPoint of
+  // 60. Assert our modified version supports at least MAX_PRECISION (100).
+  using DToSConverter = double_conversion::DoubleToStringConverter;
+  static_assert(DToSConverter::kMaxFixedDigitsAfterPoint >= MAX_PRECISION);
+
+  return DoubleToStrResult(cx, args, [&](auto& converter, auto& builder) {
+    return converter.ToFixed(d, precision, &builder);
+  });
 }
 
 // ES 2021 draft 21.1.3.2.
 static bool num_toExponential(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   // Step 1.
   double d;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Number/toFixed-values.js
@@ -0,0 +1,118 @@
+let values = [
+  [-0, undefined, "0"],
+  [-0, 0, "0"],
+  [-0, 1, "0.0"],
+  [-0, 10, "0.0000000000"],
+  [-0, 50, "0.00000000000000000000000000000000000000000000000000"],
+  [-0, 100, "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+  [0, undefined, "0"],
+  [0, 0, "0"],
+  [0, 1, "0.0"],
+  [0, 10, "0.0000000000"],
+  [0, 50, "0.00000000000000000000000000000000000000000000000000"],
+  [0, 100, "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+  [NaN, undefined, "NaN"],
+  [NaN, 0, "NaN"],
+  [NaN, 1, "NaN"],
+  [NaN, 10, "NaN"],
+  [NaN, 50, "NaN"],
+  [NaN, 100, "NaN"],
+  [Infinity, undefined, "Infinity"],
+  [Infinity, 0, "Infinity"],
+  [Infinity, 1, "Infinity"],
+  [Infinity, 10, "Infinity"],
+  [Infinity, 50, "Infinity"],
+  [Infinity, 100, "Infinity"],
+  [-Infinity, undefined, "-Infinity"],
+  [-Infinity, 0, "-Infinity"],
+  [-Infinity, 1, "-Infinity"],
+  [-Infinity, 10, "-Infinity"],
+  [-Infinity, 50, "-Infinity"],
+  [-Infinity, 100, "-Infinity"],
+  [3.141592653589793, undefined, "3"],
+  [3.141592653589793, 0, "3"],
+  [3.141592653589793, 1, "3.1"],
+  [3.141592653589793, 10, "3.1415926536"],
+  [3.141592653589793, 50, "3.14159265358979311599796346854418516159057617187500"],
+  [3.141592653589793, 100, "3.1415926535897931159979634685441851615905761718750000000000000000000000000000000000000000000000000000"],
+  [-1, undefined, "-1"],
+  [-1, 0, "-1"],
+  [-1, 1, "-1.0"],
+  [-1, 10, "-1.0000000000"],
+  [-1, 50, "-1.00000000000000000000000000000000000000000000000000"],
+  [-1, 100, "-1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+  [1, undefined, "1"],
+  [1, 0, "1"],
+  [1, 1, "1.0"],
+  [1, 10, "1.0000000000"],
+  [1, 50, "1.00000000000000000000000000000000000000000000000000"],
+  [1, 100, "1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+  [-123456.78, undefined, "-123457"],
+  [-123456.78, 0, "-123457"],
+  [-123456.78, 1, "-123456.8"],
+  [-123456.78, 10, "-123456.7800000000"],
+  [-123456.78, 50, "-123456.77999999999883584678173065185546875000000000000000"],
+  [-123456.78, 100, "-123456.7799999999988358467817306518554687500000000000000000000000000000000000000000000000000000000000000000"],
+  [123456.78, undefined, "123457"],
+  [123456.78, 0, "123457"],
+  [123456.78, 1, "123456.8"],
+  [123456.78, 10, "123456.7800000000"],
+  [123456.78, 50, "123456.77999999999883584678173065185546875000000000000000"],
+  [123456.78, 100, "123456.7799999999988358467817306518554687500000000000000000000000000000000000000000000000000000000000000000"],
+  [100000000000000000000, undefined, "100000000000000000000"],
+  [100000000000000000000, 0, "100000000000000000000"],
+  [100000000000000000000, 1, "100000000000000000000.0"],
+  [100000000000000000000, 10, "100000000000000000000.0000000000"],
+  [100000000000000000000, 50, "100000000000000000000.00000000000000000000000000000000000000000000000000"],
+  [100000000000000000000, 100, "100000000000000000000.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+  [1e+21, undefined, "1e+21"],
+  [1e+21, 0, "1e+21"],
+  [1e+21, 1, "1e+21"],
+  [1e+21, 10, "1e+21"],
+  [1e+21, 50, "1e+21"],
+  [1e+21, 100, "1e+21"],
+  [-100000000000000000000, undefined, "-100000000000000000000"],
+  [-100000000000000000000, 0, "-100000000000000000000"],
+  [-100000000000000000000, 1, "-100000000000000000000.0"],
+  [-100000000000000000000, 10, "-100000000000000000000.0000000000"],
+  [-100000000000000000000, 50, "-100000000000000000000.00000000000000000000000000000000000000000000000000"],
+  [-100000000000000000000, 100, "-100000000000000000000.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+  [-1e+21, undefined, "-1e+21"],
+  [-1e+21, 0, "-1e+21"],
+  [-1e+21, 1, "-1e+21"],
+  [-1e+21, 10, "-1e+21"],
+  [-1e+21, 50, "-1e+21"],
+  [-1e+21, 100, "-1e+21"],
+  [Number.MAX_VALUE, undefined, "1.7976931348623157e+308"],
+  [Number.MAX_VALUE, 0, "1.7976931348623157e+308"],
+  [Number.MAX_VALUE, 1, "1.7976931348623157e+308"],
+  [Number.MAX_VALUE, 10, "1.7976931348623157e+308"],
+  [Number.MAX_VALUE, 50, "1.7976931348623157e+308"],
+  [Number.MAX_VALUE, 100, "1.7976931348623157e+308"],
+  [-Number.MAX_VALUE, undefined, "-1.7976931348623157e+308"],
+  [-Number.MAX_VALUE, 0, "-1.7976931348623157e+308"],
+  [-Number.MAX_VALUE, 1, "-1.7976931348623157e+308"],
+  [-Number.MAX_VALUE, 10, "-1.7976931348623157e+308"],
+  [-Number.MAX_VALUE, 50, "-1.7976931348623157e+308"],
+  [-Number.MAX_VALUE, 100, "-1.7976931348623157e+308"],
+  [Number.MIN_VALUE, undefined, "0"],
+  [Number.MIN_VALUE, 0, "0"],
+  [Number.MIN_VALUE, 1, "0.0"],
+  [Number.MIN_VALUE, 10, "0.0000000000"],
+  [Number.MIN_VALUE, 50, "0.00000000000000000000000000000000000000000000000000"],
+  [Number.MIN_VALUE, 100, "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+  [-Number.MIN_VALUE, undefined, "-0"],
+  [-Number.MIN_VALUE, 0, "-0"],
+  [-Number.MIN_VALUE, 1, "-0.0"],
+  [-Number.MIN_VALUE, 10, "-0.0000000000"],
+  [-Number.MIN_VALUE, 50, "-0.00000000000000000000000000000000000000000000000000"],
+  [-Number.MIN_VALUE, 100, "-0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
+];
+
+for (let [val, prec, expected] of values) {
+  assertEq(Number.prototype.toFixed.call(val, prec), expected);
+}
+
+if (typeof reportCompare === "function") {
+  reportCompare(true, true);
+}