Skip to content

Commit 47ff5ed

Browse files
authored
Add modular exponentiation function (#194)
1 parent b5d1199 commit 47ff5ed

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#' Computes modular exponentiation using fast binary exponentiation.
2+
#'
3+
#' @param base Numeric or integer base.
4+
#' @param exp Non-negative integer exponent.
5+
#' @param mod Optional positive integer modulus. If `NULL`, computes
6+
#' \eqn{base^{exp}} without modulus (may overflow for large values).
7+
#'
8+
#' @return If `mod` is provided, returns an integer in \[0, mod - 1\] equal to
9+
#' \eqn{(base^{exp}) \bmod mod}. If `mod` is `NULL`, returns \eqn{base^{exp}}.
10+
#'
11+
#' @details
12+
#' Implements **binary (fast) exponentiation** running in \eqn{O(\log exp)} time
13+
#' and \eqn{O(1)} extra space.
14+
#' - When `mod` is provided, intermediate values are reduced modulo `mod` to
15+
#' avoid overflow and keep numbers bounded.
16+
#' - Negative bases are handled correctly in modular mode by normalizing
17+
#' \code{base <- (base \%\% mod + mod) \%\% mod}.
18+
#' - Negative exponents are **not supported** (would require modular inverse).
19+
#'
20+
#' @examples
21+
#' # 2^10 = 1024, and 1024 mod 1000 = 24
22+
#' modular_exponentiation(2, 10, 1000)
23+
#' # [1] 24
24+
#' modular_exponentiation(3, 0, 7) # 1
25+
#' modular_exponentiation(5, 3) # 125 (no modulus)
26+
#' modular_exponentiation(-2, 5, 13) # 6 because (-2)^5 = -32 ≡ 6 (mod 13)
27+
#'
28+
#' @seealso \code{\link[base]{%%}} for modulus operator.
29+
#'
30+
#' @export
31+
modular_exponentiation <- function(base, exp, mod = NULL) {
32+
# validate exponent
33+
if (length(exp) != 1 || is.na(exp) || exp < 0 || exp != as.integer(exp)) {
34+
stop("`exp` must be a single non-negative integer.")
35+
}
36+
exp <- as.integer(exp)
37+
38+
# no modulus: compute power with fast exponentiation (may overflow for large numbers)
39+
if (is.null(mod)) {
40+
result <- 1
41+
b <- base
42+
e <- exp
43+
while (e > 0) {
44+
if (e %% 2L == 1L) result <- result * b
45+
b <- b * b
46+
e <- e %/% 2L
47+
}
48+
return(result)
49+
}
50+
51+
# validate modulus
52+
if (length(mod) != 1 || is.na(mod) || mod <= 0 || mod != as.integer(mod)) {
53+
stop("`mod` must be a single positive integer when provided.")
54+
}
55+
mod <- as.integer(mod)
56+
57+
# normalize base into [0, mod-1]
58+
b <- ((base %% mod) + mod) %% mod
59+
result <- 1L
60+
e <- exp
61+
62+
while (e > 0L) {
63+
if (e %% 2L == 1L) {
64+
result <- (result * b) %% mod
65+
}
66+
b <- (b * b) %% mod
67+
e <- e %/% 2L
68+
}
69+
result
70+
}

0 commit comments

Comments
 (0)