cos - gehrigwilcox/C-Standard-Library GitHub Wiki

Cos(θ)

Cosine is the x position on the unit circle for a given angle.

{
  "width": 200,
  "height": 200,
  "data": {
    "sequence": {
      "start": 0,
      "stop": 6.28,
      "step": 0.01,
      "as": "theta"
    }
  },
  "transform": [
    {
      "calculate": "cos(datum.theta)",
      "as": "x"
    },
    {
      "calculate": "sin(datum.theta)",
      "as": "y"
    }
  ],
  "mark": "line",
  "encoding": {
    "x": {
      "type": "quantitative",
      "field": "x"
    },
    "y": {
      "field": "y",
      "type": "quantitative"
    }
  }
}

We need some way of calculating cosine. There really isn't a formula for cosine, but we do know how to estimate it. In calculus, there is something called a Taylor Series. It gives an estimate of the function at a given point based on how quickly that point changes. However, this estimate gets less accurate the further from the point you get.

$$Taylor Series = \sum_{n=0}^{\infty}-1^n*\frac{x^n}{n!}$$

  {
    "width": 300,
    "height": 150,
    "data": {
      "sequence": {
        "start": -4.1,
        "stop": 4.2,
        "step": 0.1,
        "as": "θ"
      }
    },
    "transform": [
      {
        "calculate": "cos(datum.θ)",
        "as": "cos(θ)"
      },
      {
        "calculate": "1-(datum.θ*datum.θ/2)+(datum.θ*datum.θ*datum.θ*datum.θ/24)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/720)+(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/20320)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/3628800)",
        "as": "Taylor Series(θ)"
      },
      {
        "fold": ["cos(θ)","Taylor Series(θ)"]
      }
    ],
    "mark": "line",
    "encoding": {
      "x": {
        "type": "quantitative",
        "field": "θ"
      },
      "y": {
        "field": "value",
        "type": "quantitative",
        "scale": {
          "domain": [-1,1]
        }
      },
      "color": {
        "field": "key",
        "type": "nominal",
        "title": null
      }
    }
  }

So we need to keep our input values close to 0 so our estimate is close to cosine.

Whats nice about angles is that eventually, they circle back on themselves. For instance, 0 radians is the same as 2π radians. This then means that cosine will repeat its pattern every 2π radians.

  {
    "width": 300,
    "height": 150,
    "data": {
      "sequence": {
        "start": -12.56,
        "stop": 12.56,
        "step": 0.01,
        "as": "θ"
      }
    },
    "transform": [
      {
        "calculate": "cos(datum.θ)",
        "as": "cos(θ)"
      },
      {
        "fold": ["cos(θ)"]
      }
    ],
    "mark": "line",
    "encoding": {
      "x": {
        "type": "quantitative",
        "field": "θ"
      },
      "y": {
        "field": "value",
        "type": "quantitative",
        "scale": {
          "domain": [-1,1]
        }
      },
      "color": {
        "field": "key",
        "type": "nominal",
        "title": null
      }
    }
  }

This is useful! Because cosine repeats itself every 2π, we just have to calculate from 0 to 2π and repeat the same pattern over and over again.

  {
    "width": 300,
    "height": 150,
    "data": {
      "sequence": {
        "start": 0,
        "stop": 6.48,
        "step": 0.01,
        "as": "θ"
      }
    },
    "transform": [
      {
        "calculate": "cos(datum.θ)",
        "as": "cos(θ)"
      },
      {
        "calculate": "1-(datum.θ*datum.θ/2)+(datum.θ*datum.θ*datum.θ*datum.θ/24)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/720)+(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/20320)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/3628800)",
        "as": "Taylor Series(θ)"
      },
      {
        "fold": ["cos(θ)","Taylor Series(θ)"]
      }
    ],
    "mark": "line",
    "encoding": {
      "x": {
        "type": "quantitative",
        "field": "θ"
      },
      "y": {
        "field": "value",
        "type": "quantitative",
        "scale": {
          "domain": [-1,1]
        }
      },
      "color": {
        "field": "key",
        "type": "nominal",
        "title": null
      }
    }
  }

However, like we said in the beginning, our estimate works best the closer we are to 0. With this in mind, we should probably center our input around zero. So, instead of θ going from 0 to 2π, it will go from -π to π.

  {
    "width": 300,
    "height": 150,
    "data": {
      "sequence": {
        "start": -3.14,
        "stop": 3.14,
        "step": 0.01,
        "as": "θ"
      }
    },
    "transform": [
      {
        "calculate": "cos(datum.θ)",
        "as": "cos(θ)"
      },
      {
        "calculate": "1-(datum.θ*datum.θ/2)+(datum.θ*datum.θ*datum.θ*datum.θ/24)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/720)+(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/20320)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/3628800)",
        "as": "Taylor Series(θ)"
      },
      {
        "fold": ["cos(θ)","Taylor Series(θ)"]
      }
    ],
    "mark": "line",
    "encoding": {
      "x": {
        "type": "quantitative",
        "field": "θ"
      },
      "y": {
        "field": "value",
        "type": "quantitative",
        "scale": {
          "domain": [-1,1]
        }
      },
      "color": {
        "field": "key",
        "type": "nominal",
        "title": null
      }
    }
  }

Much better! But we still got a problem. Near the end of our graph, our estimate is extremely bad. Can we do better? Of course we can! Another useful thing about circles is the fact that they are horizontally symmetrical. This means that we really only need half of cosine, and we can flip it upside down to get the other half.

  {
    "width": 300,
    "height": 150,
    "data": {
      "sequence": {
        "start": -3.14,
        "stop": 3.14,
        "step": 0.01,
        "as": "θ"
      }
    },
    "transform": [
      {
        "calculate": "abs(cos(datum.θ))",
        "as": "|cos(θ)|"
      },
      {
        "calculate": "cos(datum.θ)",
        "as": "cos(θ)"
      },
      {
        "fold": ["cos(θ)","|cos(θ)|"]
      }
    ],
    "mark": "line",
    "encoding": {
      "x": {
        "type": "quantitative",
        "field": "θ",
        "scale": {
          "domain": [-3.14,3.14]
        }
      },
      "y": {
        "field": "value",
        "type": "quantitative",
        "scale": {
          "domain": [-1,1]
        }
      },
      "color": {
        "field": "key",
        "type": "nominal",
        "title": null
      }
    }
  }

So now we only have θ from -π/2 to π/2. And looking at the graph, we are pretty spot on!

  {
    "width": 300,
    "height": 150,
    "data": {
      "sequence": {
        "start": -1.57,
        "stop": 1.57,
        "step": 0.01,
        "as": "θ"
      }
    },
    "transform": [
      {
        "calculate": "1-(datum.θ*datum.θ/2)+(datum.θ*datum.θ*datum.θ*datum.θ/24)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/720)+(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/20320)-(datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ*datum.θ/3628800)",
        "as": "Taylor Series(θ)"
      },
      {
        "calculate": "cos(datum.θ)",
        "as": "cos(θ)"
      },
      {
        "fold": ["cos(θ)","Taylor Series(θ)"]
      }
    ],
    "mark": "line",
    "encoding": {
      "x": {
        "type": "quantitative",
        "field": "θ",
        "scale": {
          "domain": [-1.57,1.57]
        }
      },
      "y": {
        "field": "value",
        "type": "quantitative",
        "scale": {
          "domain": [-1,1]
        }
      },
      "color": {
        "field": "key",
        "type": "nominal",
        "title": null
      }
    }
  }

So first, we have to figure out if we need to flip our estimation or not. It seems like we need to flip our estimation every π intervals, but starting at π/2. Mathematically, that would be

$$-1^{\left \lfloor \frac{\theta+\frac{\pi }{2}}{\pi } \right \rfloor}$$

-1 will flip our graph for us, but only when it is raised to an odd number. The floor rounding makes sure we only deal with whole numbers. Then, we want to make sure that we start flipping at π/2, so we add that to θ. Then, we want to flip every π intervals, so we divide everything by π.

Now all we have to do is make sure that θ is within -pi/2 and pi/2. We can do that with remainders.

$$mod(\theta+\frac{\pi}{2},\pi)-\frac{\pi}{2}$$

Remainders will tell us how far past a given number we went. In this case, we want to know how far past π we went. But since we want to be centered arount π, we first add π/2, figure out how far past π we went, and then subtract π/2.

All in all, this leaves us with

$$\cos(\theta)=-1^{\left \lfloor \frac{\theta+\frac{\pi}{2}}{\pi } \right \rfloor}*TaylorSeries(mod(\theta+\frac{\pi}{2},\pi)-\frac{\pi}{2})$$

Written into C

// Same as -1^(floor(x+pi/2)/pi)
int mul = (((int)((x+M_PI/2)/M_PI))%2 ? -1 : 1);

x=fmod(x+M_PI/2,M_PI)-M_PI/2;

// Performance gain
double xx = x*x;

// Same as mul * TaylorSeries(x)
return mul*(1-(xx/2)+((xx*xx)/24)-((xx*xx*xx)/720)+
  ((xx*xx*xx*xx)/40320)-((xx*xx*xx*xx*xx)/3628800));
⚠️ **GitHub.com Fallback** ⚠️