HomeAbout UsContact Us

Bitwise Modulo in C: Optimizing Embedded Systems

By Jithin Tom
Published in Embedded C/C++
September 26, 2026
5 min read
Bitwise Modulo in C: Optimizing Embedded Systems

Table Of Contents

01
Root Cause Analysis: The Cost of Division
02
Solution: Power-of-Two Masking
03
Performance Measurement
04
Circular Buffer Operation
05
Limitations and Constraints
06
Alternatives for Non-Power-of-Two Divisors
07
Practical Examples
08
Verification and Testing
09
Best Practices
10
Related Reading
11
References
12
Frequently Asked Questions

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.

Root Cause Analysis: The Cost of Division

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:

  1. Division to compute quotient
  2. Multiplication of quotient by divisor
  3. Subtraction to obtain remainder

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 32
uint8_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.

Solution: Power-of-Two Masking

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):

  • Mask = 32 - 1 = 31 (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.

Performance Measurement

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:

OperationInstructions EmittedExecution CyclesNotes
x % var (var = 32)UDIV + MLS3 – 13Operand-dependent early termination
x & 31AND1Single-cycle immediate bitwise AND
x % var (var = 33)UDIV + MLS3 – 13Non-power-of-two variable divisor
Constant x % 33UMULL + LSR + MLS3 – 4GCC division-by-invariant optimization
Constant x % 32AND1GCC auto-reduces constant unsigned power-of-two %
Lookup Table (32)LDRB (indexed)2 – 3Includes 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):

  • Modulo version (runtime variable divisor): ~100,000–160,000 cycles
  • Bitwise version: ~40,000 cycles
  • Speedup: 2.5–4x end-to-end (arithmetic-only speedup is 3–13x)

For ISR context where every cycle matters, this reduction directly translates to lower interrupt latency and increased available CPU time for background tasks.

Circular Buffer Operation

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.

Limitations and Constraints

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)

Critical Pitfall: Signed Integers and Negative Dividends

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 0x00000003
int32_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.

Does the Compiler Already Optimize Modulo?

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?

  1. Dynamic and Struct-Based Buffers: In modular device drivers, ring buffer sizes are configured at runtime or accessed through pointers (e.g., 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.
  2. Debug Builds (-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.
  3. Architectures Lacking Hardware Dividers: On 8-bit/16-bit or legacy architectures (e.g., AVR, PIC, MSP430, Cortex-M0), software division libraries carry severe latency penalties if constant propagation fails.

Common non-power-of-two divisors in embedded systems include:

  • Sensor sampling rates (e.g., 100 Hz → 10ms period)
  • Communication baud rates (e.g., 115200, 9600)
  • Display refresh rates (e.g., 60Hz, 75Hz)
  • Motor control PWM periods

Alternatives for Non-Power-of-Two Divisors

When the divisor isn’t a power of two, several strategies can reduce modulo cost:

1. Compiler Optimizations

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/10
uint32_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.

2. Lookup Tables

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.

3. Algorithm Restructuring

Redesign to avoid modulo entirely:

  • Use power-of-two buffer sizes (e.g., 64 instead of 50)
  • Implement circular buffers with pointer comparison instead of modulo
  • Use chain buffers or linked lists for variable-size elements
  • Apply hysteresis or debouncing to reduce update frequency

4. Power-of-Two Division via Bit Shift

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.

Practical Examples

Example 1: Periodic Task Execution in ISR

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
}

Example 2: PWM Duty Cycle Calculation

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;

Example 3: Quadrature Encoder Position

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 overflow
uint32_t angle_deg = ((uint32_t)(raw_count & ENCODER_MASK) * 360UL) / ENCODER_PPR;

Verification and Testing

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 values
test_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);

Best Practices

  1. Enable compiler warnings: Use -Woverflow to detect shift/out-of-range issues
  2. Document constraints: Comment why power-of-two sizing is required
  3. Use meaningful macro names: BUFFER_MASK vs BUFFER_SIZE_MINUS_ONE
  4. Consider alignment: Power-of-two sizes often improve memory access performance
  5. Profile first: Ensure modulo is actually a bottleneck before optimizing

References

  1. ARM. “Cortex-M4 Devices Generic User Guide.” ARM DUI 0553A. 2026.
  2. STMicroelectronics. “STM32F4xx Reference Manual.” RM0090. ST.com CDN, 2026.
  3. FreeRTOS.org. “FreeRTOS Kernel Developer Guide.” 2026.
  4. H. Warren Jr. “Hacker’s Delight: Chapter 10 - Integer Division By Constants.” 2nd ed. Addison-Wesley, 2012.
  5. M. Tremblay et al. “Embedded Systems Dictionary.” 2nd ed. Springer, 2024.
  6. GCC Documentation. “Integer Division Optimization.” GNU Compiler Collection 12.2, 2026.

Frequently Asked Questions

What is bitwise modulo and how does it differ from the % operator?

Bitwise modulo uses AND operations with a mask (value-1) to compute modulo for powers of two, avoiding division. It's faster than % but only works when the divisor is a power of two.

When can I safely replace % with bitwise AND in embedded C?

Replace % with & (mask) only when the divisor is a power of two (e.g., 2, 4, 8, 16, 32) AND the dividend is unsigned or non-negative. For negative signed integers, C's % truncates toward zero, yielding negative remainders, whereas bitwise AND wraps into [0, mask].

How much performance improvement can bitwise modulo offer on Cortex-M?

Bitwise modulo eliminates division overhead—typically 20-40+ cycles for % on Cortex-M0 (software) or 3-13 cycles on Cortex-M3/M4 (hardware UDIV+MLS) vs 1 cycle for &.

What are alternatives when the divisor isn't a power of two?

Use compiler strength reduction (multiplication by reciprocal invariants), precompute lookup tables for small ranges, or adjust algorithm design to use power-of-two buffer sizes with compile-time static assertions.

Tags

embedded-coptimizationbitwisemodulostm32

Share


Previous Article
FreeRTOS Memory Pool: Zero-Copy Data Transfer
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Software UART using GPIO and Timer in Embedded C
Software UART using GPIO and Timer in Embedded C
September 24, 2026
4 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media