Why the CORDIC algorithm lives rent-free in my head
github.com
github.com
I started working on such a thing as a fun side project a few years ago but never finished it. One of these days I hope to get back to it.
[0]: https://randomascii.wordpress.com/2013/07/16/floating-point-...
> The IEEE standard does guarantee some things. It guarantees more than the floating-point-math-is-mystical crowd realizes, but less than some programmers might think.
Summarizing the blog post, it highlights a few things though some less clearly than I would like:
* x87 was wonky
* You need to ensure rounding modes, flush-to-zero, etc. are consistently set
* Some older processors don't have FMA
* Approximate instructions (mmsqrtps et al.) don't have a consistent spec
* Compilers may reassociate expressions
For small routines and self-written libraries, it's straightforward, if painful to ensure you avoid all of that.Briefly mentioned in the blog post is IEEE-754 (2008) made the spec more explicit, and effectively assumed the death of x87. It's 2024 now, so you can definitely avoid x87. Similarly, FMA is part of the IEEE-754 2008 spec, and has been built into all modern processors since (Haswell and later on Intel).
There are still cross-architecture differences like 8-wide AVX2 vs 4-wide NEON that can trip you up, but if you are writing assembly or intrinsics or just C that inspect with Compiler Explorer or objdump, you can look at the output and say "Yep, that'll be consistent".
Surely people have written tooling for those checks for various CPUs?
Also, is it that ‘simple’? Reading https://github.com/llvm/llvm-project/issues/62479, calculations that the compiler does and that only end up in the executable as constants can make results different between architectures or compilers (possibly even compiler runs, if it runs multi-threaded and constant folding order depends on timing, but I find it hard to imagine how exactly that would happen).
So, you’d want to check constants in the code, too, but then, there’s no guarantee that compilers do the same constant folding. You can try to get more consistent by being really diligent in using constexpr, but that doesn’t guarantee that, either.
Of course the specific line linked to in that GH issue is showing that LLVM will attempt constant folding of various trig functions:
https://github.com/llvm/llvm-project/blob/faa43a80f70c639f40...
but the IEEE-754 spec does also recommend correctly rounded results for those (https://en.wikipedia.org/wiki/IEEE_754#Recommended_operation...).
The majority of code I'm talking about though uses constants that are some long, explicit number, and doesn't do any math on them that would then be amenable to constant folding itself.
That said, lines like:
https://github.com/llvm/llvm-project/blob/faa43a80f70c639f40...
are more worrying, since that may differ from what people expect dynamically (though the underlying stuff supports different denormal rules).
Either way, thanks for highlighting this! Clearly the answer is to just use LLVM/clang regardless of backend :).
Does those differences produce different results when running cross-platform simd code?
That is, if you have a bunch of floats like:
float sum = 0.f;
for (int i = 0; i < N; i++) {
sum += x[i];
}
and that you vectorize that to something like (typing in this comment, errors are likely): __mm256 sum_8wide = _mm256_setzero_ps();
for (int i = 0; i < N/8; i++) {
sum_8wide = _mm256_add_ps(sum_8wide, _mm256_load_ps(x[8*i]);
}
// Now sum up the 8 values to get the final sum
float sum = _mm256_hadd_ps(...);
that will result in a different accumulation than if you did those are 4-wide and then a reduction. The usual solution is to either use the lowest common denominator (e.g., use AVX instead of AVX2) or more performance oriented, use the 4-wide SIMD units on ARM to "emulate" an 8-wide virtual vector (~15 years since I wrote NEON... and again, this is in a comment): float32x4_t sum_lo = ; // zero;
float32x4_t sum_hi = ; // zero;
for (int i = 0; i < N/8; i++) {
sum_lo = vaddq_f32(sum_lo, vload1q_f32(x[8*i));
sum_hi = vaddq_f32(sum_hi, vload1q_f32(x[8*i + 4]));
}
// Reduce the sum in the same order
You would want to write a "virtual SIMD" wrapper library, so you don't do this manually in lots of places.> In fact, before IEEE 754 became the popular standard that it is today, fixed point was used all the time (go and ask any gamedev who worked on stuff between 1980 and 2000ish and they'll tell you all about it).
The real issue is that the PS1 has no subpixel precision. In other words, it will round a triangle coordinates to the nearest integers.
Likely the reason why they did this is because then you can completely avoid any division and multiplication hardware, with integer start and end coordinates line rasterization can be done completely with addition and comparisons.
https://all-things-andy-gavin.com/2011/02/04/making-crash-ba...
The newer Rapier library (which is a rewrite of the nphysics) instead relies on the guarantees of IEEE-754 2008 to provide cross-platform determinism, which means that it doesn't work with old platforms, but it is deterministic across modern platforms, including wasm. And yes, you can't rely on the transcedental routines provided by each platform (like sine, cosine, etc), those need to be implemented in a way to work the same everywhere. But, this is possible if avoid running on non-compliant platforms.
https://www.rustsim.org/blog/2020/06/01/this-month-in-rustsi...
I've got the feeling that by using quaternion instead of conventional orthonormal matrices, CORDIC based operations can be executed more efficiently (less compute cycles and memory) and effectively (less errors) [1].
[1] CORDIC Framework for Quaternion-based Joint Angle Computation to Classify Arm Movements:
- Taylor gives you polynomial optimized for the evaluation of one point.
- naive Newton gives you a polynomial with 0 error in the interpolation points [but the system has a bad condition]
- Chebychev gives you a polynomial with minimal error over the interval of choice.
So there is no real reason to ever use the Taylor series to approximate a function over an interval. The high school math teachers are lying.http://files.righto.com/calculator/sinclair_scientific_simul...
I would love to see how it compares to regular software and hardware trigonometric implementations on various kinds of hardware through the ages.
It is a very strange that for a very pervasive and hugely popular computer techniques, CORDIC do not get a proper detailed treatment and coverage in books. Since IoT and machine-to-machine communication are taking off, and with the efficiency of CORDIC implementation and operation, it usages will most probably increase exponentially, hence good references are needed for correct and optimized implementation.
The two anomalies are books by Prof. Omondi, and Prof. Deschamps [1],[2].
[1]Computer-Hardware Evaluation of Mathematical Functions:
https://www.worldscientific.com/worldscibooks/10.1142/p1054
[2]Guide to FPGA Implementation of Arithmetic Functions:
http://www.arithmetic-circuits.org/guide2fpga/vhdl_codes.htm
CORDIC really shines when latency doesn't matter so much. You can pipeline every stage of the computation and get a huge throughput. It's well suited for digital mixing in radio systems.
That said... The G4 also has a hardware CORDIC peripheral that implements this algo for fixed-point uses. Is this mainly to avoid floating point precision losses? You program it using registers, but aren't implementing CORDIC yourself on the CPU; dedicated hardware inside the IC does it.
where do you get them for under 2 dollars?
(digi-key does list the nuvoton chip for barely under 2 dollars in quantity 500)
JLC: $2.1 for qty=1. $1.3 at 1k qty.
Loads of stock. It's a low flash and low RAM variant, but sufficient for some purposes. 170Mhz CPU with many onboard periphs.
I was late to the game learning about CORDIC. I had used fixed point a lot in 8 and 16 bit assembly land for performance, and determinism.
When I found out about it, I was stunned! It was fast, and only required basic math capability to be useful.
Yes, general purpose code is rarely the answer. I think all of us see that after a while.
Then, after that process, one starts coding differently. Chain stuff together, using common ops, pulling off intermediate values, etc... For most things, a fast way exists, but it will often be a tweak or two to make sense in the context.
One needed to find the coordinates of the bisector of an angle subtended by an arc of a unit circle. They (x,y) coordinates of the 'arms' were available. The existing implementation was a mass of trigonometry - going from the x,y coordinates to polar (r,θ), then making sure that the θ computed was in the correct quadrant, halving the θ and converting back to x,y coordinates. All in all, many calls to trigonometric functions and inverse functions.
This was in Python and thus we had first class access to complex numbers. So all that was needed was to define two complex numbers. z1 from (x1,y1) z2 from (x2,y2) and then take the geometric mean of the product: √(z1*z2). Done.
No explicit trigonometry and no explicit conversion and back in the new code.
https://fgiesen.wordpress.com/2010/10/21/finish-your-derivat...
Wouldn't that be rotating by 22.5°? Is this an error in the article or my understanding?
> Efficient (linear time) algorithms have been developed for the Boolean operations (union, intersection and difference), geometric operations (translation, scaling and rotation), N-dimensional interference detection, and display from any point in space with hidden surfaces removed. The algorithms require neither floating-point operations, integer multiplications, nor integer divisions.
https://doi.org/10.1016/0146-664X(82)90104-6
This facilitated creation of fast, custom VLSI graphics acceleration hardware for octree representation.
Curious how CORDIC performs against cubic or other polynomial interpolation with a small table. I was taught that resource constrained synthesizers sometimes used cubic interpolation, and this was presumably when CORDIC was relatively new.
At a glance, the single bit of precision you get with each iteration of CORDIC means it would be more expensive in compute but cheaper in space than a polynomial.
But on the space note I feel we should highlight that it can be cheaper than the article might suggest with the 4096 entry lookup tables for sin(x) - Only a quarter of the full circle is needed due to symmetry.
"How Many Ways Can You Draw a Circle?", the first *Jim Blinn's Corner* article:
https://ieeexplore.ieee.org/document/4057251
He found 15 ways, which generalized to methods like CORDIC, etc.
A couple years ago I was wondering if SDF shape representation could use something like a taxi cab distance metric to move floating point operations to integer, although I haven't spent too much time investigating it.
Thank you for that link, very interesting diagrams and structures
They look very similar to what a neural network might be in certain cases, and it seems like the structures are trying to map some sort of duality
BLDC + motor control + sensorless
CORDIC is a de facto standard algorithm in the relevant application area.
Infineon explains algorithms compactly and clearly:
https://www.infineon.com/dgdl/AP1610510_CORDIC_Algorithm.pdf
Microchip gives an idea of where and what the algorithm is used for:
https://ww1.microchip.com/downloads/aemDocuments/documents/O...
CORDIC hardware accelerators are found in many microcontrollers designed to control electric motors.
If you liked this, it’s worth reading Donald Knuth seminal “The Art of Computer Programming” which explains a number of mathematical algorithms by example.
So, yeah.
> The signal each neuron outputs is calculated from this number, according to its activation function
The activation function is usually a sigmoid, which is also usually defined in terms of trigonometric functions
Which neural networks don’t use trigonometric functions or equivalent?
I don't immediately see how cordic would help you calculate e^(x) unless x is complex valued (which it is usually not).
It's literally been decades, but the last I learned about neural networks, a non-linear activation function was important for Turing completeness.
The nonlinearity (plus at least two layers) are required to solve nonlinear problems (like the famous XOR example).
However, the "decision point" of the ReLU (and it's everywhere-differentiable friends like leaky ReLU or ELU) provides a sufficient non-linearity - in essence, just as a sigmoid effectively results in a yes/no chooser with some stuff in the middle for training purposes, so does the ReLU "elbow point".
Sigmoid has a problem of 'vanishing gradients' in deep networks, as the sigmoid gradients of 0 - 0.25 in standard backpropagation means that a 'far away' layer will have tiny, useless gradients if there's a hundred sigmoid layers in between.
clamp(x/2, -1, 1) is basically a sigmoid.
Lookup tables with interpolation are much faster
Let people enjoy things.
living rent free generally means that you are able to easily recall a piece of knowledge (and often do, even if you don't want to).
something that pays rent might be like an important concept for an upcoming school test, for a class you're not interested in.
The term in its original use described an obsession with a particular group or thing and called it out as pointless. I don't think it would make sense for rent to mean effort in this original sense, nor in the broader way it's used here.
In addition, it literally says "rent free". There's nothing intellectual or pseudo-intellectual about reading the phrase "rent free" and concluding that the information does not pay anything to live in your head. This is basic reading comprehension.
Get a hobby.
Yes it is, much like "dumpster fire" or "toxic" or "gaslighting" or any other contemporary Reddit-isms that are associated with hyper-reductionist cultural hot takes. My personal distaste for them, however, has no bearing on the fact that they have very real meaning and people enjoy using them.
> By now, it has the same effect as saying “it’s nice” but uses lots more letters.
The commonly-held meaning of this is that it describes a pervasive, intrusive thought, not a nice one. This particular turn of phrase is often used to describe how politicians devote energy to talking about other politicians that they don't like, for instance.
Hey it's a quine, too!
Doesn't seem to be what the author meant in this case.
It works when there's something that is problematic for taking up space in your head. It colors the way you think in a way you wish was not the case.
While I could strain and make that sort of make sense here (eg it uses up some capacity that would otherwise allow the author to remember other more immediately useful algorithms), it's clear from the article that the author only means "I still remember the CORDIC algorithm" and nothing more.
Not a big deal, but it momentarily annoyed me as a "clickbait and switch" headline.