aboutsummaryrefslogtreecommitdiff
path: root/semestr-3/anm/sprawozdanie.tex
diff options
context:
space:
mode:
Diffstat (limited to 'semestr-3/anm/sprawozdanie.tex')
-rw-r--r--semestr-3/anm/sprawozdanie.tex488
1 files changed, 488 insertions, 0 deletions
diff --git a/semestr-3/anm/sprawozdanie.tex b/semestr-3/anm/sprawozdanie.tex
new file mode 100644
index 0000000..be4f495
--- /dev/null
+++ b/semestr-3/anm/sprawozdanie.tex
@@ -0,0 +1,488 @@
+\documentclass{mwart}
+\usepackage{polski}
+
+\setlength{\emergencystretch}{2em}
+\usepackage{datetime}
+\usepackage{ae,aecompl}
+\usepackage[activate={true,nocompatibility},final,tracking=true,kerning=true,spacing=true,stretch=10,shrink=10]{microtype}
+\frenchspacing
+
+%%% fix for \lll
+% \let\babellll\lll
+% \let\lll\relax
+\usepackage{geometry}
+\newgeometry{vmargin={25mm}, hmargin={25mm,25mm}}
+\usepackage[]{algorithm2e}
+
+
+\usepackage{enumitem}
+\usepackage{graphicx}
+\usepackage[normalem]{ulem}
+\usepackage{tikz}
+
+\usetikzlibrary{external}
+\tikzexternalize[prefix=tikz/]
+
+\usetikzlibrary{arrows.meta}
+\usetikzlibrary{matrix, arrows}
+\usepackage{program}
+\usepackage{amsfonts}
+\usepackage{amssymb}
+%%% fix for \lll
+\let\mathlll\lll
+\let\lll\babellll
+
+\usepackage{amsmath}
+\usepackage{amsthm}
+\usepackage{tikz-cd}
+\usepackage{float}
+\usepackage{hyperref}
+\usepackage{multicol}
+\usepackage{mathtools}
+
+\usepackage{array}
+\usepackage{wrapfig}
+\usepackage{multirow}
+\usepackage{tabularx}
+\newcommand{\RR}{\mathbb{R}}
+\newcommand{\CC}{\mathbb{C}}
+\DeclarePairedDelimiter\abs{\lvert}{\rvert}%
+
+\newcommand{\fC}{{\mathfrak C}}
+\newcommand{\cM}{{\mathcal M}}
+\newcommand{\cC}{{\mathcal C}}
+\newcommand{\cD}{{\mathcal D}}
+\newcommand{\bN}{{\mathbf{N}}}
+\newcommand{\bR}{{\mathbf{R}}}
+\newcommand{\bZ}{{\mathbf{Z}}}
+\newcommand{\bF}{{\mathbf{F}}}
+\newcommand{\bQ}{{\mathbf{Q}}}
+\newcommand{\bC}{{\mathbf{C}}}
+\newcommand{\cA}{{\mathcal A}}
+\newcommand{\cO}{{\mathcal O}}
+\newcommand{\cF}{{\mathcal F}}
+\newcommand{\cB}{{\mathcal B}}
+\newcommand{\Ob}{{\mathrm{Ob}}}
+\newcommand{\topl}{\mathcal T}
+\newcommand{\Set}{{\mathrm{Set}}}
+\newcommand{\Grp}{{\mathrm{Grp}}}
+\newcommand{\AbGrp}{{\mathrm{AbGrp}}}
+\newcommand{\Mod}{{\mathrm{Mod}}}
+\newcommand{\Ring}{{\mathrm{Ring}}}
+\newcommand{\Vect}{{\mathrm{Vect}}}
+\newcommand{\Alg}{{\mathrm{Alg}}}
+\newcommand{\restr}{\mathord{\upharpoonright}}
+\newcommand{\liff}{\mathrel{\leftrightarrow}}
+\newcommand{\limplies}{\mathrel{\rightarrow}}
+\newcommand{\fset}[1]{\left\{{#1}\right\}}
+\newcommand{\meet}{\mathbin{\wedge}}
+\newcommand{\biglor}{\bigvee}
+\newcommand{\bigland}{\bigwedge}
+
+
+\DeclareMathOperator{\round}{{round}}
+\DeclareMathOperator{\cl}{{cl}}
+\DeclareMathOperator{\Id}{{Id}}
+\DeclareMathOperator{\id}{{id}}
+\DeclareMathOperator{\Aut}{{Aut}}
+\DeclareMathOperator{\End}{{End}}
+\DeclareMathOperator{\Ult}{{Ult}}
+\DeclareMathOperator{\Homeo}{{Homeo}}
+\DeclareMathOperator{\dom}{{dom}}
+\DeclareMathOperator{\rng}{{rng}}
+\DeclareMathOperator{\Core}{{Core}}
+\DeclareMathOperator{\Hom}{{Hom}}
+\DeclareMathOperator{\Stab}{{Stab}}
+\DeclareMathOperator{\dcl}{{dcl}}
+\DeclareMathOperator{\acl}{{acl}}
+\DeclareMathOperator{\tp}{{tp}}
+\DeclareMathOperator{\characteristic}{{char}}
+
+
+
+\newtheorem{twr}{Twierdzenie}[section]
+\newtheorem{hip}[twr]{Hipoteza}
+\newtheorem{pyt}[twr]{Pytanie}
+\newtheorem{problem}[twr]{Problem}
+\newtheorem{lem}[twr]{Lemat}
+\newtheorem{fkt}[twr]{Fakt}
+\newtheorem{wnsk}[twr]{Wniosek}
+\newtheorem{stw}[twr]{Stwierdzenie}
+\newtheorem{cw}[twr]{Ćwiczenie}
+
+\theoremstyle{remark}
+\newtheorem{uwg}[twr]{Uwaga}
+\theoremstyle{definition}
+\newtheorem{dfn}[twr]{Definicja}
+\newtheorem*{rozw}{Rozwiązanie}
+\newtheorem*{sbclm}{Podclaim}
+\newtheorem*{clm*}{Claim}
+\newtheorem{pd}[twr]{Przykład}
+\newcounter{claimcounter}[twr]
+\newenvironment{clm}{\stepcounter{claimcounter}{\noindent {\textbf{Claim}} \theclaimcounter:}}{}
+\newenvironment{clmproof}[1][\proofname]{\proof[#1]\renewcommand{\qedsymbol}{$\square$(claim)}}{\endproof}
+\newenvironment{sbclmproof}[1][\proofname]{\proof[#1]\renewcommand{\qedsymbol}{$\square$(subclaim)}}{\endproof}
+
+\newcommand{\xqed}[1]{%
+ \leavevmode\unskip\penalty9999 \hbox{}\nobreak\hfill
+ \quad\hbox{\ensuremath{#1}}}
+\theoremstyle{definition}
+\newtheorem{zad}[twr]{Zadanie}
+
+\title{Pracownia z analizy numerycznej \\
+ \large Sprawozdanie do zadania \textbf{P1.10} \\
+ Prowadzący: dr Rafał Nowak}
+\author{Franciszek Malinka, Kacper Solecki}
+\date{Wrocław, Listopad 2020}
+
+\begin{document}
+
+\maketitle
+
+\section{Wstęp}
+Funkcje trygonometryczne mają szerokie zastosowania w matematyce, informatyce, inżynierii, architekturze, produkcji muzyki i wielu innych dziedzinach. Nietrudno zatem dojść do wniosku, że ich efektywne i dokładne obliczanie jest problemem bardzo ważnym w kontekście tych zagadnień.
+
+W niniejszym sprawozdaniu przyjrzymy się dwóm opracowanym przez nas metodom obliczania wybranych funkcji trygonometrycznych używając jednie najprostszych operacji arytmetycznych ($+$, $-$, $*$, $/$, ale też przesunięcia bitowe), ze szczególnym naciskiem na dokładne obliczanie funkcji $\sin$ oraz $\cos$, również w dziedzinie liczb zespolonych.
+
+Proponowane przez nas metody mają docelowo dawać poprawne obliczenia dla podwójnej precyzji obliczeń, jednakże testy numeryczne przeprowadzamy używając zmiennych typu \texttt{BigFloat} w języku \texttt{Julia} (w którym implementowaliśmy nasze rozwiązania). Typ ten oferuje dowolną dokładność obliczeń. Wyniki naszych funkcji porównujemy z funkcjami bibliotecznymi języka i zakładamy, że dają one dokładne wyniki.
+
+\section{Algorytm CORDIC}
+\subsection{Opis algorytmu}
+
+Pierwszą proponowaną przez nas metodą obliczania funkcji $\sin$ oraz $\cos$ jest Algorytm CORDIC (\textbf{CO}ordinate \textbf{R}otation \textbf{DI}gital \textbf{C}omputer). Algorytm ten został stworzony z myślą o komputerach o niskiej mocy obliczeniowej, ale również o możliwości ''włożenia'' algorytmu w hardware (tj. pozwala tworzyć mało skomplikowane układy bramek logicznych, które obliczają funkcje trygonometryczne). Jak się przekonamy, proces iteracyjny algorytmu korzysta jedynie z dodawania, odejmowania, przesunięć bitowych i wartości obliczonych podczas preprocessingu oraz nie wykorzystuje liczb zmiennoprzecinkowych.
+
+Zacznijmy od wprowadzenia zarysu działania algorytmu. Zapomnijmy na razie o analizie numerycznej i przenieśmy się do świata algebry liniowej. Wyobraźmy sobie, że mamy wydajny system który obliczy wektor $(x_r, y_r)$ jako wynik obrotu danego wektora $(x_0, y_0)$ o dany kąt $\theta$ wokół środka układu współrzędnych:
+\begin{align}
+ x_r = x_0\cos\theta - y_0\sin\theta, \\
+ y_r = x_0\sin\theta + y_0\cos\theta.
+\end{align}
+
+Jeśli za $(x_0, y_0)$ weźmiemy punkt $(1, 0)$, to po obrocie dostaniemy:
+\begin{align*}
+ x_r = \cos\theta, \\
+ y_r = \sin\theta.
+\end{align*}
+Zatem używając obrotu umiemy policzyć wartości funkcji $\cos$ oraz $\sin$.
+
+Zapiszmy równania $(1), (2)$ w formie macierzowej:
+\begin{align}
+ \begin{bmatrix}
+ x_r \\ y_r
+ \end{bmatrix}
+ = \begin{bmatrix}
+ \cos\theta & -\sin\theta \\
+ \sin\theta & \cos\theta
+ \end{bmatrix}
+ \begin{bmatrix}
+ x_0 \\ y_0
+ \end{bmatrix}
+ = \cos\theta
+ \begin{bmatrix}
+ 1 & -\tan\theta \\
+ \tan\theta & 1
+ \end{bmatrix}
+ \begin{bmatrix}
+ x_0 \\ y_0
+ \end{bmatrix}.
+\end{align}
+
+Powyższa równość pokazuje, że do obliczenia naszego wektora wynikowego (przy założeniu, że znamy wartości $\tan\theta$ oraz $\cos\theta$) wystarczą jedynie 4 mnożenia i kilka dodawań lub odejmowań. Chcielibyśmy pozbyć się tych mnożeń. Skorzystamy tutaj z dwóch obserwacji:
+\begin{itemize}
+ \item Każdy kąt $\theta\in [0^{\circ}, 90^{\circ}]$ możemy zapisać jako sumę \textbf{wcześniej ustalonych}, mniejszych (co do modułu) kątów $\theta_i, i \in \{0, ..., n\}$:
+ \begin{align}
+ \theta = \sum_{i=0}^n \sigma_i\theta_i, \; \sigma_i \in \{-1, 1\}.
+ \end{align}
+ Dla przykładu, kąt $57.353^{\circ}$ jest sumą kątów
+ $45^{\circ}, 26.565^{\circ}, -14.03^{\circ}$ (dobór tych kątów jest nieprzypadkowy, o czym się zaraz przekonamy).
+ Jeśli $\theta$ nie należy do zadanego przez nas przedziału, to możemy ten kąt zmienić korzystając ze wzorów redukcyjnych
+ (o tym więcej w \textsection 3).
+ \item Jeśli nasze kąty $\theta_i$ będą dobrane tak, że $\tan\theta_i = 2^{-i}$, to mnożenie przez $\tan\theta_i$ jest niczym innym jak przesunięciem bitowym (w liczbach całkowitych). Dodatkowo okazuje się, że dowolny kąt nie większy niż $90^{\circ}$ da się przybliżyć sumą tak dobranych kątów $\theta_i$, więc da się tymi kątami osiągnąć cel założony w pierwszym punkcie. Dodatkowo im więcej takich kątów wybierzemy, tym dokładniejsze będzie to przybliżenie.
+\end{itemize}
+
+Pozostały nam jeszcze mnożenia przez czynnik $\cos\theta$ (który nazwiemy przyrostem). Jeżeli to zignorujemy, to otrzymana rotacja będzie faktycznie obróceniem wektora o kąt $\theta$, ale z dodatkowym przeskalowaniem wektora.
+
+% \begin{center}
+% \begin{tikzpicture}
+% \draw [<->,thick] (0,6) node (yaxis) [above] {$y$}
+% |- (8,0) node (xaxis) [right] {$x$};
+% \draw [->, thick] (0, 0) -- (6, 2) node (v1) [right] {$(x_0, y_0)$}
+% \draw [->, cm={cos(45) ,-sin(45) ,sin(45) ,cos(45) ,(0 cm, 0 cm)}] (0, 0) -- (6,2)
+% % \draw[black, thick, ->] (0,0) -- (10,0);
+% % \draw[black, thick, ->] (0,0) -- (0,8);
+% \end{tikzpicture}
+% \end{center}
+
+Przyjrzyjmy się jak dokładnie będzie wyglądać nasz przyrost, jeśli zastosujemy zaproponowane przez nas punkty do obliczania obrotu. Powiedzmy, że chcemy obrócić wejściowy wektor o kąt $57.353^{\circ} = 45^{\circ} + 26.565^{\circ} - 14.03^{\circ}$. Wartości funkcji $\tan$ tych kątów są odwrotnościami potęg dwójki, zatem te kąty spełniają nasze założenie. Pierwsza rotacja o $45^{\circ}$ daje:
+\begin{align}
+ \begin{bmatrix}
+ x_1 \\ y_1
+ \end{bmatrix}
+ = \cos 45^{\circ}
+ \begin{bmatrix}
+ 1 & -1 \\
+ 1 & 1
+ \end{bmatrix}
+ \begin{bmatrix}
+ x_0 \\ y_0
+ \end{bmatrix}.
+\end{align}
+Druga rotacja daje:
+\begin{align}
+ \begin{bmatrix}
+ x_2 \\ y_2
+ \end{bmatrix}
+ = \cos 26.565^{\circ}
+ \begin{bmatrix}
+ 1 & -2^{-1} \\
+ 2^{-1} & 1
+ \end{bmatrix}
+ \begin{bmatrix}
+ x_1 \\ y_1
+ \end{bmatrix}.
+\end{align}
+Trzecia rotacja:
+\begin{align}
+ \begin{bmatrix}
+ x_3 \\ y_3
+ \end{bmatrix}
+ = \cos(-14.03^{\circ})
+ \begin{bmatrix}
+ 1 & 2^{-2} \\
+ -2^{-2} & 1
+ \end{bmatrix}
+ \begin{bmatrix}
+ x_2 \\ y_2
+ \end{bmatrix}.
+\end{align}
+Łącząc te równania razem dostajemy:
+\begin{align}
+ \begin{bmatrix}
+ x_3 \\ y_3
+ \end{bmatrix}
+ = \cos 45^{\circ}\cos 26.565^{\circ}\cos(-14.03^{\circ})
+ \begin{bmatrix}
+ 1 & -1 \\
+ 1 & 1
+ \end{bmatrix}
+ \begin{bmatrix}
+ 1 & -2^{-1} \\
+ 2^{-1} & 1
+ \end{bmatrix}
+ \begin{bmatrix}
+ 1 & 2^{-2} \\
+ -2^{-2} & 1
+ \end{bmatrix}
+ \begin{bmatrix}
+ x_0 \\ y_0
+ \end{bmatrix}.
+\end{align}
+
+Zauważmy, że dzięki parzystości funkcji $\cos$ znak poszczególnych kątów nie ma znaczenia dla wartości przyrostu. Z tego snujemy wniosek, że przy ustalonej liczbie iteracji przyrost nie zależy od wyboru kąta $\theta$. Możemy go zatem policzyć i wziąć go pod uwagę dopiero na koniec obliczeń.
+\begin{align}
+ P = \cos 45^{\circ}\cdot\cos 26.565^{\circ}\cdot\cos 14.03^{\circ}\cdot\ldots \approx 0.6072.
+\end{align}
+W takim razie, pomijając przyrost $P$ otrzymujemy następujący proces iteracyjny algorytmu CORDIC:
+\begin{align}
+ x_{i + 1} & = x_{i} - \sigma_i 2^{-i}y_i, \\
+ y_{i + 1} & = y_i + \sigma_i 2^{-i}y_i.
+\end{align}
+
+Pozostaje jedynie problem znajdowania znaków $\sigma_i$ przy kątach $\theta_i$. Okazuje się jednak, że możemy to robić w bardzo prosty sposób. Niech $z_0 = \theta, \sigma_0 = 1$. W każdym kroku iteracyjnym znak $\sigma_{i + 1}$ dobieramy w następujący sposób -- niech $z_{i}$ będzie równe $\theta - \sum_{k=0}^{i - 1}\sigma_k\theta_k$ (czyli $z_i$ mówi jaki jeszcze nam został kąt do obrócenia, potencjalnie obróciliśmy już za dużo, wtedy $z_i < 0$). Wtedy $\sigma_{i + 1} = sgn(z_i)$. Mamy też $z_{i + 1} = z_{i} - \sigma_i\theta_i = z_{i} - \sigma_i\arctan{2^{i}}$. Błąd przybliżenia po $n$ iteracjach możemy wtedy łatwo policzyć ze wzoru
+\begin{align}
+ \theta_{error} = z_n = \theta - \sum_{i=0}^n\sigma_i \theta_i.
+\end{align}
+
+Zbierając wszystko razem, proces iteracyjny algorytmu CORDIC wygląda następująco:
+\begin{align*}
+ x_{i + 1} & = x_{i} - \sigma_i 2^{-i}y_i, \\
+ y_{i + 1} & = y_i + \sigma_i 2^{-i}y_i, \\
+ z_{i + 1} & = z_i - \sigma_i \arctan 2^{-1}.
+\end{align*}
+
+Kąty $\theta_i = \arctan{2^{-1}}$ możemy policzyć w preprocessingu i używać jako stałych. Wtedy rezultatem naszych obliczeń będzie $\cos\theta \approx P\cdot x_n$ oraz $\sin\theta \approx P\cdot y_n$. Dodatkowo, gdybyśmy przyjęli $x_0 = 1/P$, to pozbylibyśmy się nawet tego ostatniego mnożenia.
+
+Warto jeszcze zauważyć, że
+\begin{align*}
+ \frac{1}{\cos(\arctan 2^{-i})} = \sqrt{1 + \frac{1}{2^{2i}}}.
+\end{align*}
+Możemy ten fakt wykorzystać do dokładniejszego obliczania wartości $P$.
+
+Musimy jeszcze zauważyć, że algorytm działa jedynie dla kątów $\theta$ spełniających
+\begin{align*}
+ \abs{\theta} \leq \sum_{i=0}^n\theta_i \approx 99.88^{\circ}.
+\end{align*}
+Zatem dla kątów większych niż $90^{\circ}$ musimy skorzystać ze wzorów redukcyjnych, co dokłada pewnego błędu do naszego wyniku oraz powoduje konieczność wykonania kilku dodatkowych dzieleń i mnożeń.
+
+\subsection{Niespełniona obietnica}
+We wstępie powiedzieliśmy, że algorytm będzie korzystał z dodawań, odejmowań i przesunięć bitowych, a do tego używał liczb całkowitych. Dzięki naszemu ustaleniu, że $\arctan\theta_i = 2^{-i}$, wszystkie mnożenia podczas iteracji naszego algorytmu to mnożenia przez potęgi dwójki. Jak możemy to wykorzystać?
+
+Ustalmy $M := 2^{K}$ dla pewnego $K$ (potem je wybierzemy). Teraz każdą spreprocessowaną
+przez nas wartość $T$ (czyli $T$ jest kątem $\theta_i$ lub przyrostem $P$) przyjmiemy
+$T := \round(M \cdot T)$. Chcąc policzyć wartości funkcji trygonometrycznych dla kąta $\theta$,
+uruchomimy nas proces iteracyjny dla $x_0 = \round(M/P)$, $y_0 = 0$, $z_0 = \round(M\cdot\theta)$.
+Zauważmy, że dzięki temu przeskalowaliśmy wszystkie obliczane przez nas wartości o stałą $M$
+i zaokrągliliśmy je po to, by móc pracować na liczbach całkowitych. To pozawala na wykorzystanie
+przesunięć bitowych podczas mnożenia przez potęgi dwójki, dzięki czemu znacznie zwiększyliśmy
+wydajność naszego algorytmu. Wtedy, po $n$ iteracjach naszego procesu mamy $\cos\theta \approx x_n/M$
+oraz $\sin\theta\approx y_n/M$ (już w arytmetyce zmiennoprzecinkowej).
+
+Zostało nam ustalić wartość $K$. Na pewno chcielibyśmy, aby $K$ było nie większe niż
+długość mantysy. Ponadto algorytm ma być dostosowany do mało wydajnych maszyn, dlatego
+w naszych analizach pracujemy przy użyciu \texttt{Int32}, zatem nie chcemy żeby $2^K\cdot T$ przekroczyło
+zakres \texttt{Int32}. Jednakże kąty $\theta_i$ oraz wartość $P$ są niewielkie, zatem $K = 30$ będzie
+odpowiednią wartością.
+
+\section{Wzór Taylora}
+\subsection{Opis metody}
+Zanim przejdziemy do opisu tej metody, przypomnijmy sobie pewną tożsamość trygonometryczną:
+\begin{align}
+ \sin z = \sin (x + yi) = \sin x\cosh (y) + i\cos x\sinh(y).
+\end{align}
+Korzystając z tej tożsamości pozbywamy się konieczności pracowania z liczbami zespolonymi i możemy operować jedynie w zbiorze liczb rzeczywistych.
+
+W tej metodzie wykorzystamy znany analityczny wzór zwany wzorem Taylora. Korzystając z niego możemy wyprowadzić rozwinięcia funkcji trygonometrycznych:
+
+\begin{align*}
+ \sin x & = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \ldots, \\
+ \sinh x & = x + \frac{x^3}{3!} + \frac{x^5}{5!} + \frac{x^7}{7!} + \ldots, \\
+ \cos x & = 1 - \frac{x^2}{2!} + \frac{x^4}{4!} - \frac{x^6}{6!} + \ldots, \\
+ \cosh x & = 1 + \frac{x^2}{2!} + \frac{x^4}{4!} + \frac{x^6}{6!} + \ldots. \\
+\end{align*}
+
+Obliczanie rozwinięć poszczególnych funkcji jest proste i wyabstrahowaliśmy je do jednej, generycznej funkcji \texttt{TalyorSeries}:
+\begin{center}
+ \begin{algorithm}[H]
+ \SetAlgoLined
+ \KwData{x, parity, changeSign, M}
+ \KwResult{Obliczenie szeregu Taylora odpowiedniej funkcji trygonometrycznej w punkcie x dla jego pierwszych M niezerowych wyrazów}
+ result := 0\;
+ elem := 1\;
+ \If{parity = 1}{
+ elem := x\;
+ }
+ i := parity + 1\;
+ \While{i $\le$ 2M + parity}{
+ result := result + elem\;
+ elem := elem * changeSign * x * x / (i * (i + 1))\;
+ i := i + 2\;
+ }
+ \end{algorithm}
+\end{center}
+
+Algorytm oblicza sumę $\sum_{n=0}^M\sigma_n\frac{x^n}{n!}$, gdzie $\sigma_i \in \{-1, 0, 1\}$. Wartość $\sigma_n$ zależy od wartości parametrów podanych w funkcji: gdzy \texttt{parity} jest równe $0$, wtedy mamy $\sigma_{2k + 1} = 0$, a gdy \texttt{parity} jest równe $0$ mamy $\sigma_{2k} = 0$. Odpowiada to odpowiednio szeregom $\cos x, \cosh x$ oraz $\sin x, \sinh x$. Od parametru \texttt{changeSign} zależy czy chcemy, aby kolejne niezerowe wyrazy obliczanego szeregu zmieniały znak (zmieniamy znak, gdy chcemy obliczać zwykłe funkcje trygonometryczne oraz nie zmieniamy gdy obliczamy funkcje hiperboliczne).
+
+To daje prostą możliwość obliczania pożądanych przez nas funkcji:
+
+\begin{align*}
+ \sin x & = \texttt{TaylorSeries}(x, 1, -1, M), \\
+ \sinh x & = \texttt{TaylorSeries}(x, 1, 1, M), \\
+ \cos x & = \texttt{TaylorSeries}(x, 0, -1, M), \\
+ \cosh x & = \texttt{TaylorSeries}(x, 0, 1, M).
+\end{align*}
+
+Zauważmy, że wzór Taylora nadaje się do przybliżania funkcji trygonometrycznych jedynie dla argumentów bliskich $0$.
+Na szczęście możemy sobie z tym poradzić korzystając ze znanych tożsamości trygonometrycznych oraz okresowości funkcji $\sin$ i $\cos$.
+Naszym celem przed obliczniem funkcji \texttt{TaylorSeries} będzie sprowadzenie argumentu do przedziału $[0, \pi/4]$,
+w którym wzór Taylora bardzo dobrze przybliża wartości funkcji trygonometrycznych.
+Oto tabela która przedstawia jak radzimy sobie z argumentami spoza tego przedziału:
+\begin{table}[H]
+ \centering
+ \begin{tabular}{ |p{4cm}||p{4cm}|p{4cm}| }
+ \hline
+ \multicolumn{3}{|c|}{Wzory redukcyjne} \\
+ \hline
+ Warunek na $x$ & $\sin x$ & $\cos x$ \\
+ \hline
+ $x < 0$ & $-\sin (-x)$ & $\cos (-x)$ \\
+ $x \ge 2\pi$ & $\sin(x \mod 2\pi)$ & $\cos (x\mod 2\pi)$ \\
+ $x > \pi$ & $-\sin(x - \pi)$ & $-\cos(x - \pi)$ \\
+ $x > \pi/2$ & $\cos(x - \pi/2)$ & $-\sin(x - \pi/2)$ \\
+ $x > \pi/4$ & $\cos(\pi/2 - x)$ & $\sin(\pi/2 - x)$ \\
+ \hline
+ \end{tabular}
+ \caption{Wzory redukcyjne.}
+ \label{tab:reduk}
+\end{table}
+
+Dla funkcji hiperbolicznych sposób jest prostszy: korzystamy z dwóch własności:
+\begin{align*}
+ \sinh x & = 2\sinh(x/2)\cosh(x/2), \\
+ \cosh x & = \cosh^2(x/2) + \sinh^2(x/2).
+\end{align*}
+Można by przypuszczać, że dla dużych $x$ błąd obliczania tych funkcji będzie duży. Jednakże okazuje się, że funkcje te bardzo szybko rosną i już dla $x = 1000$ wartości obu tych funkcji nie mieszczą się w zakresie \texttt{Float64}, zatem tak naprawdę wykonamy maksymalnie $15$ takich redukcji, co generuje dopuszczalnie mały błąd.
+
+\section{Analiza błędu}
+
+\subsection{Wyniki testów}
+Dokładność naszych metod porównywaliśmy z funkcjami bibliotecznymi w języku \texttt{Julia}, które domyślnie obsługują obliczanie wartości funkcji trygonometrycznych dla liczb zespolonych. Zakładmy o tych funkcjach bibliotecznych, że dają poprawny wynik.
+
+Przeprowadziliśmy testy dokładności metody opartej na wzorze Taylora dla liczb rzeczywistych oraz dla liczb zespolonych oraz testy dla metody Taylora, w której nie używaliśmy wzorów redukcyjnych, lecz rozwijaliśmy wzór dopóki wystarczająco dobrze nie przybliżał wartości funkcji dla danego argumentu. Testy dla algorytmu CORDIC przeprowadziliśmy wyłącznie dla liczb rzeczywistych.
+
+Dla każdej metody przeprowadziliśmy trzy rodzaje testów, w każdym z nich losowaliśmy $10^8$ liczb z różnych przedziałów. Ze względu na podobieństwo funkcji $\sin$ i $\cos$ oraz z faktu, że często wzory redukcyjne powodują faktycznie obliczanie innej funkcji trygonometrycznej, testy przeprowadziliśmy wyłącznie dla funkcji $\sin$. Przedziały i wyniki testów przedstawione są w poniższej tabeli:
+
+\begin{table}[H]
+ \centering
+ \resizebox{\textwidth}{!}{%
+ {\setlength{\extrarowheight}{5pt}%
+ \begin{tabular}{ |c||c|c|c|c|c| }
+ \hline
+ algorytm & przedział argumentu & średni błąd wz. & max błąd wz. & średni błąd bezwz. & max błąd bezwz. \\
+ \hline
+ \multirow{3}{6em}{Taylor dla $\RR$} & $x:$ dowolny Float64 & $1,887 \cdot 10^{-15}$ & $3,167 \cdot 10^{-8}$ & $1,179 \cdot 10^{-16}$ & $8,882 \cdot 10^{-16}$ \\
+ \cline{2-6}
+ & $-2\pi \leq x \leq 2\pi$ & $1,472 \cdot 10^{-15}$
+ & $1.184 \cdot 10^{-8}$ & $9,766 \cdot 10^{-17}$ & $5,551 \cdot 10^{-16}$ \\
+ \cline{2-6}
+ & $0 \leq x \leq 1$ & $8,694 \cdot 10^{-17}$ & $6,661 \cdot 10^{-16}$ & $4,293 \cdot 10^{-17}$ & $4,441 \cdot 10^{-16}$ \\
+ \hline
+ \multirow{3}{6em}{Taylor dla $\CC$} & $-100 \leq \abs{x} \leq 100$ & $4,932 \cdot 10^{-15}$ & $1,311 \cdot 10^{-13}$ & $1,689 \cdot 10^{26}$ & $5,898 \cdot 10^{29}$ \\
+ \cline{2-6}
+ & $-2\pi \leq \abs{x} \leq 2\pi$ & $4,338 \cdot 10^{-16}$ & $1,487 \cdot 10^{-11}$ & $1,364 \cdot 10^{-14}$ & $8,710 \cdot 10^{-13}$ \\
+ \cline{2-6}
+ & $0 \leq \abs{x} \leq 1$ & $1,597 \cdot 10^{-16}$ & $1,099 \cdot 10^{-15}$ & $1,124 \cdot 10^{-16}$ & $1,111\cdot 10^{-15}$ \\
+ \hline
+ \multirow{3}{6em}{Taylor dla $\CC$ bez wzorów redukcyjnych} & $-100 \leq \abs{x} \leq 100$ & $4,77 \cdot 10^{23}$ & $4,488 \cdot 10^{26}$ & $7,759 \cdot 10^{40}$ & $2,208 \cdot 10^{44}$ \\
+ \cline{2-6}
+ & $-2\pi \leq \abs{x} \leq 2\pi$ & $6,333 \cdot 10^{-1}$ & $1,000$ & $2,344 \cdot 10$ & $2,677 \cdot 10^2$ \\
+ \cline{2-6}
+ & $0 \leq \abs{x} \leq 1$ & $1,589 \cdot 10^{-16}$ & $1,291 \cdot 10^{-15}$ & $1,118 \cdot 10^{-16}$ & $1,116 \cdot 10^{-15}$ \\
+ \hline
+ \multirow{3}{6em}{Cordic dla $\RR$} & $x:$ dowolny Float64 & $3,100 \cdot 10^{-8}$ & $4,575 \cdot 10^{-1}$ & $2,459 \cdot 10^{-9}$ & $5,529 \cdot 10^{-3}$ \\
+ \cline{2-6}
+ & $-2\pi \leq x \leq 2\pi$ & $2,770 \cdot 10^{-8}$ & $1,183 \cdot 10^{-1}$ & $2,532 \cdot 10^{-9}$ & $6,042 \cdot 10^{-4}$ \\
+ \cline{2-6}
+ & $0 \leq x \leq 1$ & $4,176 \cdot 10^{-8}$ & $9,182 \cdot 10^{-2}$ & $2,614 \cdot 10^{-9}$ & $5,261 \cdot 10^{-4}$ \\
+ \hline
+ \end{tabular}
+ }}
+ \caption{Błędy przy obliczaniu funkcji $\sin(x)$.}
+ \label{tab:2}
+\end{table}
+
+\subsection{Wnioski}
+Jak widać w tabeli \ref{tab:2}, dla wszystkich testów zaproponowane przez nas metody sprawdzają
+się bardzo dobrze dla małych argumentów. Algorytm CORDIC wypada dużo gorzej od metody korzystającej
+ze wzoru Taylora, lecz nie jest to dla nas nic zaskakującego -- metoda ta tworzy kompromis
+między wydajnością, a dokładnością obliczeń. Dla obu metod widać, że problemem jest zmiana
+argumentu na mały, gdyż to generuje największy błąd. W obu przypadkach najgorszy błąd względny
+generowały argumenty, które są duże i zbliżone do wielokrotności $\pi$, co wynika z konieczności odejmowania,
+z którego korzysta wbudowana w \texttt{Julia} funkcja \texttt{mod2pi} oraz wzory redukcyjne.
+Prowadzi do utraty cyfr znaczących, tym samym obniżając dokładność obliczeń.
+
+Dużym problemem w obliczaniu wartości funkcji trygonometrycznych w dziedzinie liczb zespolonych jest konieczność używa funkcji hiperbolicznych, które rosną w tempie wykładniczym. Jeśli spojrzymy na wzór $(13)$ to zauważmy, że bardzo prawdopodobne jest, że będziemy mnożyć zbliżoną do $0$ wartość funkcji $\sin$ oraz $\cos$ z potencjalnie bardzo dużymi wartościami funkcji $\cosh$ i $\sinh$.
+
+Mimo to jesteśmy zadowoleni z rezultatów dla losowych testów -- jak widać, średni błąd względny jest rzędu dokładności liczb o precyzji podwójnej w przypadku metody Taylora oraz rzędu pojedynczej precyzji dla algorytmu CORDIC (co wynika z użycia \texttt{Int32} podczas procesu iteracyjnego).
+
+\begin{thebibliography}{9}
+ \bibitem{CORDIC tutorial}
+ Steve Arar.
+ \textit{An Introduction to the CORDIC Algorithm}.
+ \\\texttt{\url{https://www.allaboutcircuits.com/technical-articles/an-introduction-to-the-cordic-algorithm/}}
+
+ \bibitem{CORDIC ints}
+ Andrea Vitali.
+ \textit{Coordinate rotation digital computer algorithm (CORDIC)
+ to compute trigonometric and hyperbolic functions}.
+ \\\texttt{\url{https://bit.ly/3lVQxbJ}}
+\end{thebibliography}
+\end{document} \ No newline at end of file