From 84cf85598dd747e9984e068a0cad36d6f0656efe Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Wed, 19 Feb 2014 13:49:59 +0000 Subject: [PATCH] Harmony: implement Math.cbrt, Math.expm1 and Math.log1p. BUG=v8:2938 LOG=N R=jarin@chromium.org Review URL: https://codereview.chromium.org/163563003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@19486 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/harmony-math.js | 23 +++++++- src/runtime.cc | 111 +++++++++++++++++++++++++++++-------- src/runtime.h | 9 ++- test/mjsunit/harmony/math-cbrt.js | 27 +++++++++ test/mjsunit/harmony/math-expm1.js | 38 +++++++++++++ test/mjsunit/harmony/math-log1p.js | 41 ++++++++++++++ 6 files changed, 223 insertions(+), 26 deletions(-) create mode 100644 test/mjsunit/harmony/math-cbrt.js create mode 100644 test/mjsunit/harmony/math-expm1.js create mode 100644 test/mjsunit/harmony/math-log1p.js diff --git a/src/harmony-math.js b/src/harmony-math.js index c856ce7..7856917 100644 --- a/src/harmony-math.js +++ b/src/harmony-math.js @@ -174,6 +174,24 @@ function MathClz32(x) { } +//ES6 draft 09-27-13, section 20.2.2.9. +function MathCbrt(x) { + return %Math_cbrt(TO_NUMBER_INLINE(x)); +} + + +//ES6 draft 09-27-13, section 20.2.2.14. +function MathExpm1(x) { + return %Math_expm1(TO_NUMBER_INLINE(x)); +} + + +//ES6 draft 09-27-13, section 20.2.2.20. +function MathLog1p(x) { + return %Math_log1p(TO_NUMBER_INLINE(x)); +} + + function ExtendMath() { %CheckIsBootstrapping(); @@ -191,7 +209,10 @@ function ExtendMath() { "log2", MathLog2, "hypot", MathHypot, "fround", MathFround, - "clz32", MathClz32 + "clz32", MathClz32, + "cbrt", MathCbrt, + "log1p", MathLog1p, + "expm1", MathExpm1 )); } diff --git a/src/runtime.cc b/src/runtime.cc index 50621a9..3b56d32 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -7647,33 +7647,110 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StringCompare) { } -RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_acos) { +#define RUNTIME_UNARY_MATH(NAME) \ +RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_##NAME) { \ + SealHandleScope shs(isolate); \ + ASSERT(args.length() == 1); \ + isolate->counters()->math_##NAME()->Increment(); \ + CONVERT_DOUBLE_ARG_CHECKED(x, 0); \ + return isolate->heap()->AllocateHeapNumber(std::NAME(x)); \ +} + +RUNTIME_UNARY_MATH(acos) +RUNTIME_UNARY_MATH(asin) +RUNTIME_UNARY_MATH(atan) +RUNTIME_UNARY_MATH(log) +#undef RUNTIME_UNARY_MATH + + +// Cube root approximation, refer to: http://metamerist.com/cbrt/cbrt.htm +// Using initial approximation adapted from Kahan's cbrt and 4 iterations +// of Newton's method. +inline double CubeRootNewtonIteration(double approx, double x) { + return (1.0 / 3.0) * (x / (approx * approx) + 2 * approx); +} + + +inline double CubeRoot(double x) { + static const uint64_t magic = V8_2PART_UINT64_C(0x2A9F7893, 00000000); + uint64_t xhigh = double_to_uint64(x); + double approx = uint64_to_double(xhigh / 3 + magic); + + approx = CubeRootNewtonIteration(approx, x); + approx = CubeRootNewtonIteration(approx, x); + approx = CubeRootNewtonIteration(approx, x); + return CubeRootNewtonIteration(approx, x); +} + + +RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_cbrt) { SealHandleScope shs(isolate); ASSERT(args.length() == 1); - isolate->counters()->math_acos()->Increment(); - CONVERT_DOUBLE_ARG_CHECKED(x, 0); - return isolate->heap()->AllocateHeapNumber(std::acos(x)); + if (x == 0 || std::isinf(x)) return args[0]; + double result = (x > 0) ? CubeRoot(x) : -CubeRoot(-x); + return isolate->heap()->AllocateHeapNumber(result); } -RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_asin) { +RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_log1p) { SealHandleScope shs(isolate); ASSERT(args.length() == 1); - isolate->counters()->math_asin()->Increment(); - CONVERT_DOUBLE_ARG_CHECKED(x, 0); - return isolate->heap()->AllocateHeapNumber(std::asin(x)); + + double x_abs = std::fabs(x); + // Use Taylor series to approximate. With y = x + 1; + // log(y) at 1 == log(1) + log'(1)(y-1)/1! + log''(1)(y-1)^2/2! + ... + // == 0 + x - x^2/2 + x^3/3 ... + // The closer x is to 0, the fewer terms are required. + static const double threshold_2 = 1.0 / 0x00800000; + static const double threshold_3 = 1.0 / 0x00008000; + static const double threshold_7 = 1.0 / 0x00000080; + + double result; + if (x_abs < threshold_2) { + result = x * (1.0/1.0 - x * 1.0/2.0); + } else if (x_abs < threshold_3) { + result = x * (1.0/1.0 - x * (1.0/2.0 - x * (1.0/3.0))); + } else if (x_abs < threshold_7) { + result = x * (1.0/1.0 - x * (1.0/2.0 - x * ( + 1.0/3.0 - x * (1.0/4.0 - x * ( + 1.0/5.0 - x * (1.0/6.0 - x * ( + 1.0/7.0))))))); + } else { // Use regular log if not close enough to 0. + result = std::log(1.0 + x); + } + return isolate->heap()->AllocateHeapNumber(result); } -RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_atan) { +RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_expm1) { SealHandleScope shs(isolate); ASSERT(args.length() == 1); - isolate->counters()->math_atan()->Increment(); - CONVERT_DOUBLE_ARG_CHECKED(x, 0); - return isolate->heap()->AllocateHeapNumber(std::atan(x)); + + double x_abs = std::fabs(x); + // Use Taylor series to approximate. + // exp(x) - 1 at 0 == -1 + exp(0) + exp'(0)*x/1! + exp''(0)*x^2/2! + ... + // == x/1! + x^2/2! + x^3/3! + ... + // The closer x is to 0, the fewer terms are required. + static const double threshold_2 = 1.0 / 0x00400000; + static const double threshold_3 = 1.0 / 0x00004000; + static const double threshold_6 = 1.0 / 0x00000040; + + double result; + if (x_abs < threshold_2) { + result = x * (1.0/1.0 + x * (1.0/2.0)); + } else if (x_abs < threshold_3) { + result = x * (1.0/1.0 + x * (1.0/2.0 + x * (1.0/6.0))); + } else if (x_abs < threshold_6) { + result = x * (1.0/1.0 + x * (1.0/2.0 + x * ( + 1.0/6.0 + x * (1.0/24.0 + x * ( + 1.0/120.0 + x * (1.0/720.0)))))); + } else { // Use regular exp if not close enough to 0. + result = std::exp(x) - 1.0; + } + return isolate->heap()->AllocateHeapNumber(result); } @@ -7724,16 +7801,6 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_floor) { } -RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_log) { - SealHandleScope shs(isolate); - ASSERT(args.length() == 1); - isolate->counters()->math_log()->Increment(); - - CONVERT_DOUBLE_ARG_CHECKED(x, 0); - return isolate->heap()->AllocateHeapNumber(std::log(x)); -} - - // Slow version of Math.pow. We check for fast paths for special cases. // Used if SSE2/VFP3 is not available. RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_pow) { diff --git a/src/runtime.h b/src/runtime.h index 6e79dbe..8fe6db0 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -177,14 +177,17 @@ namespace internal { F(Math_acos, 1, 1) \ F(Math_asin, 1, 1) \ F(Math_atan, 1, 1) \ - F(Math_atan2, 2, 1) \ + F(Math_log, 1, 1) \ + F(Math_cbrt, 1, 1) \ + F(Math_log1p, 1, 1) \ + F(Math_expm1, 1, 1) \ + F(Math_sqrt, 1, 1) \ F(Math_exp, 1, 1) \ F(Math_floor, 1, 1) \ - F(Math_log, 1, 1) \ F(Math_pow, 2, 1) \ F(Math_pow_cfunction, 2, 1) \ + F(Math_atan2, 2, 1) \ F(RoundNumber, 1, 1) \ - F(Math_sqrt, 1, 1) \ F(Math_fround, 1, 1) \ \ /* Regular expressions */ \ diff --git a/test/mjsunit/harmony/math-cbrt.js b/test/mjsunit/harmony/math-cbrt.js new file mode 100644 index 0000000..83d9eb5 --- /dev/null +++ b/test/mjsunit/harmony/math-cbrt.js @@ -0,0 +1,27 @@ +// Copyright 2014 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. + +// Flags: --harmony-maths + +assertTrue(isNaN(Math.cbrt(NaN))); +assertTrue(isNaN(Math.cbrt(function() {}))); +assertTrue(isNaN(Math.cbrt({ toString: function() { return NaN; } }))); +assertTrue(isNaN(Math.cbrt({ valueOf: function() { return "abc"; } }))); +assertEquals("Infinity", String(1/Math.cbrt(0))); +assertEquals("-Infinity", String(1/Math.cbrt(-0))); +assertEquals("Infinity", String(Math.cbrt(Infinity))); +assertEquals("-Infinity", String(Math.cbrt(-Infinity))); + +for (var i = 1E-100; i < 1E100; i *= Math.PI) { + assertEqualsDelta(i, Math.cbrt(i*i*i), i * 1E-15); +} + +for (var i = -1E-100; i > -1E100; i *= Math.E) { + assertEqualsDelta(i, Math.cbrt(i*i*i), -i * 1E-15); +} + +// Let's be exact at least for small integers. +for (var i = 2; i < 10000; i++) { + assertEquals(i, Math.cbrt(i*i*i)); +} diff --git a/test/mjsunit/harmony/math-expm1.js b/test/mjsunit/harmony/math-expm1.js new file mode 100644 index 0000000..de915c0 --- /dev/null +++ b/test/mjsunit/harmony/math-expm1.js @@ -0,0 +1,38 @@ +// Copyright 2014 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. + +// Flags: --harmony-maths --no-fast-math + +assertTrue(isNaN(Math.expm1(NaN))); +assertTrue(isNaN(Math.expm1(function() {}))); +assertTrue(isNaN(Math.expm1({ toString: function() { return NaN; } }))); +assertTrue(isNaN(Math.expm1({ valueOf: function() { return "abc"; } }))); +assertEquals("Infinity", String(1/Math.expm1(0))); +assertEquals("-Infinity", String(1/Math.expm1(-0))); +assertEquals("Infinity", String(Math.expm1(Infinity))); +assertEquals(-1, Math.expm1(-Infinity)); + +for (var x = 0.1; x < 700; x += 0.1) { + var expected = Math.exp(x) - 1; + assertEqualsDelta(expected, Math.expm1(x), expected * 1E-14); + expected = Math.exp(-x) - 1; + assertEqualsDelta(expected, Math.expm1(-x), -expected * 1E-14); +} + +// Values close to 0: +// Use six terms of Taylor expansion at 0 for exp(x) as test expectation: +// exp(x) - 1 == exp(0) + exp(0) * x + x * x / 2 + ... - 1 +// == x + x * x / 2 + x * x * x / 6 + ... +function expm1(x) { + return x * (1 + x * (1/2 + x * ( + 1/6 + x * (1/24 + x * ( + 1/120 + x * (1/720 + x * ( + 1/5040 + x * (1/40320 + x*( + 1/362880 + x * (1/3628800)))))))))); +} + +for (var x = 1E-1; x > 1E-300; x *= 0.8) { + var expected = expm1(x); + assertEqualsDelta(expected, Math.expm1(x), expected * 1E-14); +} diff --git a/test/mjsunit/harmony/math-log1p.js b/test/mjsunit/harmony/math-log1p.js new file mode 100644 index 0000000..eefea6e --- /dev/null +++ b/test/mjsunit/harmony/math-log1p.js @@ -0,0 +1,41 @@ +// Copyright 2014 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. + +// Flags: --harmony-maths + +assertTrue(isNaN(Math.log1p(NaN))); +assertTrue(isNaN(Math.log1p(function() {}))); +assertTrue(isNaN(Math.log1p({ toString: function() { return NaN; } }))); +assertTrue(isNaN(Math.log1p({ valueOf: function() { return "abc"; } }))); +assertEquals("Infinity", String(1/Math.log1p(0))); +assertEquals("-Infinity", String(1/Math.log1p(-0))); +assertEquals("Infinity", String(Math.log1p(Infinity))); +assertEquals("-Infinity", String(Math.log1p(-1))); +assertTrue(isNaN(Math.log1p(-2))); +assertTrue(isNaN(Math.log1p(-Infinity))); + +for (var x = 1E300; x > 1E-1; x *= 0.8) { + var expected = Math.log(x + 1); + assertEqualsDelta(expected, Math.log1p(x), expected * 1E-14); +} + +// Values close to 0: +// Use Taylor expansion at 1 for log(x) as test expectation: +// log1p(x) == log(x + 1) == 0 + x / 1 - x^2 / 2 + x^3 / 3 - ... +function log1p(x) { + var terms = []; + var prod = x; + for (var i = 1; i <= 20; i++) { + terms.push(prod / i); + prod *= -x; + } + var sum = 0; + while (terms.length > 0) sum += terms.pop(); + return sum; +} + +for (var x = 1E-1; x > 1E-300; x *= 0.8) { + var expected = log1p(x); + assertEqualsDelta(expected, Math.log1p(x), expected * 1E-14); +} -- 2.7.4