Video summary
The video explores various sine and cosine approximation algorithms for x86 assembly, challenging common misconceptions about hardware versus software performance. It clarifies that relying solely on hardware FPU instructions is not always superior due to overhead when switching from SSE2 registers, while also noting that integer arithmetic does not universally outperform floating-point operations. Furthermore, the discussion highlights that extreme precision beyond three to seven digits is rarely necessary for typical applications like 3D rendering, and that slow mathematical functions seldom bottleneck runtime compared to I/O operations. The presentation introduces simple baselines such as small-angle approximations and return-zero tricks for multiples of pi, alongside lookup tables that offer tunable accuracy at the cost of increased memory usage.
Historical and advanced methods are examined in detail, ranging from Bascara's ancient polynomial approximations to Taylor series and Chebyshev expansions. The video explains how Taylor series allow for configurable accuracy through term summation but require range remapping, whereas Chebyshev expansions provide optimal speed-to-accuracy ratios despite having limited documentation. CORDIC algorithms are also featured as an iterative approach using bit-shifts or multiplies with arctangent tables to compute trigonometric values simultaneously; notably, the integer version achieves near-perfect accuracy without floating-point error accumulation but demands larger lookup tables for multiple tolerances. Benchmarking of half a million random inputs reveals that even the slowest reasonable algorithm is only four times slower than the fastest, yielding six additional digits of accuracy, with all results relative to the FPU as an exact reference.
The analysis concludes by weighing the trade-offs between hardware instructions like `chsby`/`chubby Chev`, which are limited to older processors, and portable software alternatives such as Taylor or Hiller series. The speaker emphasizes that performance and file size differences between these approaches are negligible by modern standards, making the choice a matter of specific application needs, available memory, and required accuracy levels like E3. Ultimately, the decision rests on a personal trade-off between performance, precision, and development time, with the creator expressing a preference for `chubby Chev` while acknowledging the excellence of other methods like Hiller series and leaving the final selection to the viewer's principles.
Read the full video transcript
hey guys welcome back to the skit Zone
my name is completely irrelevant to the
topic which is alternate trig function
algorithms or
approximations uh particularly for S and
cosine besides the tailor series
expansion that we implemented back in
the beginning of this series um I want
to talk about the performance the memory
footprint as well as the accuracy of
each algorithm and some comparisons
between them I have a bunch of
algorithms here today so you'll be kind
of interested in them um both on the low
end and the high end as far as accuracy
and runtime and everything the whole
Gambit is here so it's pretty cool stuff
um I have a lot of high level takeaways
um even Beyond just the actual
algorithms themselves so I think you'll
enjoy this as much as I did over the
past two weeks going through this I
really love this kind of stuff
personally uh okay first some
misconceptions one is that there's an
idea that Hardware is always faster than
software I would say yeah in general
that's the case but not always there's
some overhead related to I'm speaking
here about the floating Point Unit there
is a hardware instruction f sign and F
cosine as well as other instructions as
well for the x87 math Co processor um
that's for Boomers really old stuff but
um there's some overhead regarding how
do you get floing Point values on the
FPU and then back off and if you're
going to constantly be switching back
and forth FPU and like SS2 instructions
like we have in this series there's
going to be some overhead there that you
wouldn't already have if you were to
stay on the FPU entirely either way what
I'm saying is that there's sometimes a
case where software is just better than
Hardware as far as performance is
concerned and also the hardware
instructions are extremely accurate they
have to provide enough accuracy for all
the bits that you put in and so question
is do you always need that much accuracy
not so much so number two integer
arithmetic is always faster than
floating point I would say in general
yes and if you can come with an
algorithm the same algorithm in you know
integer and floating Point probably the
integer algorithm is going to be faster
but you know there are sometimes
shortcuts you can take with Floy point
and um also there's an expense related
to converting ins to floats with the
convert instructions and stuff like that
so it's not always the case I'll leave
it at that number three
the idea that you always need high
accuracy functions in reality you really
don't unless you're designing like you
know or planning caliber missile
trajectories to hit Ukrainian power
stations or something you don't really
need 16 digits of accuracy on your sign
and cosign I don't think um lot of the
time you know 3 four five is more than
enough and even for our applications we
don't go more than like six or seven
digits of accuracy for all our three 3D
rendering so you don't need as much as
you think you need most of the time uh
number
four a specific approach will be best
for all applications obviously that's
you know obviously false you know you
have to tailor your approach for the
application as I said before if you
don't need the accuracy maybe pick a
worse algorithm if you don't have the
memory space to to spare maybe pick a
smaller algorithm the choice is yours
right number five slow algorithm will
make your code do slow I want to show
you that even the fastest of the fast
algorithms are not that much faster than
the slow ones um you'll see here in a
few minutes that's the case it's not
going to be a huge impact on on your
performance know that there's always
more important things
than your math implementations you know
uh right into the screen right into a
file is always very slow and most of the
time in my experience that's what limits
your speed not so much um your sign
approximation of course obviously if you
can avoid slow functions avoid them but
it's not going to make or break your
runtime in fact most of the time your
CPU is sitting idle you know you really
you can spare a couple Nan seconds here
and there for a slower algorithm if it's
easier to implement um second to last
simple algorithms are always easiest to
implement you'll see that's not the case
sometimes the more complex algorithm are
easier to implement and I'll show you
perhaps why that is and
lastly um there's nothing to be gained
by comparing apples to oranges in this
video we're going to be comparing apples
to oranges to plums to pears and I want
to show you that even though we're doing
that you can still Garner some high
level understanding and takeaways from
this okay so right into it now here are
the two easiest s and cosine
approximations um the first is the small
angle appro oximation you may have heard
that before a lot of Engineers use this
to simplify their equations so s of X is
basically X same with tangent of X and
cosine of x is basically one for small
values of X or you can take a look at
1us X2 over 2 that's also perhaps better
way to approximate cosine either way
this is very fast as you would expect in
fact you don't even have to do anything
you can literally inline and play
replace S of X for X in your other
algorithm so if you have some other
algorithm that uses sign and cosine
we'll just replace that with these
approximations here so really there's
going to be no overhead as far as memory
footprint you're just replacing sine of
x with X and this is very very fast as
you would expect but it's only valid
around x equals 0 or perhaps higher um
values as well like for example uh you
know you can you know sign and cosine
they're periodic so you maybe you can
remap higher
values back towards zero as well but
either way it's only valid over a very
small small
range also what about return zero this
is perhaps a a dumb approximation of
sonic sign but uh it is right
infrequently and periodically this is
more of a control than anything else to
compare our other algorithms against but
even this is valid every 2 pi right so
you can perhaps use this if you know
that you're going to pass in an N Pi
into your sin of x cosine of
x
functions okay how about some a little
bit more challenging approximations here
well one might be a lookup table this is
one that's very simple and the idea is
basically you pregenerate and store
however many s of X values in order and
maybe the first one is for S of zero the
next one is for s of some small X let's
call it like you know delta T then you
have S of 2 delta T and so on 3 four 5
delta T Etc and uh basically by passing
in an input number you can say hey I
passed in 7 delta T well you check the
table what is the seventh or eighth
element down that table and then pull it
out and return so it can be very very
fast and also it can be tailored to your
application um
so if you need a big table you can have
a big table if you can get away with a
small table you can use a small table
but obviously the bigger the table the
higher your accuracy and honestly the um
the scaling is pretty good I'm not sure
what caching even means but as far as I
could tell when you scale up this table
it doesn't really cost you anything as
far as runtime it just makes your
program more accurate obviously if you
have a immense table it's going to have
some expense as far as that entire table
cannot be located entirely in your cash
and so there's going to be a problem
with that but either way uh it's going
to be pretty close as far as
performance um then you have these two
Hardware instructions I mentioned before
f sign and F cosine they're on the math
C processor that you have on your
computer right now if you're watching
this series probably um it's decently
fast they are extremely accurate I think
they have like the maximum accuracy you
can get however there is
a significant overhead when you have to
load
in the contents of an xmm register onto
the FPU because you have to actually go
through a memory address to do that and
so you're basically have to go from an
register into memory onto the FPU do the
instruction and then undo the last three
instructions so it is a little bit of
expensive operation to get this set up
especially if you're not using the FPU
for all your math but it has a very
small footprint and it just works and
you can just call this whenever you want
and it's
honestly a great bang for your buck then
you have this
bascara whatever his name is uh
approximation from like a thousand years
ago he basically has these
two um estimates here for S and cosine
you can see them here they're very fast
they're good enough like almost all the
time and they're valed over only a very
small range so you have to remap into
that range but uh I either way these
polinomial or whatever you call these
types of things are extremely good as
far as accuracy for
how much space they take up in memory so
very efficient approximations this guy
is an ultimate Boomer he's from like you
know prehistoric times but he's a really
smart guy so great approximations
there now we have some approximations
for math enjoyers so
Tor series we already covered that in
the beginning of this project however it
is just a sum of these terms and the
terms scale and they keep shrinking by
the factorial here on the bottom and so
you get more and more accuracy the more
terms that you have that you're adding
up um and so you have a configurable
accuracy you can change how many ends
you want to add up 5 10 20 and you can
relate very easily the number of terms
to the accuracy because because the 10th
term is going to be you know this and if
that number is less than your tolerance
you can stop so it's very easy to decide
when to stop your
approximation and then for the
uh idea of how efficient it is you have
a pretty good bank for your book as far
as memory footprint and performance I'll
show you the results in a minute um so
wait for that but it has a very good
speed for decent accuracy and and uh the
problem though is it's only valid over
small range as many of these algorithms
are and so you have to constantly think
about remapping so if you pass in sine
of3 this might handle your problem
directly but then what about s of 10
trillion and three well you have to
constantly subtract off some number of
two pies to get back into the range
where this is actually valid so it's
easy to implement documation is
everywhere and we've used this for
everything so far in this series and
will probably continue to do so in the
future chubby series is one that is
better however there's almost no good
documentation on this out there unless
you're really good at math you can
figure it out yourself I do have this
one paper I'll pull it up here
um this paper is by this uh Sean Felder
fella um basically he's tabulated these
approximations this table basically of
uh coefficients a and uh what you do is
you basically compute this terer
multiply it by a and add them up and for
a different amount of terms you have
more and more accuracy and he computes
it all the way down for 10^ the -4th in
accuracy however I has it both for
cosine and for S and for other ones as
well um I'll link this in the
description um however I do think
there's a typo somewhere in these tables
because I'm not getting the perfect
accuracy that he's he's bragging about
but either way um we can evaluate this
ourself and compute our own coefficients
but from what I can see here the
accuracy is is lacking but that's just a
problem of I think a typo in this table
so with that out of the way um back to
chbby Chev as far as pros and cons again
it has configurable accuracy so the more
terms you have the more accurate you're
going to be and this you'll see is
probably the best bang for your buck
this is like the the optimal sele C of
your
coefficients for a given accuracy it's
kind of the whole idea behind Chev as
far as I'm aware and so it's very fast
with very good accuracy and you'll see
that result in a second um but again
it's only valuable a very small range
and so you have to remap back into that
range and uh as I said before there's
not much papers or you know pseudo code
out there to look at you have to do this
by yourself you're kind of on your own
for this okay and lastly the one that
getss shilled all the time I'm not sure
why is this Boomer Cordic approximation
thing um and we have two ways to do this
one with floats and one with integers
and the idea is simple you can look up
videos and stuff on this or look at the
Wikipedia I do have it open actually um
the Wikipedia algorithm here actually
implements I think it's in Python the
folding Point method um so not exactly
the intended use case but I do have a
pretty good
integer paper I'll put that in the
description as well this guy Michael
Bron has a pretty awesome 16 bit
implementation but we'll do we'll do
much more than that in this video but uh
the idea is basically either with inser
floats you basically start with the
right half of this circle and then you
constantly it's not really bisecting but
you keep breaking apart this sector of
the circle into smaller and smaller
pieces correcting with an arc tangent
until you Converge on your given angle
and uh by adding different well
subsequently smaller and smaller and
smaller arctangent values you can
Converge on both your s and cosine
actually at the same time so there's
like a double uh throughput there as
well but the idea behind this is it's
it's a boomer algorithm because it has
for integers there's no Hardware
multiplies there's only add subtract and
shift I can show you this guy's paper if
if you're curious the algorithm a lot of
boiler plate stuff but um where is the
algorithm basically
write here here's the iteration of the
CTIC algorithm so you access the table
you add to your running angle sum
essentially and then you just add and
bit shifts by different amounts so
there's no multiplies it's a very
efficient integer approach for this
problem um but there is a
so I should say you can get extremely
high accuracy like like insanely high
accuracy with this even though it's
integer only you may think that integers
are you know for rounding and stuff you
must lose Precision on that you actually
have a probably the highest accuracy of
any of these methods with the exception
potentially of the FPU because you
actually have 80 bits of accuracy on
that not 64 but either way extreme
accuracy really decent speed but a
particularly large footprint especially
if you want to handle different amounts
of
Tolerance at at runtime if you want to
say hey I want 10 terms this time but 30
Terms this time you'll have some
overhead there to handle both those
cases and for floating point it's much
the same you have inherently worse
accuracy because you have this um
accumulated floating Point error um so
it's worse than the integer version it
is seemingly faster though so you may
think well this bit shifts must be super
fast yeah they're fast but when you have
to constantly um access memory to pull
things out of these tables and remember
you're passing in a floating Point
number in the first place and so that
has to be converted and there has to be
adjustments for the you know your domain
are we sign of 3 million Pi again or are
we near zero and so there is some
overhead that you have to do it either
way for both and um a lot of the time
all those convert instructions and
memory accesses may not be as fast as
grabbing something from from memory as a
floating point or keeping it in a
register as a floating point so yeah
there's some pros and cons to each of
these two now for the actual results
sorry to keep you waiting um so what I
have here is basically and this is the
example today I have one for sign and
cosine both here's the sign one we've
we're averaging
basically um well we're generating like
half a million random numbers and
evaluating the sign and or cosine of
them and in the first row you can see
the first five of these half a million
signs and the exact answer for the sign
of that number so s of 6.34 is zero and
these random numbers are always between
I think -1 and 10 so they're beyond the
range where any of these algorithms is
going to be valid but um you know like
most of these algorithms are only valid
between like 0 and pi/ 2 or negative pi
to Pi Etc so this is outside that range
obviously um and so you are looking at
the time and the performance and the
memory footprint and accuracy of of this
entire setup of taking the input X
mapping it back to the range that we can
use our approximation over and then the
function itself executing so like I said
before these are the first five of the
half a million signs here's the exact
sign I think we're using the FPU to
evaluate this exact quantity here and
then I have each algorithm and then for
each algorithm I have just for these
first five signs first five X's 1 2 3 4
five the corresponding function
evaluation for this approximation in
this case return zero always return zero
which is the whole point of it but for
other ones you can see here for tayor
series for example this is very close to
the exact answer for sign of
6.34 so a little bit hard to read this
table but that's the idea and then I
have the error plotted here so for a
given approximation what is the error
from
the exact answer and then I have a a
time
in fractions of a second for how long it
took to execute half a million of these
signs through each individual algorithm
and then I have uh divided that that out
basically per each sign how many CPU
cycles that was more or less and there's
some variation in this so don't take too
much confidence in these numbers but the
overall trend is there okay so the first
thing and then also I have on the right
hand side the bite counts of each
algorithm rounded to a multiples of 64
because I've aligned these all to 64
byte boundary for again just an Apples
to Apples comparison of the performance
okay so retting zero as you would expect
is extremely fast seven cycles per each
sign so very very efficient um just to
basically replace the x value with zero
of course not a very accurate algorithm
you can see the error is quite large in
this case it's Error of one basically
error of one so um not too good that's
actually really bad um but the memory
footprint is zero so that's kind of the
gold standard here as far
memory size now the small angle
approximation now this is only valid
around xal 0 and none of these
particular X's that I'm showing is near
zero and so all the errors are quite
large you can see here this is a
terrible error error of seven in this
case very very bad but again Cycles is
six actually it's even faster than
return zero because I can literally
inline
replace the X with the new X right sign
of x = x I can just return from this
function directly there's nothing to be
loaded into registers at all so even
faster than return zero and again zero
bytes now into the more you know
accurate algorithms I would say or more
useful I would say um first is the
tailor series and so for this I've
picked some tolerance it's probably
around one E9 or something I would say
just based off this error I'm seeing
here and you can see now we are very
close to the exact answer we're within
E9 for all of these and it takes us 221
cycles for each sign and we're using
this type of uh algorithm for everything
in the skit Zone Series so far and so
clearly it's not slow but you'll say hey
it is way slower than small angle
approximation sure it is but you're
getting way more accuracy for the time
and the memory print is 384 bytes so
larger but nowhere near as large as you
would expect a lot of these other
libraries to implement this type of
stuff um very very small function even
though it's much bigger than these two
simpler
functions okay so that's kind of our
first real function not really sure how
that compares here's a look at some
other functions so lookup table now this
one I've only implemented I think it's
100 for this particular case um
items in that lookup table so you're
inherently going to be remember sign
goes from negative 1 to one and so if
you divide that two you know y value
Delta into 100 spaces that's what 02 and
so you're looking like yeah we're
probably going to be within uh like 02
for for most of
these
and you can see just by having that
table in there even just having 100
elements
you're already up to 704
bytes so almost double the tayor series
implementation that we've used so far in
the series and you have worse accuracy
so all the haters out there that say Hey
Taylor series sucks it's this dumb
cringe math thing it is that is true it
is cringe math thing but as far as
accuracy as far as footprint and as far
as speed well I guess speed is faster
but is it worth it if you're going to be
so far off now I will say if you're
going if you can sacrifice the bites if
I can go from 100 to 10,000 then all of
a sudden my accuracy reduces by a factor
of what 100 right and so then we're in
the realm of maybe not matching this tor
series but being closer and this cycle
count is not going to increase by much
if at all so you have to weigh that is
it worth the extra bites the extra blow
in my binary or not so yeah good
question then we have bcar now here's an
interesting one right so for bascara
you'll see that the cyle count is
actually less and remember this one was
that that thousand year old
approximation here and you'll see that
it was faster than the lookup
table and it had comparable if not
better accuracy than the lookup table
for that many elements in it and the
bite count of the binary is small
smaller so almost the hands down well I
would say definitely hands down better
for this lookup table size now if you
were to increase lookup table you know
10 fold 100 fold thousandfold maybe it
would be better as far as accuracy but
honestly you don't need more than E3
accuracy for most applications in sign
and cosign you may think that you do but
think about it some more cuz you really
don't most of the time
uh so yeah that's pascar a very good
algorithm and next is the hardware
instruction so this is that FPU f sign
or F cosine instruction and here you can
see this accuracy is um is perfect
because we're using this as the
definition of exact so yeah zero error
um but look at the time it takes to do
this so just looking at this from tiller
series it is slow
so using the FPU is slower than the
teror series expansion with this many
terms in it of course better accuracy
but do you need that extra accuracy is
the question is E9 not good enough for
you I don't know check figure it out
yourself I don't know maybe it is but
the size of this binary is very
small it fits in one 64 byte block as
far as lining is concerned so yeah
pretty cool stuff there I would say if
you don't care about again in this
series we made the decision that we're
going to only stick with SS2
instructions um for everything so we're
not going to use this but if you don't
have that requirement that constraint I
should say yeah go ahead use this this
is great it just works plug and play and
uh it's not that slow and gives you I
would say extremely accurate accurate
results now here is the the best one in
my opinion is this chubby Chev expansion
thing and just look at this in less this
is a polinomial random thingy it's just
some some you know math stuff again but
look it's faster than the hardware
instruction it is essentially as
accurate right e -16 for this many terms
that I'm putting in here and the file
size is large but not that large and a
lot of the the file size here has to do
with I want to support
multiple um tolerances and so if I go
back to this table here you know I want
to support up to 40 you know e negative
40 of accuracy in reality we're not
going that deep but I've still wasted
some bites on storing these values in
the table so yeah there's that um but I
would say this one is the best one as
far as bang for your book
regarding um file size performance and
accuracy now into the two CTIC
approximations floating point and
integers you'll see here that they are
are they exactly the same look at look
at these the arrows are the same the
signs are the same of everything that is
s of X so they match exactly for these
amount of terms that I've picked in the
approximation so this is as close as you
can get to an Apples to Apples
comparison of these two algorithms now
for the floating point one you'll see
that it's slightly slower than the
integer one which is to be expected
because it it is slower to use floats
most of the time than ins um there is an
implied denominator for the integer
evaluation that's we convert back out of
at the end and that's why it's so much
faster we don't have to keep tracking
this and I will say the
um I guess the accuracy is up to a limit
you can increase this accuracy for the
integer version like almost to the I
mean to the same degree that you you can
get the hardware instruction you can get
this error to be zero if you up the
number of terms you can't do that for
the float you're always accumulating
some amount of floating Point error and
you'll never quite get to zero error
relative to this but you'll be able to
get that with integers alone so for that
reason alone if you want a truly exact
answer and you can't use for whatever
reason the hardware instructions then
use this Cordic integer approximation
and so here's that you know
misconception that integers are going to
be less accurate not the case if you
have a very large implied denominator
you can get like every single bit of
accuracy there's no waste of bits on
exponents or anything like that or signs
if you can use the bits efficiently you
can get way more accuracy out of a fixed
point or integer approximation than you
can for a Flo just my takeaway top level
here and then also look at the size so
we are bigger a bit than the other
algorithms in the in here perhaps the
exception of lookup table for more terms
but uh again a lot of these Bites come
from the fact where I want to support
multiple tolerances if I want to have 10
terms 20 terms 40 terms the tables in
these files and I'll show you those in a
minute are large enough to support
multiple tolerances multiple you know
accuracies in the function call at
runtime time do you need that no you
could pull all that out and get these
bik counts way way down if you wanted to
but again even still these bike counts
are not massive and um there's no reason
to do that in my
opinion okay so with that out of the way
that's the kind of top level comparison
and you can see um one take away here is
that there's not a huge variation you
may think oh yeah that algorithm is so
slow tayor series is so slow well first
off it's not it was actually one of the
fastest ones but even still look at the
variation here as far as you know I I'll
call the them the reasonable algorithms
so let's take a look at the ones that
are of decent accuracy so I'm going to
ignore small angle ignore return zero
tillor series is one lookup table is
I'll say not so much here but maybe
we'll grab it bascara Hardware
instruction chubby Chev and the cordex
if you look at all these the fastest one
of all these was bascara at 50
Cycles the slowest one was Cordic float
at 700 Cycles so you're looking at
little over a factor of 10 in variation
but what are you getting you're getting
six more digits of accuracy in the
result if you ignore the ones like
lookup table and bascara and you stick
with the ones that are like ea9 or
better what's the fastest one chbby Chev
178 the worst one is cord float 700 so
even still you're looking at a a factor
of four variation between the very best
and the very worst algorithms so not a
huge factor factor of four is not
massive at all factor of 400 is massive
factor of four not massive so
yeah that's the take way I wanted to
show with this that even the slowest
algorithm on this list is not going to
really slow down your program unless
that's the entirety of your program
okay so what are some caveats for this
well first the footprint for some
algorithms as I mentioned is inflated by
having large tables of this pre-computed
data both lookup tables as well as just
data for the algorithms themselves and
um that gets even worse when you want to
support multiple tolerances like hey I
want to support 10 terms 20 terms 30
Terms I have to have more and more data
in those tables to permit more and more
or uh basically tolerances at runtime so
number two the
accuracy is all compared relative to the
FPU instruction so that could be wrong
the FPU could be wrong about all this
and this entire table could be bogus so
I don't know I guess I could check that
but I I believe that it's correct
personally uh number three
implementations here are decent but they
could definitely be improved I didn't
spend much time know optimizing each
algorithm all I did was Implement them
and then align them a little bit and
debug them so I didn't spend time
optimizing each one and so even that
factor of four at the end of the day um
between chubby Chev and float that might
not be a factor of four if I were to
spend or you were to spend an hour or
two optimizing these algorithms
further okay and then I should say that
there's a certain amount of variation in
execution between each run right this
takes maybe like 5 Seconds to run half a
million of each sign through each
algorithm and I've just screenshotted
one result here of course you can take
the code run it yourself and get similar
numbers on your machine um and there's
going to be some variation so hey maybe
it's a factor of four now but it might
be a factor of three on average I don't
know could
be and then uh lastly the I should I can
show you the code but we're only timing
as best as I can the algorithms
themselves so basically we're calling a
rapper function for each algorithm and
that calls the function itself so there
basically two calls and two returns and
then the function
itself so
um it's pretty if I would say apples to
apples in that regard and we're using um
rdtsc the instruction to basically read
the number of cycles that have been you
know elapsed on the on the hardware as
along with the Cy call for time that's
elapsed and so both of those are
basically the same thing but we're using
that to evaluate the performance and
we've pulled all the random numbers and
their generation out of the loop just to
make sure we have an Apples to Apples
comparison because the uh random
function well the the random instruction
well the instruction that gives you a
random number basically makes you wait
until the number is ready and so uh you
you can't keep calling that over and
over and over again and time the result
so that's pulled out out of the loop and
that's why if you look the um memory
footprint of the entire binary is large
because I've allocated literally space
for half a million random numbers in the
binary directly so yeah there's that not
very good but
whatever okay so all the code is
available in this um I spelled skits on
wrong jeez I can't even we'll fix that
live
dude that would have been
bad so yeah it's all in this Li math
expression trig directory chubby Chev
the cordex stuff FPU lookup table all
that's in here sign and cosine and I
have two different benchmarking
functions programs that you can run um
to see on your machine the the stuff so
I'm GNA run this right now
uh here together so I'll open up just
the sign one so you can see how this is
set up uh and basically it's pretty
straightforward so we're including all
the different sign approximations as
well as functions to Generate random
numbers as well as time the number of
seconds and clock cycles that have
elapsed and print the results out and so
basically uh it prints out some
formatting stuff it Loops through the
different function slips through all of
the random numbers evaluates the sign of
those numbers prints them out prints the
time out and then formats the result
and I mentioned before this is a cool
way to do this I have a function table
that includes both the string of the
function name so return zero is a I
guess 17 or something 15 bytes long with
zero at the end followed by a pointer to
the function itself and so I can kind of
store an arbitrary number of functions
in this table and then automatically
Loop through these so if you wanted to
add an approximation for a sign go ahead
add a sign function 10 add its name
above that and then change this number
here to a nine and the whole thing would
uh would keep running that's great and
then uh at the bottom here I have
basically each sign function implemented
as this is like the wrapper function so
here is return zero you can see it just
exor out xmm0 and returns here is the
small angle approximation for sign it
just returns right you pass in X I pass
back out X so this is the fastest
algorithm obviously then for Kill series
you can see here we passed in the
tolerance in this case it's E8 or 9
whatever this happens to be uh then it
calls the ex the actual sign function
directly and then returns and so I have
lookup table bascara FPU Chev CTIC flow
and CTIC int here as well so if I run
this wait a few
moments and it will dump all the data to
the
screen and here you can see the table
that we saw before with some uh
variation obviously in the ccle count
and obviously the numbers have
changed so yeah point out right here
look at this number for X 02 well small
angle approximation look at that 02
so that came out pretty close to the
exact answer right e -6 even though
it's an absolutely asinine you know zero
instruction approximation hey it was
pretty accurate for this particular
input so there's that I also want to
show you
um
the algorithms themselves just to give
you an idea of how they're implemented
because I think it's kind of interesting
um lid math expression
trig let's do that I hate
that we have arct tangent another arc
tangent cosine is tillor series we did
that one way back in the beginning of
the series here is the bascara
approximation here you can see it
literally just uh remaps our input so if
you pass in you know X is 500 it remaps
that back between 0 and Pi / 2 that's
the first thing it does gets an idea for
the the sign of this so it has to negate
the answer if it's the wrong sign but
that's fine it tracks that in a flag and
then it just basically implements the
scar approximation you can see here a
very simple fling point the registers
here being used and then Returns the
answer so very straightforward some
hardcoded values in memory here for the
comparison of the the range I guess the
the input X needs to be remapped between
0 and Pi / 2 and then also we have like
pi^ squ half pi 4 those things are
required for the actual algorithm itself
you can see here here's the algorithm
cosine is pi^ 2us 4 so we have to have
those stored as floating Point numbers
of course you could convert four as an
INT into a float but that is very slow
so we don't do that that's bascara here
is the chubby Chev for floating Point
numbers
um and it does the same thing it remaps
our input to a certain range in this
case z to pi over two then it starts con
you know evaluating those T terms if I
open up the Boomer Shan Felder paper um
it basically implements
this expansion this uh this sum for a
given number of inputs and that you
passed in in RDI as number of inputs you
want to evaluate so if I want terms I to
pass in RDI equals 10 Etc and uh it goes
how you think it would work however you
can see here here is the CH coefficients
and you'll note that I only have some
number of these but uh either way it's
it's not up to 40 40 40 digits of
accuracy it it stops way before that
because you can't have that much
accuracy realistically and um and
maintain the number of of digits here so
you you can't have the exponent and the
accuracy both together I think in in all
this but uh either way we have it all
here just in case and that's how that
works now here is the Cordic
approximation this is the floating Point
Cordic and uh this one basically
implements the Cordic algorithm but
doesn't use bit shifts instead it uses
Hardware multiplies instead and it's not
all that much slower than the integer
version um so I would say it's pretty
decent but you see here that I have a
lot of data here for what's called the K
factor I can show you that in a moment
as well as Arc tangents as well as this
is basically powers of two here as well
and so this is all trying to basically
do I have it open
still I
do this is basically imple this is the
wrong one um you need an an arct tangent
table here to constantly
uh correct your approximation for the
the angle and so you have a table for
that you have a table in this case
for this this divide equals 2 this could
be a bit shift but I've stored that as a
uh in this case floating Point numbers
I've stored that as a bunch of powers of
two negative powers of two in a table
and then you have this knen this knen is
a function well
a scaler that scales your output because
without this you kind of you either gain
or lose length of your
approximation uh Ray or line segment
whatever that is and so you have to have
a a correction factor and uh that's
computed basically as a function of
number of terms because of that you want
to be able to support multiple inputs
you know tolerance of 10 terms 20 terms
Etc and so this has to be implemented in
my case I did a lookup table for that
that's uh this table here but uh you
could Implement that logic that they
have here in Python just the same but it
would be more expensive I think at
runtime whenever you can pull out at
compile time or even before compile time
I think is a good investment of your
time and memory because uh ultimately
this is not a huge memory footprint for
this and it's way faster than solving
those C values at run
time uh okay there's that look look
table here's lookup table so interesting
stuff here basically um I decided to
only use four floats because as you saw
before the accuracy of this table is
well of this algorithm is kind of
limited and you don't get to the point
where you actually need the full 8 byte
floats for this even 4 by floats is
enough to get your you know
0.101 accuracy that we were seeing and
so I have these values here this is
basically sign of every number between I
guess 0 and pi/
2 um so for x0 all the way to Pi / 2 in
increments of that range divided by 100
I'm guessing and then we take the answer
and we
uh map it to every other
possible spot on the number line so you
know sign is very periodic in fact the
very first pi/ two of that
plot can be flipped negated copied
translated around to make the entire
plot over you know x negative Infinity
to Infinity so that's how that works and
then also you can see here this scaler
this is what computes the entire like
which address in memory should we grab
and so basically whatever you pass in
gets skilled by this and that determines
how many bytes into the table we're
looking so that's how that works um here
is the integer CTIC
implementation um this one is a little
bit more sophisticated took the longest
to debug but was also the most accurate
and extremely fast algorithm that we
talked about today um only beaten out uh
by Chubby shev and but uh potentially
even more accurate than CH Chev if you
need it and um it enables you to pass in
obviously both the x value as well as
the number of iterations you want to
execute in this case supports from 1 to
60 um the example that I'm going to link
in the description this 16bit
implementation only handles up to 14 I
believe for number of iterations
obviously this is for older Hardware uh
yeah number of bits here is is
14 but uh either way you pass that in
and there's a bunch of data down here to
support that and so here is
the you can look it up how this works a
table of the initial X values a table of
the AR tangents and you can see just how
much accuracy you have so this
correlates the Delta y to Delta X
essentially for each iteration of your
quic approximation and you can see
originally it's very very large and this
is this is divided by 2 to the 60th or
something as far as so basically when
you use this um integer approximation
you're passing in basically a number an
integer that corresponds to your angle
from zero all the way to 2 pi and so I
guess we're using 62 bits for that
entire range and so 60 bits for just the
top quadrant the first quadrant of that
range and so basically
this these numbers correlate to how many
so if we divided that entire circle into
2 the
62 sectors this is how many sectors in
the first correction second correction
third correction Etc all the way down
and the very last correction you can see
here is two and so we're down into the
the weeds of the the limit of our
accuracy is basically down to 1 over 2
602 so it's extremely accurate algorithm
if you run it all the way to the end to
this many terms which we don't most of
the time you don't need to but you
potentially could so that's how that
works um here is the FPU instruction so
here's kind of the overhead I was
talking about before about you know why
uh why is it so slow to use this
Hardware instruction shouldn't it be
fast well here's why so we're using
these xmm registers for everything in
this project that's how all our math is
done normally you don't couple XM
registers with the FPU FPU is something
that you do separately the SC processor
is separate entirely you don't use them
interchangeably but you can here's how
you would do that basically you would
take your x value dump it somewhere in
memory then load a quad word from memory
that memory address onto the you know
stack of the
co-processor call the instruction to
evaluate the cosine that replaces on the
stack x with cosine of x and you pop it
off back into memory and then move it
from memory back in the register and
then return and so a lot of this is
overhead that you wouldn't need to use
if your entire you know code base was
done on the FPU or whatever you call
this the x87 C processor you wouldn't
need these two instructions or these two
instructions or this instruction or the
call originally this could literally
just be done in line on the cord
processor and it would be way faster
however to do Apple's Apple's comparison
here um I I wrap this you can see here
to make it work it would be faster
though if you didn't have this overhead
and so that should be remembered because
hey this instruction only works on the
old Boomer you know
instruction maybe you should use the old
Boomer instructions for all your math
hey I don't know maybe your algorithm
can be converted over to the
co-processor instead up to you anyway
that's the gist I just wanted to go
through and take these like top level
takeaways for you guys um and you can
see just the the main tick I want to
give you is it's not a huge difference
you can spend you know days and days
debuging this but is it really worth you
know two days of your time to try and
get chubby Chev to work when you could
have just implemented uh you know Taylor
series and it would have been a little
bit slower if that or is it worth
wasting time on Cordic to get that much
accuracy and whatever else just to avoid
Hardware
multiplies well maybe not maybe bcar is
enough for you maybe you only need E3
accuracy Maybe you should try that hey
is do we have enough memory for a lookup
table maybe we do do we not maybe we
don't we we have to use the hardware
instruction hey how about this what if
our application only deals with very
small angles maybe we can just use this
one that one's pretty good if you ask me
for small angles so yeah think about
your application not a huge difference
as far as performance and uh even file
size is not a huge difference there
either
these are all very small by today
standards as far as binaries go and um
ultimately performance accuracy this is
a a trade-off you have to make on your
own choice and I would say my opinion if
I had to pick one of these I'd probably
pick chbby Chev knowing what I know now
um but up to you chbby Chev bascara
killer series all very good algorithms
to do this we're going to stick I think
with Hiller serus as a matter of
principle but if you want to use the
these other ones feel free with that
I'll end the video thanks for watching I
hope you enjoyed I'll see you in the
next one