Algorithms D, P, and S

9 Oct 2021


§ Overview

(3 Jan 2025) Algorithms S and P are special to me, from when I was in MATH-470.

Algorithm subtraction and division algorithms for bigints, i.e. larger than what the processor can handle.

§ Algorithm P

Commonly known as the Fisher-Yates shuffle.

for (auto i = vec.size(); i-- > 0;){
    auto j = random_integer(i); // Random integer from 0 <= j <= i
    std::swap(vec[i], vec[j]);
}

§ Algorithm S

From Knuth's The Art of Computer Programming, Volume 2: Seminumerical Algorithms 3rd edition, pg. 267.

Given nonnegative integers nn place integers (un1u1u0)b(vn1v1v0)b,(u_{n-1} \dots u_1u_0)_b \geq (v_{n-1}\dots v_1v_0)_b, this algorithm gives their difference (wn1w1w0)b.(w_{n - 1} \dots w_1 w_0)_b.

  1. (Initialize) Set j=0,k=0.j = 0, k = 0.
  2. (Subtract Digits) Let wj=(ujvj+k)modb,w_j = (u_j - v_j + k)\mod b, and k=(ujvj+k)/b.k = \lfloor(u_j - v_j + k) / b\rfloor.
    • In other words, k{1,0},k \in \{-1, 0\}, depending on whether a borrow occurs, i.e. ujvj+k<0.u_j - v_j + k \lt 0.
  3. (Loop on jj)
    • Increment j.j.
    • If j<n,j \lt n, goto step 2.
    • Otherwise, terminate. Ensure that k=0,k = 0, if k=1k = -1 then the starting conditions for the algorithm were not met, and the results could be wrong.

§ Algorithm D

From pg. 272.

Given nonnegative integers u=(um+n1u1u0)bu = (u_{m+n-1} \dots u_1u_0)_b and v=(vn1v1v0)b,v = (v_{n-1}\dots v_1v_0)_b, where un10u_{n-1} \neq 0 and n>1,n \gt 1, we can form the radix-bb quotient u/v=(qmqm1q0)b\lfloor u/v \rfloor = (q_mq_{m-1}\dots q_0)_b and the remainder umodv=(rn1r1r0)b.u\mkern-1ex\mod v = (r_{n-1}\dots r_1r_0)_b.

  1. (Normalization)
    • Let d=(b1)/bn1.d = \lfloor (b - 1) / b_{n - 1} \rfloor.
      • Depending on the representation/system, it may be preferable to choose dd to be a power of 2.2.
      • In any case, an acceptable dd would satisfy vn1b/2v_{n - 1} \geq \lfloor b/2 \rfloor.
    • Then, set (um+num+n1u1u0)b(u_{m + n}u_{m + n - 1} \dots u_1 u_0)_b equal to dd times (um+n1u1u0)b.(u_{m + n - 1} \dots u_1 u_0)_b.
      • If d=1,d = 1, set um+n=0.u_{m + n} = 0.
    • Similiarly, set (vn1u1u0)b(v_{n - 1} \dots u_1 u_0)_b equal to dd times (vn1v1v0)b.(v_{n - 1} \dots v_1 v_0)_b.
  2. (Initialize jj)
    • Let j=m.j = m.
    • The loop over jj, i.e. steps 2 to 7, will essentially do a division of (uj+nuj+1uj)b(u_{j + n}\dots u_{j+1}u_j)_b by (vn1v1v0)b(v_{n-1}\dots v_1v_0)_b to get a single digit of the quotient.
  3. (Calculate q^\hat{q})
    • Set q^=(uj+nb+uj+n1)/vn1,\hat{q} = \lfloor (u_{j+n}b + u_{j + n - 1}) / v_{n - 1} \rfloor, and let r^=(uj+nb+uj+n1modvn1\hat{r} = (u_{j + n}b + u_{j + n - 1}\mod v_{n-1} be the remainder.
    • Test if q^=b\hat{q} = b or q^vn2>br^+uj+n2.\hat{q}v_{n-2} \gt b\hat{r} + u_{j + n - 2}.
      • If yes, then decrement q^\hat{q} and add vn1v_{n-1} to r^.\hat{r}. If r^<b,\hat{r} \lt b, repeat.
      • Otherwise, continue.
  4. (Mulsub)
    • Replace (uj+nuj+n1uj)b(u_{j + n}u_{j + n - 1}\dots u_{j})_b with (uj+nuj+n1uj)bq^(vn1v1v2)b.(u_{j + n} u_{j + n - 1} \dots u_j)_b - \hat{q}(v_{n - 1} \dots v_1 v_2)_b.
    • The digits (uj+n,uj+n1,,uj)(u_{j + n}, u_{j + n - 1}, \dots, u_j) should be kept positive.
      • If the result is actually negative, then (uj+nuj+n1uj)b(u_{j + n} u_{j + n - 1} \dots u_j)_b should be left as the true value plus bn+1,b^{n + 1}, namely the bb's complement of the true value, and the borrow to the left should be remembered.
  5. (Test Remainder)
    • Set qj=q^.q_j = \hat{q}.
    • If the result of step 4 was negative, goto 6, otherwise goto 7.
  6. (Add Back)
    • Decrement qjq_j and add (0vn1v1v0)b(0v_{n - 1}\dots v_1 v_0)_b to (uj+nuj+n1uj+1uj)b.(u_{j + n} u_{j + n - 1} \dots u_{j + 1} u_j)_b.
    • Note that a carry will occur to the left of uj+n,u_{j + n}, but it should be ignored because it cancels with the borrow in step 4.
  7. (Loop on jj)
    • Decrement j.j. Goto 3 if j0.j \geq 0.
  8. (Unnormalize)
    • Now (qmq1q0)b(q_m\dots q_1q_0)_b is the quotient.
    • The remainder can be obtained by dividing (un1u1u0)b(u_{n - 1} \dots u_1 u_0)_b by d.d.