Implement Math.sin, cos and tan using table lookup and spline interpolation.

R=jkummerow@chromium.org
BUG=

Review URL: https://codereview.chromium.org/50563003

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17594 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
yangguo@chromium.org 2013-11-08 13:10:39 +00:00
parent 412af94bbb
commit 063b7c4ebb
12 changed files with 283 additions and 208 deletions

View File

@ -3726,42 +3726,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathSin(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::SIN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(r0);
}
void FullCodeGenerator::EmitMathCos(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::COS,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(r0);
}
void FullCodeGenerator::EmitMathTan(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::TAN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(r0);
}
void FullCodeGenerator::EmitMathLog(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::LOG,
@ -3784,6 +3748,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) {
// Load the argument on the stack and call the runtime function.
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallRuntime(Runtime::kMath_floor, 1);
context()->Plug(r0);
}
void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() >= 2);

View File

@ -6353,6 +6353,11 @@ int HOptimizedGraphBuilder::InliningAstSize(Handle<JSFunction> target) {
Handle<JSFunction> caller = current_info()->closure();
Handle<SharedFunctionInfo> target_shared(target->shared());
// Always inline builtins marked for inlining.
if (target->IsBuiltin()) {
return target_shared->inline_builtin() ? 0 : kNotInlinable;
}
// Do a quick check on source code length to avoid parsing large
// inlining candidates.
if (target_shared->SourceSize() >
@ -6362,7 +6367,7 @@ int HOptimizedGraphBuilder::InliningAstSize(Handle<JSFunction> target) {
}
// Target must be inlineable.
if (!target->IsInlineable()) {
if (!target_shared->IsInlineable()) {
TraceInline(target, caller, "target not inlineable");
return kNotInlinable;
}
@ -6742,9 +6747,6 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinFunctionCall(Call* expr,
case kMathAbs:
case kMathSqrt:
case kMathLog:
case kMathSin:
case kMathCos:
case kMathTan:
if (expr->arguments()->length() == 1) {
HValue* argument = Pop();
Drop(1); // Receiver.
@ -6823,9 +6825,6 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall(
case kMathAbs:
case kMathSqrt:
case kMathLog:
case kMathSin:
case kMathCos:
case kMathTan:
if (argument_count == 2 && check_type == RECEIVER_MAP_CHECK) {
AddCheckConstantFunction(expr->holder(), receiver, receiver_map);
HValue* argument = Pop();
@ -9145,36 +9144,6 @@ void HOptimizedGraphBuilder::GenerateMathPow(CallRuntime* call) {
}
void HOptimizedGraphBuilder::GenerateMathSin(CallRuntime* call) {
ASSERT_EQ(1, call->arguments()->length());
CHECK_ALIVE(VisitArgumentList(call->arguments()));
HCallStub* result = New<HCallStub>(CodeStub::TranscendentalCache, 1);
result->set_transcendental_type(TranscendentalCache::SIN);
Drop(1);
return ast_context()->ReturnInstruction(result, call->id());
}
void HOptimizedGraphBuilder::GenerateMathCos(CallRuntime* call) {
ASSERT_EQ(1, call->arguments()->length());
CHECK_ALIVE(VisitArgumentList(call->arguments()));
HCallStub* result = New<HCallStub>(CodeStub::TranscendentalCache, 1);
result->set_transcendental_type(TranscendentalCache::COS);
Drop(1);
return ast_context()->ReturnInstruction(result, call->id());
}
void HOptimizedGraphBuilder::GenerateMathTan(CallRuntime* call) {
ASSERT_EQ(1, call->arguments()->length());
CHECK_ALIVE(VisitArgumentList(call->arguments()));
HCallStub* result = New<HCallStub>(CodeStub::TranscendentalCache, 1);
result->set_transcendental_type(TranscendentalCache::TAN);
Drop(1);
return ast_context()->ReturnInstruction(result, call->id());
}
void HOptimizedGraphBuilder::GenerateMathLog(CallRuntime* call) {
ASSERT_EQ(1, call->arguments()->length());
CHECK_ALIVE(VisitArgumentList(call->arguments()));
@ -9194,6 +9163,15 @@ void HOptimizedGraphBuilder::GenerateMathSqrt(CallRuntime* call) {
}
void HOptimizedGraphBuilder::GenerateMathFloor(CallRuntime* call) {
ASSERT(call->arguments()->length() == 1);
CHECK_ALIVE(VisitForValue(call->arguments()->at(0)));
HValue* value = Pop();
HInstruction* result = New<HUnaryMathOperation>(value, kMathFloor);
return ast_context()->ReturnInstruction(result, call->id());
}
// Check whether two RegExps are equivalent
void HOptimizedGraphBuilder::GenerateIsRegExpEquivalent(CallRuntime* call) {
return Bailout(kInlinedRuntimeFunctionIsRegExpEquivalent);

View File

@ -3693,42 +3693,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathSin(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::SIN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(eax);
}
void FullCodeGenerator::EmitMathCos(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::COS,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(eax);
}
void FullCodeGenerator::EmitMathTan(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::TAN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(eax);
}
void FullCodeGenerator::EmitMathLog(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::LOG,
@ -3751,6 +3715,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) {
// Load the argument on the stack and call the runtime function.
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallRuntime(Runtime::kMath_floor, 1);
context()->Plug(eax);
}
void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() >= 2);

View File

@ -79,7 +79,7 @@ function MathCeil(x) {
// ECMA 262 - 15.8.2.7
function MathCos(x) {
return %_MathCos(TO_NUMBER_INLINE(x));
return MathCosImpl(x);
}
// ECMA 262 - 15.8.2.8
@ -185,7 +185,7 @@ function MathRound(x) {
// ECMA 262 - 15.8.2.16
function MathSin(x) {
return %_MathSin(TO_NUMBER_INLINE(x));
return MathSinImpl(x);
}
// ECMA 262 - 15.8.2.17
@ -195,7 +195,7 @@ function MathSqrt(x) {
// ECMA 262 - 15.8.2.18
function MathTan(x) {
return %_MathTan(TO_NUMBER_INLINE(x));
return MathSinImpl(x) / MathCosImpl(x);
}
// Non-standard extension.
@ -204,6 +204,68 @@ function MathImul(x, y) {
}
var MathSinImpl = function(x) {
InitTrigonometricFunctions();
return MathSinImpl(x);
}
var MathCosImpl = function(x) {
InitTrigonometricFunctions();
return MathCosImpl(x);
}
function InitTrigonometricFunctions() {
var samples = 2048; // Table size.
var pi = 3.1415926535897932;
var pi_half = pi / 2;
var inverse_pi_half = 1 / pi_half;
var two_pi = pi * 2;
var interval = pi_half / samples;
var inverse_interval = samples / pi_half;
var table_sin = new global.Float64Array(samples + 2);
var table_cos_interval = new global.Float64Array(samples + 2);
%PopulateTrigonometricTable(table_sin, table_cos_interval, samples);
// This implements the following algorithm.
// 1) Multiplication takes care of to-number conversion.
// 2) Reduce x to the first quadrant [0, pi/2].
// Conveniently enough, in case of +/-Infinity, we get NaN.
// 3) Replace x by (pi/2-x) if x was in the 2nd or 4th quadrant.
// 4) Do a table lookup for the closest samples to the left and right of x.
// 5) Find the derivatives at those sampling points by table lookup:
// dsin(x)/dx = cos(x) = sin(pi/2-x) for x in [0, pi/2].
// 6) Use cubic spline interpolation to approximate sin(x).
// 7) Negate the result if x was in the 3rd or 4th quadrant.
// 8) Get rid of -0 by adding 0.
MathSinImpl = function(x) {
var multiple = %_MathFloor(x * inverse_pi_half);
x = (multiple & 1) * pi_half +
(1 - ((multiple & 1) << 1)) * (x - multiple * pi_half);
var double_index = x * inverse_interval;
var index = double_index | 0;
var t1 = double_index - index;
var t2 = 1 - t1;
var y1 = table_sin[index];
var y2 = table_sin[index + 1];
var dy = y2 - y1;
return (t2 * y1 + t1 * y2 +
t1 * t2 * ((table_cos_interval[index] - dy) * t2 +
(dy - table_cos_interval[index + 1]) * t1)) *
(1 - (multiple & 2)) + 0;
};
MathCosImpl = function(x) {
return MathSinImpl(x + pi_half);
};
%SetInlineBuiltinFlag(MathSinImpl);
%SetInlineBuiltinFlag(MathCosImpl);
}
// -------------------------------------------------------------------
function SetUpMath() {
@ -276,6 +338,10 @@ function SetUpMath() {
"min", MathMin,
"imul", MathImul
));
%SetInlineBuiltinFlag(MathSin);
%SetInlineBuiltinFlag(MathCos);
%SetInlineBuiltinFlag(MathTan);
}
SetUpMath();

View File

@ -3762,45 +3762,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathSin(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::SIN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ mov(a0, result_register()); // Stub requires parameter in a0 and on tos.
__ CallStub(&stub);
context()->Plug(v0);
}
void FullCodeGenerator::EmitMathCos(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::COS,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ mov(a0, result_register()); // Stub requires parameter in a0 and on tos.
__ CallStub(&stub);
context()->Plug(v0);
}
void FullCodeGenerator::EmitMathTan(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::TAN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ mov(a0, result_register()); // Stub requires parameter in a0 and on tos.
__ CallStub(&stub);
context()->Plug(v0);
}
void FullCodeGenerator::EmitMathLog(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::LOG,
@ -3824,6 +3785,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) {
// Load the argument on the stack and call the runtime function.
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallRuntime(Runtime::kMath_floor, 1);
context()->Plug(v0);
}
void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() >= 2);

View File

@ -4807,6 +4807,8 @@ bool SharedFunctionInfo::is_classic_mode() {
BOOL_GETTER(SharedFunctionInfo, compiler_hints, is_extended_mode,
kExtendedModeFunction)
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, native, kNative)
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, inline_builtin,
kInlineBuiltin)
BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints,
name_should_print_as_anonymous,
kNameShouldPrintAsAnonymous)
@ -4867,6 +4869,7 @@ Code* SharedFunctionInfo::code() {
void SharedFunctionInfo::set_code(Code* value, WriteBarrierMode mode) {
ASSERT(value->kind() != Code::OPTIMIZED_FUNCTION);
WRITE_FIELD(this, kCodeOffset, value);
CONDITIONAL_WRITE_BARRIER(value->GetHeap(), this, kCodeOffset, value, mode);
}

View File

@ -9747,20 +9747,6 @@ bool JSFunction::EnsureCompiled(Handle<JSFunction> function,
}
bool JSFunction::IsInlineable() {
if (IsBuiltin()) return false;
SharedFunctionInfo* shared_info = shared();
// Check that the function has a script associated with it.
if (!shared_info->script()->IsScript()) return false;
if (shared_info->optimization_disabled()) return false;
Code* code = shared_info->code();
if (code->kind() == Code::OPTIMIZED_FUNCTION) return true;
// If we never ran this (unlikely) then lets try to optimize it.
if (code->kind() != Code::FUNCTION) return true;
return code->optimizable();
}
void JSObject::OptimizeAsPrototype(Handle<JSObject> object) {
if (object->IsGlobalObject()) return;
@ -10029,6 +10015,16 @@ Handle<Object> SharedFunctionInfo::GetSourceCode() {
}
bool SharedFunctionInfo::IsInlineable() {
// Check that the function has a script associated with it.
if (!script()->IsScript()) return false;
if (optimization_disabled()) return false;
// If we never ran this (unlikely) then lets try to optimize it.
if (code()->kind() != Code::FUNCTION) return true;
return code()->optimizable();
}
int SharedFunctionInfo::SourceSize() {
return end_position() - start_position();
}

View File

@ -6781,6 +6781,9 @@ class SharedFunctionInfo: public HeapObject {
// global object.
DECL_BOOLEAN_ACCESSORS(native)
// Indicate that this builtin needs to be inlined in crankshaft.
DECL_BOOLEAN_ACCESSORS(inline_builtin)
// Indicates that the function was created by the Function function.
// Though it's anonymous, toString should treat it as if it had the name
// "anonymous". We don't set the name itself so that the system does not
@ -6870,6 +6873,9 @@ class SharedFunctionInfo: public HeapObject {
set_dont_optimize(reason != kNoReason);
}
// Check whether or not this function is inlineable.
bool IsInlineable();
// Source size of this function.
int SourceSize();
@ -7020,6 +7026,7 @@ class SharedFunctionInfo: public HeapObject {
kUsesArguments,
kHasDuplicateParameters,
kNative,
kInlineBuiltin,
kBoundFunction,
kIsAnonymous,
kNameShouldPrintAsAnonymous,
@ -7246,9 +7253,6 @@ class JSFunction: public JSObject {
// Tells whether or not the function is on the concurrent recompilation queue.
inline bool IsInRecompileQueue();
// Check whether or not this function is inlineable.
bool IsInlineable();
// [literals_or_bindings]: Fixed array holding either
// the materialized literals or the bindings of a bound function.
//

View File

@ -5379,6 +5379,20 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetNativeFlag) {
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_SetInlineBuiltinFlag) {
SealHandleScope shs(isolate);
RUNTIME_ASSERT(args.length() == 1);
Handle<Object> object = args.at<Object>(0);
if (object->IsJSFunction()) {
JSFunction* func = JSFunction::cast(*object);
func->shared()->set_inline_builtin(true);
}
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_StoreArrayLiteralElement) {
HandleScope scope(isolate);
RUNTIME_ASSERT(args.length() == 5);
@ -7802,6 +7816,35 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_tan) {
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_PopulateTrigonometricTable) {
HandleScope scope(isolate);
ASSERT(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, sin_table, 0);
CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, cos_table, 1);
CONVERT_SMI_ARG_CHECKED(samples, 2);
RUNTIME_ASSERT(sin_table->type() == kExternalDoubleArray);
RUNTIME_ASSERT(cos_table->type() == kExternalDoubleArray);
double* sin_buffer = reinterpret_cast<double*>(
JSArrayBuffer::cast(sin_table->buffer())->backing_store());
double* cos_buffer = reinterpret_cast<double*>(
JSArrayBuffer::cast(cos_table->buffer())->backing_store());
static const double pi_half = 3.1415926535897932 / 2;
double interval = pi_half / samples;
for (int i = 0; i < samples + 1; i++) {
double sample = sin(i * interval);
sin_buffer[i] = sample;
cos_buffer[samples - i] = sample * interval;
}
// Fill this to catch out of bound accesses when calculating Math.sin(pi/2).
sin_buffer[samples + 1] = sin(pi_half + interval);
cos_buffer[samples + 1] = cos(pi_half + interval) * interval;
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_DateMakeDay) {
SealHandleScope shs(isolate);
ASSERT(args.length() == 2);

View File

@ -106,6 +106,7 @@ namespace internal {
F(AllocateInOldPointerSpace, 1, 1) \
F(AllocateInOldDataSpace, 1, 1) \
F(SetNativeFlag, 1, 1) \
F(SetInlineBuiltinFlag, 1, 1) \
F(StoreArrayLiteralElement, 5, 1) \
F(DebugCallbackSupportsStepping, 1, 1) \
F(DebugPrepareStepInIfStepping, 1, 1) \
@ -189,6 +190,7 @@ namespace internal {
F(Math_sin, 1, 1) \
F(Math_sqrt, 1, 1) \
F(Math_tan, 1, 1) \
F(PopulateTrigonometricTable, 3, 1) \
\
/* Regular expressions */ \
F(RegExpCompile, 3, 1) \
@ -625,11 +627,9 @@ namespace internal {
F(IsSpecObject, 1, 1) \
F(IsStringWrapperSafeForDefaultValueOf, 1, 1) \
F(MathPow, 2, 1) \
F(MathSin, 1, 1) \
F(MathCos, 1, 1) \
F(MathTan, 1, 1) \
F(MathSqrt, 1, 1) \
F(MathLog, 1, 1) \
F(MathFloor, 1, 1) \
F(IsRegExpEquivalent, 2, 1) \
F(HasCachedArrayIndex, 1, 1) \
F(GetCachedArrayIndex, 1, 1) \

View File

@ -3650,42 +3650,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathSin(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::SIN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(rax);
}
void FullCodeGenerator::EmitMathCos(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::COS,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(rax);
}
void FullCodeGenerator::EmitMathTan(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::TAN,
TranscendentalCacheStub::TAGGED);
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallStub(&stub);
context()->Plug(rax);
}
void FullCodeGenerator::EmitMathLog(CallRuntime* expr) {
// Load the argument on the stack and call the stub.
TranscendentalCacheStub stub(TranscendentalCache::LOG,
@ -3708,6 +3672,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) {
}
void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) {
// Load the argument on the stack and call the runtime function.
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 1);
VisitForStackValue(args->at(0));
__ CallRuntime(Runtime::kMath_floor, 1);
context()->Plug(rax);
}
void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() >= 2);

View File

@ -42,9 +42,101 @@ cosTest();
// By accident, the slow case for sine and cosine were both sine at
// some point. This is a regression test for that issue.
var x = Math.pow(2, 70);
var x = Math.pow(2, 30);
assertTrue(Math.sin(x) != Math.cos(x));
// Ensure that sine and log are not the same.
x = 0.5;
assertTrue(Math.sin(x) != Math.log(x));
// Test against approximation by series.
var factorial = [1];
var accuracy = 50;
for (var i = 1; i < accuracy; i++) {
factorial[i] = factorial[i-1] * i;
}
// We sum up in the reverse order for higher precision, as we expect the terms
// to grow smaller for x reasonably close to 0.
function precision_sum(array) {
var result = 0;
while (array.length > 0) {
result += array.pop();
}
return result;
}
function sin(x) {
var sign = 1;
var x2 = x*x;
var terms = [];
for (var i = 1; i < accuracy; i += 2) {
terms.push(sign * x / factorial[i]);
x *= x2;
sign *= -1;
}
return precision_sum(terms);
}
function cos(x) {
var sign = -1;
var x2 = x*x;
x = x2;
var terms = [1];
for (var i = 2; i < accuracy; i += 2) {
terms.push(sign * x / factorial[i]);
x *= x2;
sign *= -1;
}
return precision_sum(terms);
}
function abs_error(fun, ref, x) {
return Math.abs(ref(x) - fun(x));
}
var test_inputs = [];
for (var i = -10000; i < 10000; i += 177) test_inputs.push(i/1257);
var epsilon = 0.000001;
test_inputs.push(0);
test_inputs.push(0 + epsilon);
test_inputs.push(0 - epsilon);
test_inputs.push(Math.PI/2);
test_inputs.push(Math.PI/2 + epsilon);
test_inputs.push(Math.PI/2 - epsilon);
test_inputs.push(Math.PI);
test_inputs.push(Math.PI + epsilon);
test_inputs.push(Math.PI - epsilon);
test_inputs.push(- 2*Math.PI);
test_inputs.push(- 2*Math.PI + epsilon);
test_inputs.push(- 2*Math.PI - epsilon);
var squares = [];
for (var i = 0; i < test_inputs.length; i++) {
var x = test_inputs[i];
var err_sin = abs_error(Math.sin, sin, x);
var err_cos = abs_error(Math.cos, cos, x)
assertTrue(err_sin < 1E-13);
assertTrue(err_cos < 1E-13);
squares.push(err_sin*err_sin + err_cos*err_cos);
}
// Sum squares up by adding them pairwise, to avoid losing precision.
while (squares.length > 1) {
var reduced = [];
if (squares.length % 2 == 1) reduced.push(squares.pop());
// Remaining number of elements is even.
while(squares.length > 1) reduced.push(squares.pop() + squares.pop());
squares = reduced;
}
var err_rms = Math.sqrt(squares[0] / test_inputs.length / 2);
assertTrue(err_rms < 1E-14);
assertEquals(-1, Math.cos({ valueOf: function() { return Math.PI; } }));
assertEquals(0, Math.sin("0x00000"));
assertTrue(isNaN(Math.sin(Infinity)));
assertTrue(isNaN(Math.cos("-Infinity")));
assertEquals("Infinity", String(Math.tan(Math.PI/2)));
assertEquals("-Infinity", String(Math.tan(-Math.PI/2)));