{ "hash": "a54f152d613af8d0723b2c5f1c17e58c", "result": { "engine": "jupyter", "markdown": "---\ntitle: \"Algebraic Rotations and Stereography\"\ndescription: |\n How do you rotate in 2D and 3D without standard trigonometry?\nformat:\n html:\n html-math-method: katex\njupyter: python3\ndate: \"2021-09-26\"\ndate-modified: \"2025-06-28\"\ncategories:\n - geometry\n - symmetry\n---\n\n\n\n\n\nExpressing rotation mathematically can be rather challenging, even in two dimensional space.\nTypically, the subject is approached from complicated-looking rotation matrices,\n with the occasional accompanying comment about complex numbers.\nThe challenge only increases when examining three dimensional space,\n where everyone has different ideas about the best implementations.\nIn what follows, I attempt to provide a derivation of 2D and 3D rotations based in intuition\n and without the use of trigonometry.\n\n\nComplex Numbers: 2D Rotations\n-----------------------------\n\nAs points in the plane, multiplication between complex numbers is frequently analyzed\n as a combination of scaling (a ratio of two scales) and rotation (a point on the complex unit circle).\nHowever, the machinery used in these analyses usually expresses complex numbers in a polar form\n involving the complex exponential.\nThis leverages prior knowledge of trigonometry and some calculus, and in fact obfuscates\n the algebraic properties of complex numbers.\nNeither concept is truly necessary to understand points on the complex unit circle,\n or complex number multiplication in general.\n\n\n### A review of complex multiplication\n\nA complex number $z = a + bi$ multiplies with another complex number\n $w = c + di$ in the obvious way: by distributing over addition.\n\n$$\nzw = (a + bi)(c + di) = ac + bci + adi + bdi^2 = (ac - bd) + i(ad + bc)\n$$\n\n*z* also has associated to it a conjugate $z^* = a - bi$.\nThe product $zz^*$ is the *norm* $a^2 + b^2$, a positive real quantity whose square root\n is typically called the *magnitude* of the complex number.\nTaking the square root is frequently unnecessary, as many of the properties of\n this quantity do not depend on this normalization.\n\nOne such property is that for two complex numbers *z* and *w*,\n the norm of the product is the product of the norm.\n\n$$\n\\begin{align*}\n (zw)(zw)^* &= (ac - bd)^2 + (ad + bc)^2\n \\\\\n &= a^2c^2 - 2abcd + b^2d^2 + a^2d^2 + 2abcd + b^2c^2\n \\\\\n &= a^2c^2 + b^2d^2 + a^2d^2 + b^2c^2\n = a^2(c^2 + d^2) + b^2(c^2 + d^2)\n \\\\\n &= (a^2 + b^2)(c^2 + d^2)\n\\end{align*}\n$$\n\nConjugation also distributes over multiplication, and complex numbers possess multiplicative inverses.\nAlso, the norm of the multiplicative inverse is the inverse of the norm.\n\n$$\n\\begin{gather*}\n z^*w^* = (a - bi)(c - di) = ac - bci - adi + bdi^2\n \\\\\n = (ac - bd) - i(ad + bc) = (zw)^{*}\n \\\\\n {1 \\over z} = {z^{*} \\over z z^{*}} = {a - bi \\over a^2 + b^2}\n \\\\[14pt]\n \\left({1 \\over z}\\right)\n \\left({1 \\over z}\\right)^{*}\n = {1 \\over zz^{*}} = {1 \\over a^2 + b^2}\n\\end{gather*}\n$$\n\nA final interesting note about complex multiplication is the product $z^{*} w$\n\n$$\nz^{*} w = (a - bi)(c + di) = ac - bci + adi - bdi^2 = (ac + bd) + i(ad - bc)\n$$\n\nThe real part is the same as the dot product $(a, c) \\cdot (b, d)$ and the imaginary part\n looks like the determinant of $\\begin{pmatrix}a & b \\\\ c & d\\end{pmatrix}$.\nThis shows the inherent value of complex arithmetic, as both of these quantities\n have important interpretations in vector algebra.\n\n\n### Onto the Circle\n\nIf $zz^{*} = a^2 + b^2 = 1$, then *z* lies on the complex unit circle.\nSince the norm is 1, a general complex number *w* has the same norm as its product with *z*, *zw*.\nSpecifically, if *w* is 1, multiplication by *z* can be seen as the rotation that maps 1 to *z*.\nBut how do you describe a point on the unit circle?\n\nSimple.\nFor any complex *w*, there are three other complex numbers that are guaranteed\n to share its norm: $-w, w^{*}$, and $-w^{*}$.\nDividing any one of these numbers by another results in a complex number on the unit circle.\nDividing *w* by -*w* is boring, since that just results in -1.\nHowever, division by $w^*$ turns out to be important:\n\n$$\n\\begin{align*}\n z &= {w \\over w^*} =\n \\left( {c + di \\over c - di} \\right)\n \\left( {c + di \\over c + di} \\right) = 1\n \\\\[8pt]\n &= {(c^2 - d^2) + (2cd)i \\over c^2 + d^2}\n\\end{align*}\n$$\n\nDividing the numerator and denominator of this expression through by $c^2$ yields an expression\n in $t = d/c$, where t can be interpreted as the slope of the line joining 0 and *w*.\nSlopes range from $-\\infty$ to $\\infty$, which means that the variable *t* does as well.\n\n$$\n\\begin{align*}\n {(c^2 - d^2) + (2cd)i \\over c^2 + d^2}\n &= {1/c^2 \\over 1/c^2} \\cdot {(c^2 - d^2) + (2cd)i \\over c^2 + d^2}\n \\\\\n &= {(1 - d^2/c^2) + (2d/c)i \\over 1 + d^2/c^2}\n = {(1 - t^2) + (2t)i \\over 1 + t^2}\n \\\\\n &= {1 - t^2 \\over 1 + t^2} + i{2t \\over 1 + t^2} = z(t)\n\\end{align*}\n$$\n\nThis gives us a point *z* on the unit circle.\nIts reciprocal is:\n\n$$\n{1 \\over z} = {z^{*} \\over zz^{*}} = z^{*}\n$$\n\nApplying the same operation to *z* as we did to *w* gives us $z / z^{*} = z^2$,\n or as an action on points, the rotation from 1 to $z^2$.\n\nThis demonstrates the decomposition of the rotation into two parts.\nFor a general complex number *w* which may not lie on the unit circle, multiplication by\n this number dilates the complex plane as well rotating 1 toward the direction of *w*.\nThen, division by $w^{*}$ undoes the dilation and performs the rotation again.\nGeneral (i.e., non-unit) rotations *must* occur in pairs.\n\n\n### Two Halves, Twice Over\n\nPlugging in -1, 0, and 1 for *t* reveals some interesting behavior:\n $z(0) = 1 + 0i$, $z(1) = 0 + 1i$, and $z(-1) = 0 - 1i$.\nAssuming continuity, this shows that $t \\in (-1, 1)$ plots the semicircle in the right half-plane.\nThe other half of the circle comes from *t* with magnitude greater than 1,\n and the point where $z(t) = -1$ exists if we admit the extra point $t = \\pm \\infty$.\n\nIs it possible to get the other half into a nicer interval?\nNo problem, just double the rotation.\n\n$$\n\\begin{align*}\n z(t)^2 &= (\\Re[z]^2 - \\Im[z]^2) + i(2\\Re[z]\\Im[z])\n \\\\[8pt]\n &= {(1 - t^2)^2 - (2t)^2 \\over (1 + t^2)^2} + i{2(1 - t^2)(2t) \\over (1 + t^2)^2}\n \\\\[8pt]\n &= {1 - 6t^2 + t^4 \\over 1 + 2t^2 + t^4}\n + i {4t - 4t^3 \\over 1 + 2t^2 + t^4}\n\\end{align*}\n$$\n\nNow $z(-1)^2 = z(1)^2 = -1$ and the entire circle fits in the interval \\[-1, 1\\].\nThis action of squaring *z* means that as *t* ranges from $-\\infty$ to $\\infty$, the point\n $z^2$ loops around the circle twice, since the unit rotation *z* is applied twice.\n\n\n### Plotting Transcendence\n\nLet's go on a brief tangent and compare these expressions to their\n transcendental trigonometric counterparts.\nGraphing the real and imaginary parts separately shows how much they resemble\n $\\cos(\\pi t)$ and $\\sin(\\pi t)$ around zero[^1].\n\n[^1]: An approximation of a function as the ratio of two polynomials is called a\n [Padé approximant](https://en.wikipedia.org/wiki/Pad%C3%A9_approximant).\n Specifically, the real part (which approximates cosine) has order \\[4/4\\] and the imaginary part\n (which approximates sine) has order \\[3/4\\].\n\n::: {#129c8e7f .cell layout-ncol='2' execution_count=3}\n\n::: {.cell-output .cell-output-display}\n{}\n:::\n\n::: {.cell-output .cell-output-display}\n{}\n:::\n:::\n\n\nIdeally, the zeroes of the real part should occur at $t = \\pm 1/2$, since $\\cos(\\pm \\pi/2) = 0$.\nInstead, they occur at\n\n$$\n\\begin{align*}\n 0 &= 1 - 6t^2 + t^4\n = (t^2 - 2t - 1)(t^2 + 2t - 1)\n \\\\\n &\\implies t = \\pm {1 \\over \\delta_s} = \\pm(\\sqrt 2 - 1) \\approx \\pm 0.414\n\\end{align*}\n$$\n\nWith a bit of polynomial interpolation, this can be rectified.\nA cubic interpolation between the expected and actual values is given by:\n\n$$\n\\begin{gather*}\n P(x) = ax^3 + bx^2 + cx + d\n \\\\\n \\begin{pmatrix}\n 1 & 0 & 0 & 0 \\\\\n 1 & 1/2 & 1/4 & 1/8 \\\\\n 1 & -1/2 & 1/4 & -1/8 \\\\\n 1 & 1 & 1 & 1\n \\end{pmatrix} \\begin{pmatrix}\n d \\\\\n c \\\\\n b \\\\\n a\n \\end{pmatrix} = \\begin{pmatrix}\n 0 \\\\\n \\sqrt 2 - 1 \\\\\n -\\sqrt 2 + 1 \\\\\n 1\n \\end{pmatrix}\n \\\\\n \\implies \\begin{pmatrix}\n d \\\\\n c \\\\\n b \\\\\n a\n \\end{pmatrix} = \\begin{pmatrix}\n 0 \\\\\n -3 + 8\\sqrt 2/3 \\\\\n 0 \\\\\n 4 - 8\\sqrt 2/3\n \\end{pmatrix}\n \\implies P(x) \\approx 0.229 x^3 + 0.771x\n\\end{gather*}\n$$\n\n::: {}\n\n::: {#92fceeac .cell layout-ncol='2' execution_count=4}\n\n::: {.cell-output .cell-output-display}\n{}\n:::\n\n::: {.cell-output .cell-output-display}\n{}\n:::\n:::\n\n\nThe error over the interval \\[-1, 1\\] in these approximations is around 1% (RMS).\n:::\n\nNotably, this interpolating polynomial is entirely odd, which gives it some symmetry about 0.\nAlong with being a very good approximation, it has the feature that a rotation by an\n *n*^th^ of a turn is about $z(P(1/n))^2$.\nThe approximation be improved further by taking *z* to higher powers and deriving a higher-order\n interpolating polynomial.\n\nIt is impossible to shrink this error to 0 because the derivative of the imaginary part at\n $t = 1$ would be π.\nBut the imaginary part is a rational expression, so its derivative is also a rational expression.\nSince π is transcendental, there must always be some error[^2].\n\n[^2]: Even with the interpolating polynomial, the quantity is a ratio of two algebraic numbers,\n which is also algebraic and not transcendental.\n\nEven though it is arguably easier to calculate, there probably isn't a definite benefit to this approximation.\nFor example,\n\n- Other accurate approximants for sine and cosine can be calculated directly from their power series.\n- There are no inverse functions like `acos` or `asin` to complement these approximations.\n - This isn't that bad, since I have refrained from describing rotations with an angle\n (i.e., the quantity returned by these functions).\n Even so, the inverse functions have their use cases.\n- Trigonometric functions can be hardware-accelerated (at least in some FPUs).\n- If no such hardware exists, approximations of `sin` and `cos` can be calculated by software beforehand\n and stored in a lookup table (which is also probably involved at some stage of the FPU).\n\nElaborating on the latter two points, using a lookup table means evaluation happens in roughly constant time.\nA best-case analysis of the above approximation, given some value *t* is...\n\n\n| Expression | Additions | Multiplications | Divisions |\n|-----------------------------------------------------------|-----------|-----------------|-----------|\n| $p = t \\cdot (0.228763834 \\cdot t \\cdot t + 0.771236166)$ | 1 | 3 | |\n| $q = p \\cdot p$ | | 1 | |\n| $r = 1 + q$ | 1 | | |\n| $c = { 1 - q \\over r}$ | 1 | | 1 |\n| $s = { p + p \\over r}$ | 1 | | 1 |\n| real = $c \\cdot c - s \\cdot s$ | 1 | 2 | |\n| imag = $c \\cdot s + c \\cdot s$ | 1 | 1 | |\n| Total | 6 | 7 | 2 |\n\n...or 15 FLOPs.\n\nOn a more optimistic note, a Monte Carlo test of the above approximation on my computer yields\n promising results when compared with GCC's `libm` implementations of `sin`, `cos`, and `cexp`.\n\n::: {#50127cec .cell execution_count=5}\n\n::: {.cell-output .cell-output-display}\n```{=html}\n
Timing for 10000000 math.h sin and cos:\t2838358ns\nTiming for 10000000 approximations:\t1239784ns\nApproximation faster, speedup: 1598574ns (2.29x)\nSquared error in cosines (100000 runs): \n\tAverage: 0.000051 (0.713743% error)\n\tLargest: 0.000174 (1.320551% error)\n\t\tInput:\t\t0.729202\n\t\tValue:\t\t-0.659428\n\t\tApproximation:\t-0.672634\nSquared error in sines (100000 runs): \n\tAverage: 0.000070 (0.835334% error)\n\tLargest: 0.000288 (1.698413% error)\n\t\tInput:\t\t0.842206\n\t\tValue:\t\t0.475669\n\t\tApproximation:\t0.458685\nTiming for 10000000 complex.h cexp:\t4725615ns\nTiming for 10000000 approximations:\t1780325ns\nApproximation faster, speedup: 2945290ns (2.65x)\nSquared error in cosines (100000 runs): \n\tAverage: 0.000051 (0.713743% error)\n\tLargest: 0.000174 (1.320551% error)\n\t\tInput:\t\t0.729202\n\t\tValue:\t\t-0.659428\n\t\tApproximation:\t-0.672634\nSquared error in sines (100000 runs): \n\tAverage: 0.000070 (0.835334% error)\n\tLargest: 0.000288 (1.698413% error)\n\t\tInput:\t\t0.842206\n\t\tValue:\t\t0.475669\n\t\tApproximation:\t0.458685\n