
Bitwise modulo optimization is a critical technique for reducing latency in time-sensitive embedded applications. The modulo operator (%) invokes division, which is expensive on microcontrollers. On ARM Cortex-M cores, a 32-bit modulo operation is costly: the Cortex-M0 lacks a hardware divider and requires 20-40+ clock cycles for a software division library call. Even on Cortex-M3/M4 with hardware dividers, the full modulo sequence (UDIV + MLS) consumes 3-13 cycles depending on the operands. In contrast, a bitwise AND operation executes in a single cycle. When the divisor is a power of two, modulo can be replaced with a bitwise AND using a mask (divisor-1), yielding significant performance improvements.
This optimization is particularly valuable in interrupt service routines (ISRs), real-time operating system (RTOS) tick handlers, and digital signal processing (DSP) algorithms where modulo operations appear frequently. Common applications include circular buffer indexing, waveform generation, fixed-point arithmetic, and task scheduling algorithms.
Microcontrollers often lack dedicated hardware division units. The Cortex-M0, for example, implements division in software via a loop that consumes variable cycles based on the dividend and divisor values. Even Cortex-M3/M4 with hardware dividers still require multiple cycles for modulo operations. Compiler-generated code for % typically involves:
This sequence translates to multiple instructions and memory accesses. In worst-case scenarios on Cortex-M0, a single % operation can exceed 40 cycles—equivalent to dozens of NOP instructions or multiple GPIO toggles. On Cortex-M3/M4, the hardware UDIV instruction takes 2-12 cycles, but the compiler must also emit an MLS (multiply-and-subtract) to compute the remainder, bringing the total modulo cost to 3-13 cycles—still far more than a single-cycle bitwise AND.
Consider a circular buffer implementation:
#define BUFFER_SIZE 32uint8_t buffer[BUFFER_SIZE];uint16_t head = 0;void buffer_write(uint8_t data) {buffer[head] = data;head = (head + 1) % BUFFER_SIZE; // Expensive modulo}
Each buffer write incurs the division overhead. At 1 MHz CPU frequency, the modulo alone consumes 20 cycles per write. Combined with the store, increment, and function call overhead, the effective write throughput drops well below 50 kHz—insufficient for many real-time applications.
When the divisor is a power of two, the modulo operation x % n (where n = 2^k) is mathematically equivalent to x & (n - 1) for all non-negative integers (x >= 0). This identity holds because powers of two have exactly one bit set in binary representation. Subtracting one produces a contiguous bitmask containing ones in all lower k positions:
n = 2^k = 0b...000100...00 (bit k set)n - 1 = 2^k - 1 = 0b...000011...11 (k bits set from bit 0 to k-1)x & (n - 1) clears all bits >= 2^k and preserves the lower k bits,which strictly equals x mod (2^k) for all x >= 0.
For BUFFER_SIZE = 32 (2^5):
0b00011111)(head + 1) & 31 directly extracts the remainder after division by 32.The optimized implementation with compile-time safety:
#include <stdint.h>#define BUFFER_SIZE 32#define BUFFER_MASK (BUFFER_SIZE - 1)// Enforce power-of-two sizing at compile time_Static_assert((BUFFER_SIZE != 0) && ((BUFFER_SIZE & (BUFFER_SIZE - 1)) == 0),"BUFFER_SIZE must be a non-zero power of two");uint8_t buffer[BUFFER_SIZE];uint16_t head = 0;void buffer_write(uint8_t data) {buffer[head] = data;head = (head + 1) & BUFFER_MASK; // Single-cycle AND}
This transformation eliminates division entirely. The AND operation compiles to a single ands instruction on Cortex-M, executing in one cycle regardless of input values.
Benchmarking on STM32F407 (Cortex-M4F @ 168 MHz, 0 wait states via 64-KB CCM SRAM) with GCC 11.2 (-O2). Note that divisors are tested as runtime variables to prevent constant-folding, isolating the underlying arithmetic execution cost:
| Operation | Instructions Emitted | Execution Cycles | Notes |
|---|---|---|---|
x % var (var = 32) | UDIV + MLS | 3 – 13 | Operand-dependent early termination |
x & 31 | AND | 1 | Single-cycle immediate bitwise AND |
x % var (var = 33) | UDIV + MLS | 3 – 13 | Non-power-of-two variable divisor |
Constant x % 33 | UMULL + LSR + MLS | 3 – 4 | GCC division-by-invariant optimization |
Constant x % 32 | AND | 1 | GCC auto-reduces constant unsigned power-of-two % |
| Lookup Table (32) | LDRB (indexed) | 2 – 3 | Includes address calculation and memory latency |
In a tight loop executing 10,000 iterations (including loop control overhead of ~3 cycles per iteration for branch, increment, and compare):
For ISR context where every cycle matters, this reduction directly translates to lower interrupt latency and increased available CPU time for background tasks.
Here’s how bitwise modulo enables efficient circular buffer wrapping:
+---------------------+| Circular Buffer || (Size = 32) |+----------+----------+|v+----+ +----+ +----+ +----+ +----+ +----+ +----+| 00 | | 01 | | 02 | |... | | 30 | | 31 | | 00 |...+----+ +----+ +----+ +----+ +----+ +----+ +----+^ ^ ^| | |head=0 head=31 head=32(0 & 31 = 0) (31 & 31 = 31) (32 & 31 = 0)
When head reaches 32, 32 & 31 wraps it to 0, implementing modulo 32 with a single AND operation.
The bitwise modulo optimization applies only when the divisor is a power of two. Attempting to use x & (n-1) for non-powers of two produces incorrect results. For example:
10 % 3 = 1 but 10 & (3-1) = 10 & 2 = 2 (incorrect)10 % 6 = 4 but 10 & (6-1) = 10 & 5 = 0 (incorrect)A common bug in embedded firmware arises when applying bitwise masking to signed integers. In C99 and later (ISO/IEC 9899:1999 §6.5.5), integer division truncates toward zero, which mandates that the remainder retains the algebraic sign of the dividend:
// C99 / C11 / C23 Truncated Remainder Semantics:-5 % 4 == -1
However, bitwise AND operates on the underlying two’s complement bit pattern without sign extension:
// 32-bit two's complement representation:// -5 is 0xFFFFFFFB// 3 is 0x00000003int32_t val = -5;int32_t masked = val & 3; // Evaluates to 3, NOT -1!
Because -1 != 3, x % n and x & (n - 1) diverge for negative values. If you are computing circular wrapping on bidirectional sensor deltas or audio waveforms where x < 0, bitwise AND yields the positive modular congruence in [0, n - 1]. While positive modular wrapping is frequently desired in circular topology, assuming it mirrors standard C % will introduce functional discrepancies. Always use unsigned integer types (uint32_t, size_t) when replacing % with bitwise AND.
Modern compilers (GCC, Clang) with optimization enabled (-O1, -O2, -Os) already automatically transform x % CONST_POW2 into x & (CONST_POW2 - 1) for unsigned types.
Why should embedded engineers still write explicit bitwise masks?
ring_buf->size). The compiler cannot know at compile time that the struct member is a power of two, forcing it to emit a division instruction (UDIV) or call a software division routine (__aeabi_uidivmod). Storing ring_buf->mask in the struct allows single-cycle indexing regardless of optimization flags.-O0): In unoptimized debug builds, compilers do not perform strength reduction. A modulo in an ISR compiled at -O0 will execute a slow division, potentially causing missed deadlines during JTAG debugging.Common non-power-of-two divisors in embedded systems include:
When the divisor isn’t a power of two, several strategies can reduce modulo cost:
Modern compilers (GCC 6+, LLVM 5+) replace constant modulo operations with optimized sequences using multiplication, shifts, and adds—known as “division by invariant integers.” For example:
// Original source:uint32_t rem = x % 10;// GCC/Clang at -O2 transform this into (Hacker's Delight Ch. 10):// 0xCCCCCCCD / 2^35 approximates 1/10uint32_t quot = (uint32_t)(((uint64_t)x * 0xCCCCCCCDULL) >> 35);uint32_t remainder = x - (quot * 10); // equivalent to x % 10
On Cortex-M4, GCC implements this using UMULL (1 cycle), LSRS (1 cycle), and MLS (1 cycle), completing in only 3–4 cycles. On Cortex-M0 cores lacking UMULL, this sequence requires multiple 32-bit arithmetic steps consuming ~10–12 cycles—still substantially faster than a software division loop.
For small divisors (< 256), precompute a remainder table:
const uint8_t mod3_table[256] = {0,1,2,0,1,2,0,1,2, /* ... repeats every 3 entries */};uint8_t fast_mod3(uint16_t x) {return mod3_table[x & 0xFF]; // Plus adjustment for high byte if needed}
Trade-off: ROM space for RAM/CPU time. Suitable when divisor is small and fixed.
Redesign to avoid modulo entirely:
When the divisor is a power of two, division itself can also be replaced by a right shift:
// When N = 2^k, division by N becomes a right shift:// Instead of: average = sum / N;average = sum >> k;
This is complementary to the bitwise modulo technique: x / N becomes x >> k, and x % N becomes x & (N - 1). Together, they eliminate both division and modulo with single-cycle operations.
Instead of using modulo to execute a block of code every N interrupts, use a power-of-two mask. For example, to execute an operation every 16th interrupt:
#define ISR_DECIMATION_FACTOR 16#define ISR_MASK (ISR_DECIMATION_FACTOR - 1)void TIM2_IRQHandler(void) {static uint32_t isr_counter = 0;isr_counter++;if ((isr_counter & ISR_MASK) == 0) {// Run decimated operation}// Clear interrupt flag}
For edge-aligned PWM with period = 256 ticks (power of two):
#define PWM_PERIOD 256#define PWM_MASK (PWM_PERIOD - 1)uint16_t duty = (counter & PWM_MASK) < compare_value ? 1 : 0;
Encoder counts often use power-of-two PPR (pulses per revolution) for easy angle calculation:
#define ENCODER_PPR 1024UL // 2^10#define ENCODER_MASK (ENCODER_PPR - 1UL)// Use explicit 32-bit unsigned literals to prevent 16-bit architecture overflowuint32_t angle_deg = ((uint32_t)(raw_count & ENCODER_MASK) * 360UL) / ENCODER_PPR;
When applying bitwise modulo optimization, verify correctness across the full input range:
#include <assert.h>#include <stdint.h>void test_bitwise_modulo(uint32_t divisor) {// Verify non-zero power of two (guards against division by zero)assert(divisor != 0 && (divisor & (divisor - 1)) == 0);uint32_t mask = divisor - 1;for (uint32_t i = 0; i < 1000000; ++i) {assert((i % divisor) == (i & mask));}}// Test common power-of-two valuestest_bitwise_modulo(2);test_bitwise_modulo(4);test_bitwise_modulo(8);test_bitwise_modulo(16);test_bitwise_modulo(32);test_bitwise_modulo(64);test_bitwise_modulo(128);test_bitwise_modulo(256);
-Woverflow to detect shift/out-of-range issuesBUFFER_MASK vs BUFFER_SIZE_MINUS_ONEQuick Links
Legal Stuff





