Implement Math.hypot
https://bugs.webkit.org/show_bug.cgi?id=129486

Patch by Tibor Meszaros <tmeszaros.u-szeged@partner.samsung.com> on 2014-03-17
Reviewed by Darin Adler.

Source/JavaScriptCore:

* runtime/MathObject.cpp:
(JSC::MathObject::finishCreation):
(JSC::mathProtoFuncHypot):

LayoutTests:

* js/Object-getOwnPropertyNames-expected.txt:
* js/math-expected.txt:
* js/script-tests/Object-getOwnPropertyNames.js:
* js/script-tests/math.js:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@165795 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index fd688e7..99a00a7 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,15 @@
+2014-03-17  Tibor Meszaros  <tmeszaros.u-szeged@partner.samsung.com>
+
+        Implement Math.hypot
+        https://bugs.webkit.org/show_bug.cgi?id=129486
+
+        Reviewed by Darin Adler.
+
+        * js/Object-getOwnPropertyNames-expected.txt:
+        * js/math-expected.txt:
+        * js/script-tests/Object-getOwnPropertyNames.js:
+        * js/script-tests/math.js:
+
 2014-03-17  Jer Noble  <jer.noble@apple.com>
 
         Layout Test mathml/wbr-in-mroot-crash.html crashes
diff --git a/LayoutTests/js/Object-getOwnPropertyNames-expected.txt b/LayoutTests/js/Object-getOwnPropertyNames-expected.txt
index 764a921..20044cc 100644
--- a/LayoutTests/js/Object-getOwnPropertyNames-expected.txt
+++ b/LayoutTests/js/Object-getOwnPropertyNames-expected.txt
@@ -58,7 +58,7 @@
 PASS getSortedOwnPropertyNames(RegExp.prototype) is ['compile', 'constructor', 'exec', 'global', 'ignoreCase', 'lastIndex', 'multiline', 'source', 'test', 'toString']
 PASS getSortedOwnPropertyNames(Error) is ['length', 'name', 'prototype']
 PASS getSortedOwnPropertyNames(Error.prototype) is ['constructor', 'message', 'name', 'toString']
-PASS getSortedOwnPropertyNames(Math) is ['E','LN10','LN2','LOG10E','LOG2E','PI','SQRT1_2','SQRT2','abs','acos','acosh','asin','asinh','atan','atan2','atanh','cbrt','ceil','cos','cosh','exp','expm1','floor','fround','imul','log','log10','log1p','log2','max','min','pow','random','round','sin','sinh','sqrt','tan','tanh','trunc']
+PASS getSortedOwnPropertyNames(Math) is ['E','LN10','LN2','LOG10E','LOG2E','PI','SQRT1_2','SQRT2','abs','acos','acosh','asin','asinh','atan','atan2','atanh','cbrt','ceil','cos','cosh','exp','expm1','floor','fround','hypot','imul','log','log10','log1p','log2','max','min','pow','random','round','sin','sinh','sqrt','tan','tanh','trunc']
 PASS getSortedOwnPropertyNames(JSON) is ['parse', 'stringify']
 PASS globalPropertyNames.indexOf('NaN') != -1 is true
 PASS globalPropertyNames.indexOf('Infinity') != -1 is true
diff --git a/LayoutTests/js/math-expected.txt b/LayoutTests/js/math-expected.txt
index 827088f..246e986 100644
--- a/LayoutTests/js/math-expected.txt
+++ b/LayoutTests/js/math-expected.txt
@@ -83,6 +83,29 @@
 PASS Math.floor(-Number.MAX_VALUE) is -Number.MAX_VALUE
 PASS Math.floor(Infinity) is Infinity
 PASS Math.floor(-Infinity) is -Infinity
+PASS Math.hypot.length is 2
+PASS Math.hypot(NaN) is NaN
+PASS Math.hypot() is 0
+PASS Math.hypot(-0) is 0
+PASS Math.hypot(0) is 0
+PASS Math.hypot(-Infinity) is Infinity
+PASS Math.hypot(Infinity) is Infinity
+PASS Math.hypot(-3) is 3
+PASS Math.hypot(3, 4) is 5
+PASS Math.hypot(1, NaN, Infinity) is Infinity
+PASS Math.hypot(3, 4, 5) is 7.0710678118654755
+PASS Math.hypot(3, 4, '5') is 7.0710678118654755
+PASS Math.hypot(-3, -4) is 5
+PASS Math.hypot(3, -Infinity) is Infinity
+PASS Math.hypot(3, NaN) is NaN
+PASS Math.hypot(NaN, 3) is NaN
+PASS Math.hypot(0, NaN) is NaN
+PASS Math.hypot(NaN, 0) is NaN
+PASS Math.hypot(NaN, {valueOf:function(){throw "err"}}) threw exception err.
+PASS Math.hypot(NaN, NaN, {valueOf:function(){throw "err"}}) threw exception err.
+PASS Math.hypot({valueOf:function(){throw "error1"}}, {valueOf:function(){sideEffect = 1}}) threw exception error1.
+PASS sideEffect is 0
+PASS Math.hypot(3, 4, 'foo') is NaN
 PASS Math.log(NaN) is NaN
 PASS Math.log(0) is -Infinity
 PASS Math.log(-0) is -Infinity
diff --git a/LayoutTests/js/script-tests/Object-getOwnPropertyNames.js b/LayoutTests/js/script-tests/Object-getOwnPropertyNames.js
index 99c4e39..dc90250 100644
--- a/LayoutTests/js/script-tests/Object-getOwnPropertyNames.js
+++ b/LayoutTests/js/script-tests/Object-getOwnPropertyNames.js
@@ -66,7 +66,7 @@
     "RegExp.prototype": "['compile', 'constructor', 'exec', 'global', 'ignoreCase', 'lastIndex', 'multiline', 'source', 'test', 'toString']",
     "Error": "['length', 'name', 'prototype']",
     "Error.prototype": "['constructor', 'message', 'name', 'toString']",
-    "Math": "['E','LN10','LN2','LOG10E','LOG2E','PI','SQRT1_2','SQRT2','abs','acos','acosh','asin','asinh','atan','atan2','atanh','cbrt','ceil','cos','cosh','exp','expm1','floor','fround','imul','log','log10','log1p','log2','max','min','pow','random','round','sin','sinh','sqrt','tan','tanh','trunc']",
+    "Math": "['E','LN10','LN2','LOG10E','LOG2E','PI','SQRT1_2','SQRT2','abs','acos','acosh','asin','asinh','atan','atan2','atanh','cbrt','ceil','cos','cosh','exp','expm1','floor','fround','hypot','imul','log','log10','log1p','log2','max','min','pow','random','round','sin','sinh','sqrt','tan','tanh','trunc']",
     "JSON": "['parse', 'stringify']"
 };
 
diff --git a/LayoutTests/js/script-tests/math.js b/LayoutTests/js/script-tests/math.js
index 943fb68..44aae05 100644
--- a/LayoutTests/js/script-tests/math.js
+++ b/LayoutTests/js/script-tests/math.js
@@ -124,6 +124,31 @@
 shouldBe("Math.floor(Infinity)", "Infinity");
 shouldBe("Math.floor(-Infinity)", "-Infinity");
 
+shouldBe("Math.hypot.length","2");
+shouldBe("Math.hypot(NaN)", "NaN");
+shouldBe("Math.hypot()", "0");
+shouldBe("Math.hypot(-0)", "0");
+shouldBe("Math.hypot(0)", "0");
+shouldBe("Math.hypot(-Infinity)", "Infinity");
+shouldBe("Math.hypot(Infinity)", "Infinity");
+shouldBe("Math.hypot(-3)", "3");
+shouldBe("Math.hypot(3, 4)", "5");
+shouldBe("Math.hypot(1, NaN, Infinity)", "Infinity");
+shouldBe("Math.hypot(3, 4, 5)", "7.0710678118654755");
+shouldBe("Math.hypot(3, 4, '5')", "7.0710678118654755");
+shouldBe("Math.hypot(-3, -4)", "5");
+shouldBe("Math.hypot(3, -Infinity)", "Infinity");
+shouldBe("Math.hypot(3, NaN)", "NaN");
+shouldBe("Math.hypot(NaN, 3)", "NaN");
+shouldBe("Math.hypot(0, NaN)", "NaN");
+shouldBe("Math.hypot(NaN, 0)", "NaN");
+shouldThrow("Math.hypot(NaN, {valueOf:function(){throw \"err\"}})","'err'");
+shouldThrow("Math.hypot(NaN, NaN, {valueOf:function(){throw \"err\"}})","'err'");
+sideEffect = 0;
+shouldThrow("Math.hypot({valueOf:function(){throw \"error1\"}}, {valueOf:function(){sideEffect = 1}})", "'error1'");
+shouldBe('sideEffect', '0');
+shouldBe("Math.hypot(3, 4, 'foo')", "NaN");
+
 shouldBe("Math.log(NaN)", "NaN");
 shouldBe("Math.log(0)", "-Infinity");
 shouldBe("Math.log(-0)", "-Infinity");
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index d7b6c58..5025163 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,14 @@
+2014-03-17  Tibor Meszaros  <tmeszaros.u-szeged@partner.samsung.com>
+
+        Implement Math.hypot
+        https://bugs.webkit.org/show_bug.cgi?id=129486
+
+        Reviewed by Darin Adler.
+
+        * runtime/MathObject.cpp:
+        (JSC::MathObject::finishCreation):
+        (JSC::mathProtoFuncHypot):
+
 2014-03-17  Zsolt Borbely  <borbezs@inf.u-szeged.hu>
 
         Fix the !ENABLE(PROMISES) build
diff --git a/Source/JavaScriptCore/runtime/MathObject.cpp b/Source/JavaScriptCore/runtime/MathObject.cpp
index f671ace9..d600c92 100644
--- a/Source/JavaScriptCore/runtime/MathObject.cpp
+++ b/Source/JavaScriptCore/runtime/MathObject.cpp
@@ -29,6 +29,7 @@
 #include <wtf/MathExtras.h>
 #include <wtf/RandomNumber.h>
 #include <wtf/RandomNumberSeed.h>
+#include <wtf/Vector.h>
 
 namespace JSC {
 
@@ -50,6 +51,7 @@
 static EncodedJSValue JSC_HOST_CALL mathProtoFuncExpm1(ExecState*);
 static EncodedJSValue JSC_HOST_CALL mathProtoFuncFloor(ExecState*);
 static EncodedJSValue JSC_HOST_CALL mathProtoFuncFround(ExecState*);
+static EncodedJSValue JSC_HOST_CALL mathProtoFuncHypot(ExecState*);
 static EncodedJSValue JSC_HOST_CALL mathProtoFuncLog(ExecState*);
 static EncodedJSValue JSC_HOST_CALL mathProtoFuncLog1p(ExecState*);
 static EncodedJSValue JSC_HOST_CALL mathProtoFuncLog10(ExecState*);
@@ -108,6 +110,7 @@
     putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier(&vm, "expm1"), 1, mathProtoFuncExpm1, NoIntrinsic, DontEnum | Function);
     putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier(&vm, "floor"), 1, mathProtoFuncFloor, FloorIntrinsic, DontEnum | Function);
     putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier(&vm, "fround"), 1, mathProtoFuncFround, NoIntrinsic, DontEnum | Function);
+    putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier(&vm, "hypot"), 2, mathProtoFuncHypot, NoIntrinsic, DontEnum | Function);
     putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier(&vm, "log"), 1, mathProtoFuncLog, LogIntrinsic, DontEnum | Function);
     putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier(&vm, "log10"), 1, mathProtoFuncLog10, NoIntrinsic, DontEnum | Function);
     putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier(&vm, "log1p"), 1, mathProtoFuncLog1p, NoIntrinsic, DontEnum | Function);
@@ -175,6 +178,35 @@
     return JSValue::encode(jsNumber(floor(exec->argument(0).toNumber(exec))));
 }
 
+EncodedJSValue JSC_HOST_CALL mathProtoFuncHypot(ExecState* exec)
+{
+    unsigned argsCount = exec->argumentCount();
+    double max = 0;
+    Vector<double, 8> args;
+    args.reserveInitialCapacity(argsCount);
+    for (unsigned i = 0; i < argsCount; ++i) {
+        args.uncheckedAppend(exec->uncheckedArgument(i).toNumber(exec));
+        if (exec->hadException())
+            return JSValue::encode(jsNull());
+        if (std::isinf(args[i]))
+            return JSValue::encode(jsDoubleNumber(+std::numeric_limits<double>::infinity()));
+        max = std::max(fabs(args[i]), max);
+    }
+    if (!max)
+        max = 1;
+    // Kahan summation algorithm significantly reduces the numerical error in the total obtained.
+    double sum = 0;
+    double compensation = 0;
+    for (double argument : args) {
+        double scaledArgument = argument / max;
+        double summand = scaledArgument * scaledArgument - compensation;
+        double preliminary = sum + summand;
+        compensation = (preliminary - sum) - summand;
+        sum = preliminary;
+    }
+    return JSValue::encode(jsDoubleNumber(sqrt(sum) * max));
+}
+
 EncodedJSValue JSC_HOST_CALL mathProtoFuncLog(ExecState* exec)
 {
     return JSValue::encode(jsDoubleNumber(log(exec->argument(0).toNumber(exec))));