Reading old C code can be an adventure. Trades that aren’t even considered today were often made differently, and reading the old code gives a couple minutes of alien chills followed by the flash of new insight. One example for me was encountering binary angle measures (BAMs). Back in the days when floating point numbers were too expensive, code using a lot of trigonometry (e.g. games, signal processing, physics simulators) needed a more efficient way to represent angles.

A common hack of the day was to use BAMs, which allowed encoding angles as ordinary integers with the associated addition and multiplication working as expected and even being modular about the unit circle. If you haven’t seen the trick before, meditate on it a moment before scrolling down. It isn’t that difficult to derive, and it is a fun trick to realize for yourself.

Have you got it? Here is one last hint. Try to map angles to integers such that overflow occurs at one revolution. If a calculation results in 361 degrees, that should be the same as simply getting 1 degree.

The easiest way to understand it is with a diagram of the bitfield. Assuming the angles are encoded as 8 bit integers. All units are in degrees.

Diagram of an 8 bit BAM

msb lsb

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

| | | | | | | |

180 90 45 22.5 11.25 5.625 2.8125 1.40625

The scheme is to take the value where overflow is desired, 360 in this case, assign the most significant bit to be half that value, and assign each subsequent bit as half the previous. Because this is how ordinary binary integers are represented, arithmetic operations are preserved. Two BAMs may be added together with an ordinary addition operation with no special consideration. Any given BAM may be multiplied by an integer constant with no special consideration.

Of course, there is precision loss, but that is a factor for any finite representation of an infinite set. BAMs as a representation have more than enough positive attributes to make up for it. Because the encoding works exactly like binary integers, the machine operations are the same. Because overflow occurs at exactly one revolution, there is never mucking with modulo arithmetic because it is handled by overflow. These traits combine to deliver both great performance and simple code.

It is doubly fun that the exact same scheme works to represent signed angles instead of unsigned. The only difference is the representable range goes from [0, 360) to [-180, 180). However, there is an important caveat here that signed integer overflow is undefined behavior in C, so a compiler makes no guarantees with attempts to encode the angles directly as signed numbers.

Converting to and from BAMs is also simple. Any novice bit hacker could write the loop scanning and setting the individual bits in a couple minutes, but there is an easier and faster way amounting to one line of C code that I leave as a puzzle for the reader.

If old code is familiar to you, I expect you have encountered this trick before, but I was fascinated the first time I saw it in production code. It was something never taught in school. If this is your first introduction, I hope you find it as cool as I do. Modern sensibilities might deem BAMs an ugly performance hack, but that’s a boring view. There is a consistent and obvious reason it works. Nothing could be more elegant.