Numeric Algorithms

Ready for some classic algorithms?

Basics

A numeric algorithm does some computation given one or more numeric values. For the purposes of measuring complexity, the size of a number is the number of bits (or digits) in the numbers, not the value of the numbers themselves!

Note the base of the numerals does not matter when computing asymptotic complexity. There is always a linear relationship between the number of digits required to represent a number in base $a$ and base $b$.

Exercise: Let $bits(n)$ be the number of bits required to represent the number $n$ in base 2, and $digits(n)$ be the number of digits required to represent in $n$ in base 10. Prove that $bits \in \Theta(digits)$.
Exercise: Why does this mean, technically, that we can disregard the base when giving asymptotic complexity?

Wikipedia has a nice collection of links to articles on numeric algorithms.

Addition

The addition algorithm you already know works in any base $\geq 2$, because the sum of any three digits is at most a two-digit number (in any base!).

Example:
  • In base 2, $1+1+1=11$
  • In base 8, $7+7+7=25$
  • In base 10, $9+9+9=27$
  • In base 16, $F+F+F=2D$.

The algorithm goes from right to left, propagating carries. In binary:

 101011010
 100010111
1001110001

In decimal:

 939972
 654108
1594080

The complexity is $\Theta(n)$. We can’t do better because we have to touch each bit anyway.

Subtraction is similar; you can do it in linear time.

Exercise: Give an algorithm for subtraction.

Multiplication and Division

You could multiply by repeated addition...

$$ \begin{eqnarray} 39053 \times 46987 & = & \underbrace{39053 + 39053 + 39053 + \ldots + 39053}_{46987\:terms} \\ \end{eqnarray} $$ ...but uggggh! That was 46986 additions of 5-digit integers!
Exercise: Prove that this algorithm has exponential time complexity.

You probably learned this algorithm before (shown here in base 2):

    1011
  × 1101
    1011
   0000
  1011
 1011   
10001111

The complexity of that algorithm is $\Theta(n^2)$, because multiplying an $n$-bit number by a one-bit number can be done in linear time, and we basically do that $n$ times. Then we do $n+1$ additions of $2n$-bit numbers: also quadratic.

Did you notice that all we did was just shifting and adding? And that shifting is really multiplying by 2? Look at the above example: it is saying $11 \times 13 = 11 + 44 + 88 = 143$. Some people actually carry out this binary-ish algorithm in decimal numbers like this:

 11    13
 22     6
 44     3
 88     1
143
Exercise: Try multiplying 4823 × 1089 this way.

Let’s generalize this to an actual algorithm. Here it is in Python, assuming $y \geq 0$:

def times(x, y):
    result = 0
    while y > 0:
        if y & 1:            # accumulate only when y is odd
            result += x
        y >>= 1              # halve y
        x <<= 1              # double x
    return result
Exercise: Rewrite this in Java, Ruby, and Julia. In Java you will need the BigInteger library.

There is also a quadratic time division algorithm. As long as $y \geq 1$, this Python function returns the quotient and the remainder:

def divide(x, y):
    if x == 0:
        return (0, 0)
    q, r = divide(x >> 1, y)
    q, r = (q << 1, r << 1)
    if x & 1:
        r += 1
    if r >= y:
        q, r = (q + 1, r - y)
    return (q, r)
Exercise: Rewrite in Julia and Ruby and Java.

Exponentiation

You can do exponentiation by repeated multiplication:

$$ \begin{eqnarray} 31^{400} & = & \underbrace{31 \times 31 \times 31 \times \ldots \times 31}_{400\:terms} \end{eqnarray} $$

Do we really have to do 399 multiplications? No! Let’s divide an conquer. First notice that, for example:

$$ x^{400} = x^{200}x^{200} $$

So we just have to compute $x^{200}$ once! Also note:

$$ x^{125} = x^{62}x^{62}x $$

In general:

$$ x^n = \begin{cases} 1 & n = 0\\ x^{\lfloor n/2\rfloor} \cdot x^{\lfloor n/2\rfloor} & n\;\mathrm{is\;even} \\ (x^{\lfloor n/2\rfloor} \cdot x^{\lfloor n/2\rfloor}) \cdot x & n\;\mathrm{is\;odd} \end{cases} $$

So as you recurse, halving the exponent, you do one multiplication when you get an even value, and two with an odd:

def pow(x, y):
    if y == 0:
        return 1
    h = pow(x, y // 2)
    result = h * h
    if y & 1:
        result *= x
    return result

This lets us do $31^{400}$ in 11 multiplcations, not 399. Divide and Conquer FTW:

$$ \begin{eqnarray} 31^{400} & = & 31^{200} \cdot 31^{200} \\ 31^{200} & = & 31^{100} \cdot 31^{100} \\ 31^{100} & = & 31^{50} \cdot 31^{50} \\ 31^{50} & = & 31^{25} \cdot 31^{25} \\ 31^{25} & = & 31^{12} \cdot 31^{12} \cdot 31 \\ 31^{12} & = & 31^6 \cdot 31^6 \\ 31^6 & = & 31^3 \cdot 31^3 \\ 31^3 & = & 31^1 \cdot 31^1 \cdot 31 \\ 31^1 & = & 31^0 \cdot 31^0 \cdot 31 \\ \end{eqnarray} $$

BUT WAIT! THIS IS NOT TAIL RECURSIVE! Even though you only recurse 7 levels deep, you are still stacking up partial results. This extra space can be significant! We can do better by using an iterative approach, based on the following observation:

$$ \begin{eqnarray} 31^{400} = 31^{16} + 31^{128} + 31^{256} \end{eqnarray} $$

I got that from the binary representation of 400 ($110010000_2$). Now walk through it, right to left; accumulate the result into $a$:

a = 1      #              a == 31^0
x = 31     # x == 31^1
x *= x     # x == 31^2
x *= x     # x == 31^4
x *= x     # x == 31^8
x *= x     # x == 31^16
a *= x     #              a == 31^16
x *= x     # x == 31^32
x *= x     # x == 31^64
x *= x     # x == 31^128
a *= x     #              a == 31^144
x *= x     # x == 31^256
a *= x     #              a == 31^400

Here it is in Python. Note the halving-and-doubling:

def pow(x, y):
    result = 1
    while y > 0:
        if y & 1:
            result *= x
        y >>= 1
        x *= x
    return result
Exercise: What is the fewest number of multiplications required? In the recursive version we only needed 10, computing the exponents [1, 2, 3, 6, 12, 25, 50, 100, 200, 400], while in the iterative approach we needed 11, for [1, 2, 4, 8, 16, 32, 64, 128, 144, 256, 400]. Is there a better way? Hint: Construct a list of integers from 1 to $n$ where each number but the first is the sum of only previous numbers. Try to minimize the size of the list. This technique was first observed, to my knowledge, by Phil Dorin.

Analysis Time! The number of multiplications is linear in the number of bits of the exponent (it can’t be over $2n$, right?) but what’s the complexity of the whole thing? You still have to compute (that is, at least write out), the whole answer, and how many bits are in that? First let’s find out what the answer is. Python says:

>>> 31**400
35049153521902777114708986131739468304466934398225961899541896904817914996246
23354936652173201312485109671587265639812785215391138217944131481131092898719
74685894570874283007085730451124921973944672872304632607933438457819117259406
11939966676143051741472986147114119144281388547152293994389062523413380947354
01786426523926432321776970921620521452184049782817872783269059536200968913247
54765133653979081932865951491386227358704384254668823602537295120143399193347
26030963677780786780733041458754894712578986705032469879343454522360163745820
4353222027948981902971920726384470581625600419245578432001

Okay now lets estimate the number of bits:

31400 ≅ (24.95)400 ≅ 21980

Practically a 2000-bit number (more or less). Hey, that’s pretty big, considering 31 is a 5-bit number, and 400 is a 9-bit number!

Exercise: How many bits are in the number (er, numeral) $x^y$ where $x$ and $y$ are both 5000-bit numbers?
Exercise: What is the complexity of that algorithm above for computing $x^y$, where $x$ and $y$ are both $n$-bit numbers? Remember you have to do at most n multiplications, but don’t forget to consider the size of the numbers being multiplied.

So writing out the result of an exponentiation is just too darn big! We’re usually a lot more interested in modular exponentation! This is coming up soon.

Integer Square Root

For a positive integer $n$, $\mathrm{isqrt}(n) =_{def}\lfloor \sqrt{n}\rfloor$.

The naïve approach is to “binary search it”. Let’s try to find the integer square root of 149.

We can do a little better by:

Factorial

This was covered on the first day of class. Review if you need to.

Fibonacci

This was covered on the first day of class. Review if you need to.

Greatest Common Divisor

Euclid’s algorithm is, for $y \geq 0$, shown here in Python:

def gcd(x, y):
    return x if y == 0 else gcd(y, x % y)
Exercise: Prove that this is correct.

Hey, did ya notice? It’s TAIL RECURISVE WOOO!

Examples

    gcd(253, 113)
    = gcd(113, 27)
    = gcd(27, 5)
    = gcd(5, 2)
    = gcd(2, 1)
    = gcd(1, 0)
    = 1

    gcd(2535, 1130)
    = gcd(1130, 275)
    = gcd(275, 30)
    = gcd(30, 5)
    = gcd(5, 0)
    = 5

    gcd(33031, 182845)
    = gcd(182845, 33031)
    = gcd(33031, 17690)
    = gcd(17690, 15341)
    = gcd(15341, 2349)
    = gcd(2349, 1247)
    = gcd(1247, 1102)
    = gcd(1102, 145)
    = gcd(145, 87)
    = gcd(87, 58)
    = gcd(58, 29)
    = gcd(29, 0)
    = 29
Exercise: Show why this algorithm has worst-case $\Theta(n^3)$ time complexity.
Exercise: Show the computation of $gcd(4181,6765)$ in the style of the examples above. What do you notice about the arguments to the recursive calls? Can you infer something about the “nastiest possible inputs” to this algorithm?

Least Common Multiple

It turns out that, for positive integers:

$$ \mathrm{lcm}(x,y) = \frac{xy}{\mathrm{gcd}(x,y)} $$

Since multiplying $x$ and $y$ might lead to a super large number, it is much more efficient to compute

$$ \mathrm{lcm}(x,y) = \left(\frac{x}{\mathrm{gcd}(x,y)}\right) y $$

In other words:

def gcd(x, y):
    return x if y == 0 else gcd(y, x % y)

def lcm(x, y):
    return (x / gcd(x, y)) * y

Modular Arithmetic

When numbers get too big, start counting over.

Example: 5395 seconds = 89 minutes, 55 seconds = 1 hour, 29 minutes, 55 seconds.
Example: As I write this, my computer says the time is 1424192736481. That’s the number of milliseconds since the epoch. Does that number mean much to you?

Let’s look at numbers mod 6:

-12-11-10-9-8-7-6-5-4-3-2-1-0 1234567891011121314
012345 012345 012345 012345 012

In the “mod 6 world”, both 2 and 10 map to 2. So 2 and 10 are in some sense equivalent in that world. We now have the notation “x ≡ y (mod N)”. More examples:

What do addition, multiplication, and exponentiation look like in a modular context?

Modular Addition

If the values to be added are in $0 ... N-1$, you don’t have to divide to get the remainder:

Example:
  • (6 + 8) mod 31 = 14
  • (27 + 29) mod 31 = 56 − 31 = 25
Exercise: Prove that modular addition has linear complexity.

If adding a lot of numbers, do the modulos on the intermediate results:

  (30 + 26 + 8 + 29 + 28 + 5) mod 31
= (((((30 + 26) mod 31 + 8) mod 31 + 29) mod 31 + 28) mod 31 + 5) mod 31
= ((((25 + 8) mod 31 + 29) mod 31 + 28) mod 31 + 5) mod 31
= (((2 + 29) mod 31 + 28) mod 31 + 5) mod 31
= ((0 + 28) mod 31 + 5) mod 31
= (28 + 5) mod 31
= 2

Modular Multiplication

To compute $xy\:\mathrm{mod}\:N$, multiply (that’s quadratic) then divide to get the remainder (also quadratic). Again, do the modulos on intermediate results:

  (30 × 26 × 8 × 29 × 28 × 5) mod 31
  (((((30 × 26) mod 31 × 8) mod 31 × 29) mod 31 × 28) mod 31 × 5) mod 31
= ((((5 × 8) mod 31 × 29) mod 31 × 28) mod 31 × 5) mod 31
= (((9 × 29) mod 31 × 28) mod 31 × 5) mod 31
= ((13 × 28) mod 31 × 5) mod 31
= (23 × 5) mod 31
= 22

Modular Exponentiation

To compute $x^y\:\mathrm{mod}\:N$, we can use the exponentiation algorithm above, doing the modulos for all intermediate results.

Let’s try 31400 mod 100:

a = 1              #              a == 1
p = 31             # p == 31^1 == 31
(p *= p) % 100     # p == 31^2 == 61
(p *= p) % 100     # p == 31^4 == 21
(p *= p) % 100     # p == 31^8 == 41
(p *= p) % 100     # p == 31^16 == 81
(a *= p) % 100     #              a == 31^16 == 81
(p *= p) % 100     # p == 31^32 == 61
(p *= p) % 100     # p == 31^64 == 21
(p *= p) % 100     # p == 31^128 == 41
(a *= p) % 100     #              a == 31^144 == 21
(p *= p) % 100     # p == 31^256 == 81
(a *= p) % 100     #              a == 31^400 == 1

Also see the Wikipedia article; it includes a nice code fragment. Here it is in Python:

def modpow(x, y, n):
    result = 1
    while y > 0:
        if y & 1:
            result = (result * x) % n
        y >>= 1
        x = (x * x) % n
    return result

That algorithm is pretty cool, and you should know how to do it, but in practice there will be libraries.

Modular Exponentiation is built into many languages

Let’s compute $5438394857757488^{3424255654452323}\:\mathrm{mod}\:234235256666421$ in multiple languages!

Python

>>> pow(5438394857757488,3424255654452323,234235256666421)
189325407946433

Julia

julia> powermod(5438394857757488,3424255654452323,234235256666421)
189325407946433

Scala

scala> BigInt("5438394857757488").modPow(BigInt("3424255654452323"), BigInt("234235256666421"))
res0: scala.math.BigInt = 189325407946433

Ruby

>> require 'openssl'
=> true
>> 5438394857757488.to_bn.mod_exp(3424255654452323,234235256666421).to_i
=> 189325407946433

Clojure

user=> (.modPow (biginteger 5438394857757488) (biginteger 3424255654452323) (biginteger 234235256666421))
189325407946433

JavaScript

Not built-in; you have to install a big integer library. Fortunately that’s not too hard. You can get big-integer:

> var bigInt = require("big-integer");
> bigInt(5438394857757488).modPow(bigInt(3424255654452323), bigInt(234235256666421)).toString()
'189325407946433'
Java

Oh, Java doesn’t have a REPL? Booooooo.

Doing modular exponentiation in your head

The fact that you can do the modulos at any intermediate results lets you do some cool computations without a calculator:

2345 mod 31
= (25)69 mod 31
= 3269 mod 31
= (32 mod 31)69 mod 31
= 169 mod 31
= (1 mod 31)69 mod 31
= 1

Once you get good at this stuff you just "pull out" the mod 31 and write only this:

2345 ≡ (25)69 ≡ 3269 ≡ 169 ≡ 1 (mod 31)

Fermat’s Little Theorem for more fun!

Fermat’s Little Theorem is:

A corollary is:

This means we can do even more modular exponentiation in our head.

  6^36 mod 31
  = (6^(30) * 6^6) mod 31
  = ((6^30 mod 31) * (6^6 mod 31)) mod 31
  = (1 * (6^6 mod 31)) mod 31
  = (6^2 * 6^2 * 6^2) mod 31
  = ((6^2 mod 31) * (6^2 mod 31) * (6^2 mod 31)) mod 31
  = (5 * 5 * 5) mod 31
  = 125 mod 31
  = 1

Discrete Logaarithm

Remember, in real numbers, when $x^y=z$ we say that $y$ is the logaritm of $z$ (base $x$)? Well in the modular integer world, whenever $b^k \equiv g\;(mod\:N)$, then we say $k$ is the discrete logarithm of $g$ in base $b$ modulo $N$.

Modular Exponentiation is probably One-Way

IMPORTANT We’ve just seen a polynomial time algorithm for computing $z = x^y\:\mathrm{mod}\:N$, but there currently exists no known efficient algorithm for finding $y$ given $x$, $z$, and $N$. Finding the discrete logarithm is in general computationally intractable. So modular exponentiation is a kind of one-way function.

Modular Multiplicative Inverse

The (regular) multiplicative inverse of a non-zero real number $x$ is the number $y$ such that $xy = 1$. We usually write it as $x^{-1}$ or $\frac{1}{x}$.

In the modular world the same idea holds.

Example: The modular multiplicative inverse of 11, mod 25, is 16, because (16 × 11) mod 25 = 1.

But wait.... You don’t always have a modular inverse. There is no number $y$ such that $12y\:\mathrm{mod}\:34 = 1$. There is a theorem that says $x$ has an inverse mod $N$ if and only if $x$ is relatively prime to $N$. ($x$ and $y$ are relatively prime means that gcd($x$, $y$) = 1.)

Code to find the modular inverse is built into many languages, or at least easy to find in a library.

Julia

julia> invmod(234535235234,2345665654331)
146170270779

Scala

scala> BigInt("234535235234").modInverse(BigInt("2345665654331"))
res0: scala.math.BigInt = 146170270779

Clojure

user=> (.modInverse (biginteger 234535235234) (biginteger 2345665654331))
146170270779

To compute the modular inverse, yourself, from scratch (for educational purposes), you can use the Extended Euclidean algorithm. Details are in any good book on number theory, algorithms, or cryptography.

Exercise: Write a modular inverse function in Python, Ruby, and (hehe) C.

Primality Testing

There are lots of algorithms out there to determine if a number is prime. Two main classes of algorithms:

Even the fastest deterministic algorithms are really, really, slow compared to the probabilistic ones. Besides, you can get probabilistic algorithms to have a probability of error less than that of hardware failures or getting hit by lightning and winning the lottery on the same day.

Some Algorithms

Actually we care about a few other attributes of these algorithms:

AlgorithmGeneral?Deterministic?Polynomial?Unconditional?
Trial Division Yes Yes No, not at all ☠ Yes
Miller I think so Maybe Yes No
Miller-Rabin Yes No Yes, $O(n^3)$, or $O(n^{2+\epsilon})$ with FFT Yes
Baillie-PSW Yes No Yes Yes
ECPP Yes Yes Expected, $O(n^{5+\epsilon})$ for most inputs TODO
Fast ECPP Yes Yes Expected, $O(n^{4+\epsilon})$ for most inputs TODO
AKS Yes Yes Yes, $O(n^{12+\epsilon})$ Yes
Lenstra-Pomerance Yes Yes Yes, $O(n^{6+\epsilon})$ Yes

Solovay-Strassen Test

It turns out that, for an odd positive $n$, if $n$ is prime then

a(n-1)/2 ≡ Jacobi(a,n) (mod n)

is true for all $a \in \{1 ... n-1\}$, and if $n$ is composite then that congruence holds true for at most half of the values in $\{1 ... n-1\}$.

So if you pick a random $a$ less than $n$, and the congruence fails, you definitely know $n$ is composite. But if it holds, $n$ has a 50% chance (1 out of 2) of being composite. So pick another value for $a$. If it holds again, you have a 1/4 chance of being composite. If another one passes, your odds of being composite go down to 1 in 8. Pass 30 straight tests and you have only a 1 in a billion chance.

Lehmann Test

This one doesn’t require computing the Jacobi symbol. You simply compute $a^{(p-1)/2}\:\mathrm{mod}\:p$ for $k$ randomly selected $a$’s (all less than $p$). If you ever get a value other than $1$ or $p-1$, then $p$ is definitely composite. Otherwise, if you always get $1$ and $p-1$ and there is at least one $p-1$ then the probability that $p$ is prime is $1-2^{-k}$.

Miller-Rabin Test

Read the Wikipedia article.

Also some cool implementations on Rosetta Code.

Integer Factorization

No one as yet knows any efficient general-purpose algorithm for determining the prime factors of an integer, but a number of special-purpose algorithms (besides the simple-minded "direct search" approach) have been invented.

MathWorld has good articles on Prime Factorization and Prime Factorization Algorithms. Start your self-study tour there.

Julia has a factor function built-in:

julia> factor(227275645766597)
Dict{Int64,Int64} with 3 entries:
  23  => 3
  211 => 1
  97  => 4

julia> factor(2398472839472983423499238211)
Dict{Int128,Int64} with 5 entries:
  7                 => 1
  911063            => 1
  83                => 1
  113               => 1
  40098840940603649 => 1

julia> factor(2398472839472983423499238212398472304972342300923841)
Dict{BigInt,Int64} with 5 entries:
  125573593                             => 1
  4327                                  => 1
  3                                     => 2
  367                                   => 1
  1336413995720810139514854155187173177 => 1

Why care? The fact we can generate primes efficiently but not (yet???) factor integers efficiently gives us one way to implement public-key cryptography.

Exercise: But those Julia answers came back like right away! So they must have been easy to factor. See how long it takes to factor 1786002367958461541890674463979503668058286768574381006801034668188273329.
Exercise: Even if it took a long time to factor the number in the previous exercise, please note that was only a 73-digit number. Never, never, never, try to do real cryptography with such tiny numbers. Find out the sizes of numbers used in real cyptosystems based on the difficulty of factoring large numbers.

Oh yeah, you might want to read about Shor’s Algorithm.

Floating-Point Algorithms

These are covered in a different class (CMSI 284).