На днях увидел старую статейку, которую уже когда-то давно просматривал. Суть: чувак написал compile-time вычисление синусов. Вещь это полезная, ибо например, если я напишу int a = 2+2*2, умный компилятор поймет, что это константа и я имел в виду int a = 6. Но если я напишу double x = sin(0), то он уже ничего понимать не будет, ибо синусам не обучен. Они для него это – нечто сидящее в . Потому – compile-time синусы вещь в хозяйстве полезная и неплохо бы, если с ними часто приходится работать, было обучить компилятор их понимать и вычислять. Естественно, это можно сделать через шаблоны, что чувак и делает. Затем, он утверждает, что написал massively inlined версию FFT server, которая работает аж в 3 (три!!!) раза быстрее обычной (что само по себе очень сомнительно). Вот его код:
template
class Sine {
public:
static inline float sin()
{
return (I*2*M_PI/N) * SineSeries::accumulate();
}
};
// Compute J terms in the series expansion. K is the loop variable.
template
class SineSeries {
public:
enum { go = (K+1 != J) };
static inline float accumulate()
{
return 1-(I*2*M_PI/N)*(I*2*M_PI/N)/(2*K+2)/(2*K+3) *
SineSeries::accumulate();
}
};
// Specialization to terminate loop
template
class SineSeries {
public:
static inline float accumulate()
{ return 1; }
};
Создавать template … нельзя по понятным причинам (например, и одна специализация или разные?), потому параметр здесь передается в виде i*2pi/n.
Как вы можете видеть, синус здесь вычисляется через ряд sin x = x – x^3/3! + x^5/5! – x^7/7! + … В школе и институте меня всегда учили, что так считать синус очень-очень плохо. Собственно, чем такой способ плох можно увидеть вычислив не предложенные в статье Sine::sin(); а хотя бы Sine::sin(); Получим мы значение около 8.545132. Таким синус бывает только в военное время. Следовательно, надо либо увеличивать число членов в ряде (по дефолту оно 10), либо писать на темплейтах что-то типа (x mod 2*pi). Очутившись перед выбором, я конечно выбрал третий вариант. А именно: воспользовавшись школьной формулой

и применяя ее рекурсивно, уменьшать аргумент синуса до тех пор, пока он не станет малым, например 0.1. А для таких малых значений sin(x) = x. Для того, чтобы это реализвать, мне понадобилось проверять условие в малости. Набрав в гугле “compile time if c++” я нашел статью, в которой показано как это делать. В принципе способ очень простой, мог бы и сам догадаться. В итоге получился следующий код:
#define M_PI 3.141592
template
struct if_less {};
template
struct if_less //less than tolerance
{
template
static inline double st(void) { return (i*2*M_PI/n);}
};
template
struct if_less //greater than tolerance
{
template
static inline double st(void)
{
double s = sine_rec::calc();
return (3*s – 4*s*s*s);
}
};
template
struct sine_rec
{
static inline double calc()
{
return if_less::st();
}
};
который делает все то же самое, но быстрее и надежнее :)