Comments 25
Мне кажется, что выпуклые функции синус и косинус можно аппроксимировать не только рядом Тейлора ( многочленом вида a*x + b*x^3 +c*x^5 ... ) , а обычной параболой поведенной через точки 1,2,3 как на рисунке
Кажется - это плохо.
Если нужно посчитать быстро и не очень точно, то лучше таблица + линейная интерполяция
Мк без математики, это как запорожец на соревнованиях формулы F1. Ну получили синус, а дальше-то что с ним делать? Оно конечно будет ехать, и даже с хорошей скоростью, но только по прямой линии. А чтобы проходить повороты - нужна математика.
https://github.com/AVI-crak/Rtos_cortex/blob/master/math_s.c#L356
Целью данной публикации попытка предложить способ расчета синуса и косинуса достаточно быстро
Насколько быстро? Для вычисления синуса придумано огромное число разных алгоритмов приближения. Если вы предлагаете свой вариант, то он должен быть чем-то лучше, например, быстрее других при заданных точности и расходе памяти. А для этого нужны бенчмарки.
Мне кажется, что выпуклые функции синус и косинус можно аппроксимировать ...
Математика – это точная наука, здесь не может ничего казаться. Как только вы определите критерии, сразу появится возможность будет вычислить, можно или нельзя.
Я не привожу кода на каком либо языке или псевдокода, дабы не засорять публикацию
У вас публикация и так короткая, можно было бы и добавить (ну или хотя бы выложить на github). Не то, что это сложно или лично мне интересно, но просто глаз резануло от такого заявления.
код моего метода расчета синуса и косинуса приведен в комментариях к https://habr.com/ru/post/659089/ + в конце скрипт для расчета коэффициентов в зависимости от числа интервалов на 0 ... пи/2
Мне кажется, линейной функцией будет еще быстрее :)
ПС: а можно и константой 0.5 :) Так будет практически мгновенно и довольно точно :)
пример функции косинус на golang интервал 0...90 градусов
интервал разбит на 4 равных отрезка
максимальное отклонение от настоящего косинуса на 85 градусах =
0.08715574274765812 - 0.0876323 = -0,000476557
// sin_cos project main.go
package main
import (
"fmt"
"math"
)
const pi32 float64 = math.Pi
func Cos4(ugol float32) float32 {
/// ugol от 0 до пи/2
var interval int8
var a float32
var b float32
var c float32
interval = int8((8 * float64(ugol)) / pi32)
switch interval {
case 0:
a = -0.4888191
b = -0.0018803
c = 1
case 1:
a = -0.4144009
b = -0.0638028
c = 1.0128406
case 2:
a = -0.2768938
b = -0.2824576
c = 1.0997504
case 3:
a = -0.0972322
b = -0.7072145
c = 1.3508006
default:
a = 0
b = 0
c = 0
}
return ugol*(a*ugol+b) + c
}
func main() {
var arg1 float64
for ii := 0; ii <= 90; ii++ {
arg1 = float64(ii) * pi32 / float64(180) ///, cos(1)
fmt.Print(ii, "\t", arg1, "\t", math.Cos(arg1))
fmt.Print("\t", Cos4(float32(arg1)))
fmt.Println("\t", 1000*(float32(math.Cos(arg1))-Cos4(float32(arg1))))
}
}
cos=x=>1-.4053*x*x; // грязная апроксимация косинуса (для +-Pi/2) максимальная погрешность 5%
sin=x=>(1.27325-0.40529*Math.abs(x))*x //погрешность 5.5%
Помниться забавы ради развлекался изучал что можно "выжать" из второй степени без всяких разбиений - первая проблема собственно что парабола относительно совпадает с косинусом до 90 градусов если ее просто отмасштабировать получается полином `cos=x=>1-.4053*x*x`.
Через формулы приведения можно бы получить то же и для синуса, но в такой формуле появляется перемена знака... и нужен был как бы назвать "инверсный квадрат" - при возведении в который всегда получается отрицательный знак... тогда бы получалось 2й степенью обойтись без кондишнов и для синуса. В итоге это развернулось что один из иксов в формулу берется с отбросом знака.
В принципе для максимально быстрого отброса знака почти честно можно ксорить все биты числа на его знаковый бит, для этого (вероятнее всего у вас не будет float который бы работал еще и с XOR) понадобиться пересчитать найденные константы в арифметику статической точки, что бы все считать в целых
погрешность может быть уменьшена ценой потери контрольных точек `sin=x=>(1.217-0.38529*Math.abs(x))*x` до 3.9% но обычно точность контрольных точек чрезвыйчайно ожидается множеством алгоритмов, которые без нее могут начать работать не правильно.
Поискать наиболее проблемные места поможет такой тест (выводящий последние 30 мест с наибольшей ошибкой) Параметром количества шагов можно "позумить" детализацию (таким образом можно как быстро отыскать в целом проблемные места, так и отдельные участки посмотреть точнее)
dErr=(f,g,x=0,to=Math.PI/2,steps=100)=>{
let step= (to-x)/steps, e, ers=[];
for(; x<=to; x+=step){
if((e=Math.abs(f(x)-g(x)))< ers[(ers.length=30)-1]?.e)continue;
ers.splice(ers.findIndex(o=>o===undefined|| e>o.e),0,{e, x})
}
return ers;
}
console.log(dErr(Math.sin,sin))
оказалось что в некоторых случаях 5% погрешности вполне визуально допустимы.
Еще стоит вспомнить что столько степень полинома ресурсозатратна сколько количество членов и например формула синуса на основе коэффициентов Тэзлора 5го порядка уже довольно визуально точна но в ней нет 2го и 4го членов `sinTailor5=x=>x - x**3/6 + x**5/120` что то же самое что `x(1-xx(1/6+xx/120)`, где на возведениях в квадрат экономим заводя переменную xx. Важно так же отметить что эта формула является частичной суммой, потому не оптимальна, и ее как и формулу до 3го порядка `x=> x - x**3/6` можно значительно подтюнить масштабированием коэффициентов.
Так же не стоит забывать, если вы почему-то считаете много синусов, то если у вас получиться их хотя бы равномерно упорядочить, то полином может считаться феерически быстро дельта функциями. В этом случае дело в том, что значение sinTailor5(x+dx) отличается от sinTailor5(x) на полином меньшей на один степени (потому что см. степенная производная)
Тот отличается от предыдущего на полином третьей, который отличается на полином 2ой... короче... итеративный счет полинома 5й степени это не больше 6 сложений - гарантировано.
Когда я говорю не больше я имею ввиду что может быть и меньше, но это уже отдельная битхакерская песня... В качестве задачи с двумя звездочками на досуге можете сунуть ЭТО в консоль броузера и подумать как и почему это оно выводит квадраты (учитывая что в цикле только есть умножение на константу):
let x=new Uint32Array(1).fill(531489<<8), c=8193/8192;
new Uint8Array(31).fill().map(()=>x[0]*=c);
Приближение синуса и косинуса полиномом 2 степени