Consolidate binary exponentiation files (#10742)

* Consolidate binary exponentiation files

* updating DIRECTORY.md

* Fix typos in doctests

* Add suggestions from code review

* Fix timeit benchmarks

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
This commit is contained in:
Tianyi Zheng 2023-10-21 13:27:36 -04:00 committed by GitHub
parent 47c19d9b2d
commit 06edc0eea0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 182 additions and 125 deletions

View File

@ -578,9 +578,7 @@
* [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py)
* [Base Neg2 Conversion](maths/base_neg2_conversion.py)
* [Basic Maths](maths/basic_maths.py)
* [Binary Exp Mod](maths/binary_exp_mod.py)
* [Binary Exponentiation](maths/binary_exponentiation.py)
* [Binary Exponentiation 2](maths/binary_exponentiation_2.py)
* [Binary Multiplication](maths/binary_multiplication.py)
* [Binomial Coefficient](maths/binomial_coefficient.py)
* [Binomial Distribution](maths/binomial_distribution.py)

View File

@ -1,28 +0,0 @@
def bin_exp_mod(a: int, n: int, b: int) -> int:
"""
>>> bin_exp_mod(3, 4, 5)
1
>>> bin_exp_mod(7, 13, 10)
7
"""
# mod b
assert b != 0, "This cannot accept modulo that is == 0"
if n == 0:
return 1
if n % 2 == 1:
return (bin_exp_mod(a, n - 1, b) * a) % b
r = bin_exp_mod(a, n // 2, b)
return (r * r) % b
if __name__ == "__main__":
try:
BASE = int(input("Enter Base : ").strip())
POWER = int(input("Enter Power : ").strip())
MODULO = int(input("Enter Modulo : ").strip())
except ValueError:
print("Invalid literal for integer")
print(bin_exp_mod(BASE, POWER, MODULO))

View File

@ -1,48 +1,196 @@
"""Binary Exponentiation."""
"""
Binary Exponentiation
# Author : Junth Basnet
# Time Complexity : O(logn)
This is a method to find a^b in O(log b) time complexity and is one of the most commonly
used methods of exponentiation. The method is also useful for modular exponentiation,
when the solution to (a^b) % c is required.
To calculate a^b:
- If b is even, then a^b = (a * a)^(b / 2)
- If b is odd, then a^b = a * a^(b - 1)
Repeat until b = 1 or b = 0
For modular exponentiation, we use the fact that (a * b) % c = ((a % c) * (b % c)) % c
"""
def binary_exponentiation(a: int, n: int) -> int:
def binary_exp_recursive(base: float, exponent: int) -> float:
"""
Compute a number raised by some quantity
>>> binary_exponentiation(-1, 3)
-1
>>> binary_exponentiation(-1, 4)
1
>>> binary_exponentiation(2, 2)
4
>>> binary_exponentiation(3, 5)
Computes a^b recursively, where a is the base and b is the exponent
>>> binary_exp_recursive(3, 5)
243
>>> binary_exponentiation(10, 3)
1000
>>> binary_exponentiation(5e3, 1)
5000.0
>>> binary_exponentiation(-5e3, 1)
-5000.0
>>> binary_exp_recursive(11, 13)
34522712143931
>>> binary_exp_recursive(-1, 3)
-1
>>> binary_exp_recursive(0, 5)
0
>>> binary_exp_recursive(3, 1)
3
>>> binary_exp_recursive(3, 0)
1
>>> binary_exp_recursive(1.5, 4)
5.0625
>>> binary_exp_recursive(3, -1)
Traceback (most recent call last):
...
ValueError: Exponent must be a non-negative integer
"""
if n == 0:
if exponent < 0:
raise ValueError("Exponent must be a non-negative integer")
if exponent == 0:
return 1
elif n % 2 == 1:
return binary_exponentiation(a, n - 1) * a
if exponent % 2 == 1:
return binary_exp_recursive(base, exponent - 1) * base
else:
b = binary_exponentiation(a, n // 2)
return b * b
b = binary_exp_recursive(base, exponent // 2)
return b * b
def binary_exp_iterative(base: float, exponent: int) -> float:
"""
Computes a^b iteratively, where a is the base and b is the exponent
>>> binary_exp_iterative(3, 5)
243
>>> binary_exp_iterative(11, 13)
34522712143931
>>> binary_exp_iterative(-1, 3)
-1
>>> binary_exp_iterative(0, 5)
0
>>> binary_exp_iterative(3, 1)
3
>>> binary_exp_iterative(3, 0)
1
>>> binary_exp_iterative(1.5, 4)
5.0625
>>> binary_exp_iterative(3, -1)
Traceback (most recent call last):
...
ValueError: Exponent must be a non-negative integer
"""
if exponent < 0:
raise ValueError("Exponent must be a non-negative integer")
res: int | float = 1
while exponent > 0:
if exponent & 1:
res *= base
base *= base
exponent >>= 1
return res
def binary_exp_mod_recursive(base: float, exponent: int, modulus: int) -> float:
"""
Computes a^b % c recursively, where a is the base, b is the exponent, and c is the
modulus
>>> binary_exp_mod_recursive(3, 4, 5)
1
>>> binary_exp_mod_recursive(11, 13, 7)
4
>>> binary_exp_mod_recursive(1.5, 4, 3)
2.0625
>>> binary_exp_mod_recursive(7, -1, 10)
Traceback (most recent call last):
...
ValueError: Exponent must be a non-negative integer
>>> binary_exp_mod_recursive(7, 13, 0)
Traceback (most recent call last):
...
ValueError: Modulus must be a positive integer
"""
if exponent < 0:
raise ValueError("Exponent must be a non-negative integer")
if modulus <= 0:
raise ValueError("Modulus must be a positive integer")
if exponent == 0:
return 1
if exponent % 2 == 1:
return (binary_exp_mod_recursive(base, exponent - 1, modulus) * base) % modulus
r = binary_exp_mod_recursive(base, exponent // 2, modulus)
return (r * r) % modulus
def binary_exp_mod_iterative(base: float, exponent: int, modulus: int) -> float:
"""
Computes a^b % c iteratively, where a is the base, b is the exponent, and c is the
modulus
>>> binary_exp_mod_iterative(3, 4, 5)
1
>>> binary_exp_mod_iterative(11, 13, 7)
4
>>> binary_exp_mod_iterative(1.5, 4, 3)
2.0625
>>> binary_exp_mod_iterative(7, -1, 10)
Traceback (most recent call last):
...
ValueError: Exponent must be a non-negative integer
>>> binary_exp_mod_iterative(7, 13, 0)
Traceback (most recent call last):
...
ValueError: Modulus must be a positive integer
"""
if exponent < 0:
raise ValueError("Exponent must be a non-negative integer")
if modulus <= 0:
raise ValueError("Modulus must be a positive integer")
res: int | float = 1
while exponent > 0:
if exponent & 1:
res = ((res % modulus) * (base % modulus)) % modulus
base *= base
exponent >>= 1
return res
if __name__ == "__main__":
import doctest
from timeit import timeit
doctest.testmod()
a = 1269380576
b = 374
c = 34
try:
BASE = int(float(input("Enter Base : ").strip()))
POWER = int(input("Enter Power : ").strip())
except ValueError:
print("Invalid literal for integer")
RESULT = binary_exponentiation(BASE, POWER)
print(f"{BASE}^({POWER}) : {RESULT}")
runs = 100_000
print(
timeit(
f"binary_exp_recursive({a}, {b})",
setup="from __main__ import binary_exp_recursive",
number=runs,
)
)
print(
timeit(
f"binary_exp_iterative({a}, {b})",
setup="from __main__ import binary_exp_iterative",
number=runs,
)
)
print(
timeit(
f"binary_exp_mod_recursive({a}, {b}, {c})",
setup="from __main__ import binary_exp_mod_recursive",
number=runs,
)
)
print(
timeit(
f"binary_exp_mod_iterative({a}, {b}, {c})",
setup="from __main__ import binary_exp_mod_iterative",
number=runs,
)
)

View File

@ -1,61 +0,0 @@
"""
Binary Exponentiation
This is a method to find a^b in O(log b) time complexity
This is one of the most commonly used methods of exponentiation
It's also useful when the solution to (a^b) % c is required because a, b, c may be
over the computer's calculation limits
Let's say you need to calculate a ^ b
- RULE 1 : a ^ b = (a*a) ^ (b/2) ---- example : 4 ^ 4 = (4*4) ^ (4/2) = 16 ^ 2
- RULE 2 : IF b is odd, then a ^ b = a * (a ^ (b - 1)), where b - 1 is even
Once b is even, repeat the process until b = 1 or b = 0, because a^1 = a and a^0 = 1
For modular exponentiation, we use the fact that (a*b) % c = ((a%c) * (b%c)) % c
Now apply RULE 1 or 2 as required
@author chinmoy159
"""
def b_expo(a: int, b: int) -> int:
"""
>>> b_expo(2, 10)
1024
>>> b_expo(9, 0)
1
>>> b_expo(0, 12)
0
>>> b_expo(4, 12)
16777216
"""
res = 1
while b > 0:
if b & 1:
res *= a
a *= a
b >>= 1
return res
def b_expo_mod(a: int, b: int, c: int) -> int:
"""
>>> b_expo_mod(2, 10, 1000000007)
1024
>>> b_expo_mod(11, 13, 19)
11
>>> b_expo_mod(0, 19, 20)
0
>>> b_expo_mod(15, 5, 4)
3
"""
res = 1
while b > 0:
if b & 1:
res = ((res % c) * (a % c)) % c
a *= a
b >>= 1
return res