In [1]:
bin(19) # convert to binary
# note 0b at the beginning, which is just to tell user that this is a binary representation
Out[1]:
'0b10011'
In [2]:
bin(123210) 
Out[2]:
'0b11110000101001010'
In [5]:
bin(19)[-1] # get last bit
Out[5]:
'0'
In [8]:
bin(19)[2:]  # get rid of the '0b' at the beginning
Out[8]:
'10011'
In [9]:
def exp_mod(a,e,b):
    """Returns a^e mod b, using repeated squaring and binary expansion.
    Computes same thing as exp_mod from last time, but much faster"""
    bin_rep = bin(e)[2:] # gets rid of '0b' from the binary expansion
    prod = 1  # keep track of the current product
    cur_a_powtwo = a % b  # keeps track of a^(2^i) for higher and higher i
    for i in range(len(bin_rep)):
        if bin_rep[-1-i]=='1': # gets ith from last element in bin rep
            prod = prod * cur_a_powtwo % b  # if corresponding bit is 1, 
            # multiply prod by the current a^(2^i)
        cur_a_powtwo = (cur_a_powtwo)^2 % b  # repeated squaring step
    return prod
In [10]:
exp_mod(3,13,7)
Out[10]:
3
In [11]:
exp_mod(3,19,7)
Out[11]:
3
In [12]:
exp_mod(2,4,7)
Out[12]:
2
In [14]:
exp_mod(5,8,6)
Out[14]:
1
In [19]:
exp_mod(54,1223476600033243,52)
Out[19]:
24
In [20]:
# number of steps to compute above:
len(bin(1223476600033243))
Out[20]:
53
In [21]:
exp_mod(54,12234131212376600033243,52)
Out[21]:
20
In [ ]:
# steps to compute exp_mod(a,e,b) is around number of bits in e, approx log_2(e)