Bug 896264 - Implement Math.hypot(). r=jorendorff.
authorDavid Caabeiro <david.caabeiro@gmail.com>
Wed, 02 Oct 2013 17:27:50 +0200
changeset 151878 eafe6b0acd33631cedf91cfe2f6245ea634bf2f9
parent 151877 9f4815b4ad647f0724fb9ec5a794031817a998aa
child 151879 35a83682c173c50c54901ccb5781448d2f66cc99
push id25512
push usercbook@mozilla.com
push dateThu, 24 Oct 2013 05:06:01 +0000
treeherderautoland@19fd3388c372 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs896264
milestone27.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 896264 - Implement Math.hypot(). r=jorendorff.
js/src/jsmath.cpp
js/src/jsmath.h
js/src/tests/ecma_6/Math/hypot-approx.js
js/src/tests/ecma_6/Math/hypot-exact.js
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -1268,84 +1268,51 @@ js::math_atanh_uncached(double x)
 }
 
 bool
 js::math_atanh(JSContext *cx, unsigned argc, Value *vp)
 {
     return math_function<math_atanh_impl>(cx, argc, vp);
 }
 
-// Math.hypot is disabled pending the resolution of spec issues (bug 896264).
-#if 0
-#if !HAVE_HYPOT
-double hypot(double x, double y)
-{
-    if (mozilla::IsInfinite(x) || mozilla::IsInfinite(y))
-        return PositiveInfinity();
-
-    if (mozilla::IsNaN(x) || mozilla::IsNaN(y))
-        return GenericNaN();
-
-    double xabs = mozilla::Abs(x);
-    double yabs = mozilla::Abs(y);
-
-    double min = std::min(xabs, yabs);
-    double max = std::max(xabs, yabs);
-
-    if (min == 0) {
-        return max;
-    } else {
-        double u = min / max;
-        return max * sqrt(1 + u * u);
-    }
-}
-#endif
-
-double
-js::math_hypot_impl(double x, double y)
-{
-#ifdef XP_WIN
-    // On Windows, hypot(NaN, Infinity) is NaN. ES6 requires Infinity.
-    if (mozilla::IsInfinite(x) || mozilla::IsInfinite(y))
-        return PositiveInfinity();
-#endif
-    return hypot(x, y);
-}
-
 bool
 js::math_hypot(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (args.length() < 2) {
-        args.rval().setNumber(GenericNaN());
-        return true;
+
+    bool isInfinite = false;
+    bool isNaN = false;
+
+    double scale = 0;
+    double sumsq = 1;
+
+    for (unsigned i = 0; i < args.length(); i++) {
+        double x;
+        if (!ToNumber(cx, args[i], &x))
+            return false;
+
+        isInfinite |= mozilla::IsInfinite(x);
+        isNaN |= mozilla::IsNaN(x);
+
+        double xabs = mozilla::Abs(x);
+
+        if (scale < xabs) {
+            sumsq = 1 + sumsq * (scale / xabs) * (scale / xabs);
+            scale = xabs;
+        } else if (scale != 0) {
+            sumsq += (xabs / scale) * (xabs / scale);
+        }
     }
 
-    double x, y;
-    if (!ToNumber(cx, args[0], &x))
-        return false;
-
-    if (!ToNumber(cx, args[1], &y))
-        return false;
-
-    if (args.length() == 2) {
-        args.rval().setNumber(math_hypot_impl(x, y));
-        return true;
-    }
-
-    /* args.length() > 2 */
-    double z;
-    if (!ToNumber(cx, args[2], &z)) {
-        return false;
-    }
-
-    args.rval().setNumber(math_hypot_impl(math_hypot_impl(x, y), z));
+    double result = isInfinite ? PositiveInfinity() :
+                    isNaN ? GenericNaN() :
+                    scale * sqrt(sumsq);
+    args.rval().setNumber(result);
     return true;
 }
-#endif
 
 #if !HAVE_TRUNC
 double trunc(double x)
 {
     return x > 0 ? floor(x) : ceil(x);
 }
 #endif
 
@@ -1463,20 +1430,17 @@ static const JSFunctionSpec math_static_
     JS_FN("log1p",          math_log1p,           1, 0),
     JS_FN("expm1",          math_expm1,           1, 0),
     JS_FN("cosh",           math_cosh,            1, 0),
     JS_FN("sinh",           math_sinh,            1, 0),
     JS_FN("tanh",           math_tanh,            1, 0),
     JS_FN("acosh",          math_acosh,           1, 0),
     JS_FN("asinh",          math_asinh,           1, 0),
     JS_FN("atanh",          math_atanh,           1, 0),
-// Math.hypot is disabled pending the resolution of spec issues (bug 896264).
-#if 0
     JS_FN("hypot",          math_hypot,           2, 0),
-#endif
     JS_FN("trunc",          math_trunc,           1, 0),
     JS_FN("sign",           math_sign,            1, 0),
     JS_FN("cbrt",           math_cbrt,            1, 0),
     JS_FS_END
 };
 
 JSObject *
 js_InitMathClass(JSContext *cx, HandleObject obj)
--- a/js/src/jsmath.h
+++ b/js/src/jsmath.h
@@ -157,21 +157,18 @@ extern bool
 math_acosh(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern bool
 math_asinh(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern bool
 math_atanh(JSContext *cx, unsigned argc, js::Value *vp);
 
-// Math.hypot is disabled pending the resolution of spec issues (bug 896264).
-#if 0
 extern bool
 math_hypot(JSContext *cx, unsigned argc, Value *vp);
-#endif
 
 extern bool
 math_trunc(JSContext *cx, unsigned argc, Value *vp);
 
 extern bool
 math_sign(JSContext *cx, unsigned argc, Value *vp);
 
 extern bool
--- a/js/src/tests/ecma_6/Math/hypot-approx.js
+++ b/js/src/tests/ecma_6/Math/hypot-approx.js
@@ -1,16 +1,25 @@
-// |reftest| skip
-// Math.hypot is disabled pending the resolution of spec issues (bug 896264).
-
 for (var i = -20; i < 20; i++) {
     assertEq(Math.hypot(+0, i), Math.abs(i));
     assertEq(Math.hypot(-0, i), Math.abs(i));
 }
 
-assertNear(Math.hypot(1e300, 1e300), 1.4142135623730952e+300);
+// The implementation must avoid underlow.
+// The implementation must avoid overflow, where possible.
+// The implementation must minimise rounding errors.
+
 assertNear(Math.hypot(1e-300, 1e-300), 1.414213562373095e-300);
+assertNear(Math.hypot(1e-300, 1e-300, 1e-300), 1.732050807568877e-300);
+
+assertNear(Math.hypot(1e-3, 1e-3, 1e-3), 0.0017320508075688772);
+
+assertNear(Math.hypot(1e300, 1e300), 1.4142135623730952e+300);
+assertNear(Math.hypot(1e100, 1e200, 1e300), 1e300);
+
 assertNear(Math.hypot(1e3, 1e-3), 1000.0000000005);
+assertNear(Math.hypot(1e-300, 1e300), 1e300);
+assertNear(Math.hypot(1e3, 1e-3, 1e3, 1e-3), 1414.2135623738021555);
 
 for (var i = 1, j = 1; i < 2; i += 0.05, j += 0.05)
     assertNear(Math.hypot(i, j), Math.sqrt(i * i + j * j));
 
 reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_6/Math/hypot-exact.js
+++ b/js/src/tests/ecma_6/Math/hypot-exact.js
@@ -1,12 +1,12 @@
-// |reftest| skip
-// Math.hypot is disabled pending the resolution of spec issues (bug 896264).
+// Properties of Math.hypot that are guaranteed by the spec.
 
-// Properties of Math.hypot that are guaranteed by the spec.
+// If no arguments are passed, the result is +0.
+assertEq(Math.hypot(), +0);
 
 // If any argument is +∞, the result is +∞.
 // If any argument is −∞, the result is +∞.
 for (var inf of [Infinity, -Infinity]) {
     assertEq(Math.hypot(inf, 0), Infinity);
     assertEq(Math.hypot(0, inf), Infinity);
     assertEq(Math.hypot(inf, inf), Infinity);
     assertEq(Math.hypot(inf, -inf), Infinity);
@@ -26,18 +26,17 @@ for (var inf of [Infinity, -Infinity]) {
     assertEq(Math.hypot(NaN, inf), Infinity);
 
     assertEq(Math.hypot(inf, NaN, NaN), Infinity);
     assertEq(Math.hypot(NaN, inf, NaN), Infinity);
     assertEq(Math.hypot(NaN, NaN, inf), Infinity);
 }
 
 // If no argument is +∞ or −∞, and any argument is NaN, the result is NaN.
-assertEq(Math.hypot(), NaN);
-assertEq(Math.hypot(1), NaN);
+assertEq(Math.hypot(NaN), NaN);
 
 assertEq(Math.hypot(NaN, 0), NaN);
 assertEq(Math.hypot(0, NaN), NaN);
 
 assertEq(Math.hypot(NaN, NaN), NaN);
 
 assertEq(Math.hypot(NaN, 0, 0), NaN);
 assertEq(Math.hypot(0, NaN, 0), NaN);