Ways to do modulo multiplication with primitive types
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Modular multiplication computes (a * b) % m where a, b, and m are integers. The challenge is that a * b may overflow the primitive integer type (e.g., 64-bit int) before the modulo can be applied. For example, multiplying two numbers near 2^63 overflows a 64-bit signed integer even though the final result after modulo fits easily. This article covers techniques to compute (a * b) % m safely with primitive types: using modular properties, Russian peasant multiplication, 128-bit intermediates, and language-specific solutions in C++, Java, and Python.
The Overflow Problem
Method 1: Modular Arithmetic Properties
The key property: (a * b) % m = ((a % m) * (b % m)) % m
This only helps if a or b is much larger than m. If both a and b are already less than m but a * b overflows, this does not prevent the overflow.
Method 2: Russian Peasant Multiplication (Binary Method)
Computes (a * b) % m using repeated doubling, avoiding any multiplication larger than 2 * m:
Time complexity: O(log b) — one iteration per bit of b. This is the standard approach for competitive programming.
Python Implementation
Python integers have arbitrary precision, so overflow is not an issue. Use pow(a, b, m) for modular exponentiation.
Method 3: 128-bit Intermediates (C/C++)
GCC and Clang support __int128 for intermediate calculations:
This is the simplest and fastest solution when __int128 is available (GCC/Clang on 64-bit systems). MSVC does not support __int128.
Method 4: Java Solutions
Java does not have unsigned 64-bit integers or __int128. Use Math.floorMod, BigInteger, or manual methods:
Method 5: Modular Exponentiation
(a^n) % m uses repeated squaring with modular multiplication:
Method Summary
| Method | Overflow-Safe? | Speed | Availability |
(a%m * b%m) % m | Only if m < 2^32 | O(1) | All languages |
| Russian peasant | Yes | O(log b) | All languages |
__int128 intermediate | Yes | O(1) | GCC/Clang only |
BigInteger | Yes | O(n^2) | Java |
Python native % | Yes (arbitrary precision) | O(1) amortized | Python |
Common Pitfalls
- Assuming
(a * b) % mis safe without checking ranges: In C/C++ and Java, ifa * boverflows the integer type, the result is undefined behavior (C/C++) or wraps around (Java). Always verify thataandbare small enough for direct multiplication, or use a safe method. - Forgetting to reduce
aandbbefore multiplication: Even with the Russian peasant method, reducea %= mfirst. Ifa > m, the additions inside the loop can still overflow whena + result > INT64_MAX. - Using
__int128on MSVC or 32-bit systems:__int128is a GCC/Clang extension on 64-bit platforms. It is not available on MSVC or 32-bit targets. Use the binary method orBigIntegeras a portable alternative. - Not using Python's built-in
pow(a, b, m)for modular exponentiation: Python's three-argumentpow()is implemented in C and uses efficient modular exponentiation internally. Writing your own is slower and unnecessary. - Confusing modulo behavior with negative numbers: C/C++
%can return negative values for negative operands. Use((a % m) + m) % mto guarantee a non-negative result. Java'sMath.floorMod()handles this correctly.
Summary
- The core challenge is that
a * bmay overflow before% mcan be applied - Russian peasant multiplication (binary method) is the universal O(log b) solution for any language
- Use
__int128intermediates in GCC/Clang for the simplest and fastest solution - In Java, use
BigIntegerfor correctness or the binary method for performance - Python has arbitrary-precision integers, so
(a * b) % mworks directly without overflow concerns

