Math.sincos(x)?

# Robert Poor (7 years ago)

[Preamble: this is my first post to es-discuss -- if this isn't the right place to suggest language extensions, please let me know. Thanks. - rdp]

How often have you seen code that calls Math.sin() and Math.cos() with the same argument, e.g:

x = r * Math.cos(theta); y = r * Math.sin(theta);

? This trope is repeated for polar coordinates, complex arithmetic, FTTs, etc.

However, most implementations for cos(theta) actually generate sin(theta) as a "byproduct" (and vice-versa). Since trigonometric functions are relatively expensive, and this is a common pattern, wouldn't it make sense to define a function that returns cos(theta) and sin(theta) at the same time? A polyfill might look like this:

function sincos(theta) { return { sin: sin(theta), cos: cos(theta) }; }

(or whatever the preferred style is for returning multiple values), but a real implementation would make a single call to the underlying trigonometric routine.

# Алексей (7 years ago)

If you think that performance of cos is too big (did you measure it?) than just calculate cos from sin with the formula sin^2 + con^2 = 1

function sincos (a) {
    const sin = Math.sin(a)
    const cos = (1 - sin**2)**0.5
    return {sin, cos}
}

2017-06-22 21:02 GMT+03:00 Robert Poor <rdpoor at gmail.com>:

# Boris Zbarsky (7 years ago)

On 6/22/17 2:02 PM, Robert Poor wrote:

However, most implementations for cos(theta) actually generate sin(theta) as a "byproduct" (and vice-versa).

At first glance, the v8 implementation of cos typically uses a Taylor series and does not compute sin.

And the SpiderMonkey implementation of cos looks like it just calls the C standard library cos, so also doesn't compute sin(theta) in a way visible to it.

# Boris Zbarsky (7 years ago)

On 6/22/17 2:13 PM, Алексей wrote:

function sincos (a) { const sin = Math.sin(a) const cos = (1 - sin**2)**0.5

This will completely fail for a near pi/2. It will return zero instead of the correct small but nonzero value it should be returning.

# kdex (7 years ago)

Also the sign of the cos component is wrong for a bunch of inputs.

# Nicolas B. Pierron (7 years ago)

On Thu, Jun 22, 2017 at 6:02 PM, Robert Poor <rdpoor at gmail.com> wrote:

How often have you seen code that calls Math.sin() and Math.cos() with the same argument, e.g:

x = r * Math.cos(theta); y = r * Math.sin(theta);

? This trope is repeated for polar coordinates, complex arithmetic, FTTs, etc.

However, most implementations for cos(theta) actually generate sin(theta) as a "byproduct" (and vice-versa). Since trigonometric functions are relatively expensive, and this is a common pattern, wouldn't it make sense to define a function that returns cos(theta) and sin(theta) at the same time?

For your information, the optimizing compiler of SpiderMonkey already try to optimize consecutive calls to cos and sin by the sincos function when possible. I do not know if other optimizing compilers are doing this optimization as well, but I would expect that it would be quite easy to add.

searchfox.org/mozilla-central/rev/3291398f10dcbe192fb52e74974b172616c018aa/js/src/jit/Ion.cpp#1310

On Thu, Jun 22, 2017 at 6:02 PM, Robert Poor <rdpoor at gmail.com> wrote:

A polyfill might look like this:

function sincos(theta) { return { sin: sin(theta), cos: cos(theta) }; }

I would expect all engines to use escape analysis to remove this object allocation, when this function is inlined.

Until this code gets inlined, by allocating an object this code will add pressure to the nursery of the garbage collector. This will also add memory writes/reads to/from memory. Both are not desirable when you are doing math-intensive operations.

# Robert Poor (7 years ago)

I previously wrote:

most implementations for cos(theta) actually generate sin(theta) as a "byproduct"

As others several have pointed out, that's not really true. V8, for example, uses a (different) Taylor series approximation for sin and cos. But given the amount of work that goes into reducing theta to a small interval, it still might make sense. (See comment about SpiderMonkey below...)

@Nicolas:

SpiderMonkey already try to optimize consecutive calls to cos and sin

Very cool!

return { sin: sin(theta), cos: cos(theta) }; will add pressure to the

nursery of the garbage collector.

True -- forgive my ignorance, but is there a way to return multiple values that does not cause heap allocation? That was my intention.

Another possible optimization: cache theta with its interval-reduced equivalent. If you get a subsequent call with the same theta, you've already done half the work.

# Nicolas B. Pierron (7 years ago)

On Fri, Jun 23, 2017 at 10:38 AM, Robert Poor <rdpoor at gmail.com> wrote:

return { sin: sin(theta), cos: cos(theta) }; will add pressure to the nursery of the garbage collector.

True -- forgive my ignorance, but is there a way to return multiple values that does not cause heap allocation? That was my intention.

The only one I know, is by setting the properties of an object given as arguments, assuming that the property do exists, and that the object is reused for multiple calls.

Another possible optimization: cache theta with its interval-reduced equivalent. If you get a subsequent call with the same theta, you've already done half the work.

As far as I know the spec does not provide any mean to specify an acceptable precision to math operations so having an interval-reduced equivalent would not be compliant with the spec.

# Florian Bösch (7 years ago)

On Thu, Jun 22, 2017 at 8:02 PM, Robert Poor <rdpoor at gmail.com> wrote:

function sincos(theta) { return { sin: sin(theta), cos: cos(theta) }; }

Allocating, filling, accessing and GC'ing the return object will take more time than calling the underlying C library function which emits the machine code for the processor to consult his trigonometric functions.

# Robert Poor (7 years ago)

Allocating, filling, accessing and GC'ing the return object will take

more time than calling the underlying C

That seems like the nail in the coffin for this suggestion. Thanks. I don't have anything to add.