Итак, функция Sin принимает радиус и возвращает синус этого радиуса.
Я слышал, что практически любая функция может быть заменена справочной таблицей.
Независимо от того, как я использую функцию sin, как я могу использовать свою собственную справочную таблицу вместо греха?
Давайте сделаем простой пример нашел здесь.
/* sin example */
#include <stdio.h> /* printf */
#include <math.h> /* sin */
int main ()
{
double param, result;
param = 0.53;
result = sin (param);
printf ("The sine of %f is %f.\n", param, result );
return 0;
}
Допустим, я хочу заменить там функцию sin на мою собственную справочную таблицу с 256 числами с плавающей запятой.
#define TableSize 256.f
float SinArray[TableSize];
for(int i = 0; i < TableSize; i++)
{
// sin
SinArray[i] = static_cast<float>(sin( (i/TableSize)*(2.f*m_pi) ));
}
Как теперь я могу использовать эту таблицу вместо замены функции sin.
Вы можете спросить: «Почему? Почему бы просто не использовать функцию греха?» ну, это потому, что я использую функцию sin, чтобы изменить форму аудиовхода. Если я могу использовать справочную таблицу, я могу создать пилообразные или треугольные формы, чтобы
Поскольку функция sin принимает радиус и возвращает синус этого радиуса, как я могу продублировать это действие с помощью справочной таблицы? Как мне вообще это сделать?
Вам нужно интерполировать между двумя значениями в таблице
псевдокод:
float sin(float radius) {
radius = radius modulo 2*m_pi;
int idx = floor(TableSize * radius/2*m_pi);
float r = TableSize * radius/2*m_pi - idx;
return SinArray[idx] * (1-r) + SinArray[idx+1] * r;
}
Сопоставьте радиусы через равные промежутки времени с их значениями греха, это ваша таблица. Если вы хотите его использовать, округлите радиус, значение которого вы хотите, до ближайшего ключа в вашей таблице — это значение вашего греха.
Или округлить радиус, для которого вы хотите получить значение, до ближайшего целого числа и индексировать в массив.
Не могу понять, почему вы делаете это на обычной / современной машине — sin()
это аппаратная инструкция.
Но ради интереса вы можете сделать что-то вроде этого:
const double tau = 2 * M_PI;
class Sin
{
public:
Sin(int tableSize = 256)
: tableSize_(tableSize)
{
initSinTable();
}
double operator()(double radians) const
{
double mapped = mapDomain(radians);
int idx = (int)floor(tableSize_ * mapped / tau);
double lambda = tableSize_ * mapped / tau - idx;
double lower = sinArray[idx] ;
double upper = 0;
if (idx < tableSize_ - 1)
upper = sinArray[idx + 1];
double res = lower * (1 - lambda) + upper *lambda;
return res;
}
private:
static double mapDomain(double d)
{
double mapped = fmod(d, tau);
if (mapped < 0)
mapped += tau;
return mapped;
}
int tableSize_;
std::vector<double> sinArray;
void initSinTable()
{
sinArray.resize(tableSize_);
for (int i = 0; i < tableSize_; ++i)
{
sinArray[i] = sin((i*1.0 / tableSize_)*tau);
}
}
};
int main()
{
Sin mySin(512);
std::cout << mySin(tau / 6) << std::endl;
std::cout << sin(tau / 6) << std::endl;
}
Что касается использования tau
, увидеть: Википедия
Вот еще одна реализация, обобщенная для повторного использования для произвольных функций:
#include <iostream>
#include <vector>
#include <cmath>
#define LOG(X) (std::cout << X << '\n')
template <typename Num>
class Lookup
{
public:
Lookup(Num (*func)(Num), Num from, Num to, size_t num_samples)
: from_(from), to_(to), x_(double(num_samples - 1) / (to - from))
{
for (size_t i = 0; i < num_samples; ++i)
{
Num num = (to - from) * i / (num_samples - 1);
Num sample = func(num);
LOG("[" << i << "] f(" << num << ") == " << sample);
samples_.push_back(sample);
}
}
Num operator()(Num n) const
{
size_t index = round((n - from_) * x_);
LOG("(" << n << ") ~@ [" << (n - from_) << " / " << (to_ - from_)
<< " * " << (samples_.size() - 1) << "] == [" << index << "] == "<< samples_.at(index) << "]");
return samples_.at(index);
}
private:
static Num round(Num n) { return std::floor(n + 0.5); }
std::vector<Num> samples_;
Num from_, to_, x_;
};
int main()
{
Lookup<double> lsin(std::sin, 0.0, 1.0, 21);
for (double d = 0; d <= 1.0; d += 0.1)
std::cout << d << ' ' << sin(d) << ' ' << lsin(d) << '\n';
}
Предназначен для float
или же double
типы — вероятно, не будет работать с чем-либо еще, учитывая использование floor
и деления, где преждевременное усечение до целых чисел было бы проблематичным.
«Таинственно» названный x_
рассчитывается для ускорения operator()(Num)
; это можно было бы рассчитать как сумму, на которую нужно разделить, но умножение иногда быстрее с точки зрения тактов.
Ведение журнала просто показывает, что оно работает как задумано:
[0] f(0) == 0
[1] f(0.05) == 0.0499792
[2] f(0.1) == 0.0998334
[3] f(0.15) == 0.149438
[4] f(0.2) == 0.198669
[5] f(0.25) == 0.247404
[6] f(0.3) == 0.29552
[7] f(0.35) == 0.342898
[8] f(0.4) == 0.389418
[9] f(0.45) == 0.434966
[10] f(0.5) == 0.479426
[11] f(0.55) == 0.522687
[12] f(0.6) == 0.564642
[13] f(0.65) == 0.605186
[14] f(0.7) == 0.644218
[15] f(0.75) == 0.681639
[16] f(0.8) == 0.717356
[17] f(0.85) == 0.75128
[18] f(0.9) == 0.783327
[19] f(0.95) == 0.813416
[20] f(1) == 0.841471
(0) ~@ [0 / 1 * 20] == [0] == 0]
0 0 0
(0.1) ~@ [0.1 / 1 * 20] == [2] == 0.0998334]
0.1 0.0998334 0.0998334
(0.2) ~@ [0.2 / 1 * 20] == [4] == 0.198669]
0.2 0.198669 0.198669
(0.3) ~@ [0.3 / 1 * 20] == [6] == 0.29552]
0.3 0.29552 0.29552
(0.4) ~@ [0.4 / 1 * 20] == [8] == 0.389418]
0.4 0.389418 0.389418
(0.5) ~@ [0.5 / 1 * 20] == [10] == 0.479426]
0.5 0.479426 0.479426
(0.6) ~@ [0.6 / 1 * 20] == [12] == 0.564642]
0.6 0.564642 0.564642
(0.7) ~@ [0.7 / 1 * 20] == [14] == 0.644218]
0.7 0.644218 0.644218
(0.8) ~@ [0.8 / 1 * 20] == [16] == 0.717356]
0.8 0.717356 0.717356
(0.9) ~@ [0.9 / 1 * 20] == [18] == 0.783327]
0.9 0.783327 0.783327
(1) ~@ [1 / 1 * 20] == [20] == 0.841471]
1 0.841471 0.841471