3

(edit: the question that my question has been flagged a duplicate of was already linked in my original post, even before the flagging, and I did not consider it sufficient to answer my specific question, why was how to get a certain complexity without making any assumptions about an unknown function.)

I'm trying to solve exercise in CLRS (Cormen Intro to Algorithms, 3ed). The exercise reads:

Give an efficient algorithm to convert a given β-bit (binary) integer to a decimal representation. Argue that if multiplication or division of integers whose length is at most β takes time M(β), then we can convert binary to decimal in time Θ[M(β)lgβ]. (Hint: Use a divide-and-conquer approach, obtaining the top and bottom halves of the result with separate recursions).

This question has been asked here, and here. However, the answers there either make incorrect assumptions, such as M(β)=O(β), or give an answer the completely ignores what the question is asking for. Another answer here even explicitly states the Θ[M(β)lgβ] result, but the explanation is quite handwavey, as if the result were obvious:

You can do base conversion in O(M(n) log(n)) time, however, by picking a power of target-base that's roughly the square root of the to-be-converted number, doing divide-and-remainder by it (which can be done in O(M(n)) time via Newton's method), and recursing on the two halves.

That explanation was not entirely clear to me: without making any assumptions about M(n), such a recursion would result in O(M(n) n) time, not O(M(n) log(n)). (edit: my question has been marked a duplicate of that thread, but I had already included the link to that thread in my original post, before it was marked as duplicate, as I feel that the answer to that thread did not sufficiently address the issue I was confused about).

As I understand, the question is saying that each multiplication, quotient, and remainder operation takes a constant time M, which dominates every other kind of operation, such as addition. Hence, the dominant term M(β)lgβ comes simply from performing only lgβ multiplications and divisions.

However, I am not able to come up with anything that requires only lgβ divisions. For example, if we take the hint from the question, we can come up with the following divide and conquer algorithm, in pseudocode.

decimal(x, start, end, n):
    digits = end - start + 1 // assume power of 2
    if digits == 1:
        x[start] = n // 1-based index
    else:
        t = 10^(digits/2) // assume negligible time
        quotient, remainder = n / t // takes M time
        decimal(x, start, start+digits/2-1, quotient)
        decimal(x, start+digits/2, end, remainder)

Calling decimal(x,1,d,n) on d-digit number n, with d a power of 2 for simplicity, places the decimal representation of in in the size-d array x. Assuming that the line quotient, remainder = n / t, takes time M and dominates everything else in the runtime, the runtime recursion is T(β) = 2T(β/2) + M, which has the solution T(β) = Θ(βM), not Θ(Mlgβ).

Is my understanding of the question correct? If so, how is it possible to get the decimal representation using only Θ(lgβ) multiplications and/or divisions?

The following page by a very well-known StackOverflow user discusses this problem. In particular:

Binary -> Radix: The binary -> radix conversion is the same but in reverse. Start with an N-digit number X in base 16. You wish to convert this into an M-digit number R in base b. Compute: high = floor( X / bM/2 ). Compute: low = X - bM/2 * high. Recursively convert low and high. Append the results. The final result R will be the original number converted into base b.

However, I still don't see how this is O(lg B) multiplications; if you are recursing on both halves, you are by definition visiting every node in the recursion tree, therefore there are O(B) multiplications!

page 55 of 239 of Modern Computer Arithmetic by Brent, which can be seen here, also discusses "subquadratic algorithms", and mentions the M(β)lgβ divide and conquer algorithm. However, I am still clueless as to where the lg β comes from. Again, if you divide and conquer, and recurse on both halves, the runtime is at least linear, not logarithmic! On page 55 of 239 of that book, the following algorithm is provided (slightly paraphrased):

Algorithm 1.26 FastIntegerOutput
Input: A = (sum 0 to n-1) a_i 2^i
Output: a string S of characters, representing A in based 10
    if A < 10 then
        return char(A)
    else
        find k such that 10^(2k-2) <= A < 10^(2k)
        (Q, R) = DivRem(A, 10^k)
        r = FastIntegerOutput(R)
        return concatenate(FastIntegerOutput(Q), zeros(k-len(r)), r)

Brent claims:

If the input A has n words, Algorithm FastIntegerOutput has complexity O(M(n) log n)

but again, I don't see how this is possible, as the line (Q, R) = DivRem(A, B^k) is called O(n) times, not O(lg n)?

xdavidliu
  • 2,411
  • 14
  • 33
  • 2
    The assumption of a constant time M for a multiplication or division is wrong. It is M(beta) so it depends on the length of the numbers. I guess you need to know (or assume) something about this dependency to derive the bound. – Henry Dec 23 '17 at 05:48
  • @Henry Not really; the question is entirely contained in the block quote in the beginning of my question. By constant, I mean, and the question seems to suggest, "large and constant", in that the argument B is always understood to be the top-level B, so that in the first level of recursion, all multiplications still take M(B), not M(B/2). Hence, for example, assuming the full integer we want to convert is B-bits, the runtime recursion is T(B) = something + M(B), T(B/2) = something + M(B), *not* M(B/2) – xdavidliu Dec 23 '17 at 05:50
  • What do you mean by "all multiplications"? each one or all together. – Henry Dec 23 '17 at 05:54
  • each multiplication / division takes time M(B), even if you are multiplying two numbers that are each only B/2 bits or B/4 bits. The problem explicitly says "the time needed to multiply / divide numbers that are *at most* B bits is M(B)", and does not say *anything* about the form of M(B). In fact, this assumption of constant-time multiplication actually makes complete sense, since integer multiplication and division takes constant time, e.g. for 32-bit ints, multiplying 2 x 3 takes the same amount of time as multiplying 914331 x 149131 – xdavidliu Dec 23 '17 at 05:56
  • At the bottom level, where you split the numbers below 100 into two digits each. you will need O(beta) operations. If each one of them takes M the effort is O(M * beta). So that does not work out. My gut feeling is that you can achieve the formula if M(beta) grows linearly or more with beta. – Henry Dec 23 '17 at 06:00
  • certainly if we assume something about the form of M(B), then we can get anything we want to happen. However, I think the problem is extremely clear that is not the way to go here. – xdavidliu Dec 23 '17 at 06:03
  • 1
    constant time multiplication is only true if the numbers fit into one word. Doing a long multiplication the naive way (better methods do exist) the effort is quadratic in the number of bits. Also keep in mind, the result of a multiplication of two beta bit numbers has roughly 2*beta bits so alone for output of the result we need at least linear effort. – Henry Dec 23 '17 at 06:05
  • i think having all numbers fitting into one word, with B being the length of a word or constant multiple of it, is exactly what the problem is saying. Also, if we assume M dominates other operations, like populating arrays, we need not consider the fact that outputting the result requires linear time, as long as it doesn't require a linear number of multiplications / divisions. – xdavidliu Dec 23 '17 at 06:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/161843/discussion-between-xdavidliu-and-henry). – xdavidliu Dec 23 '17 at 06:11
  • Possible duplicate of [Computational complexity of base conversion](https://stackoverflow.com/questions/28418332/computational-complexity-of-base-conversion) – tmyklebu Dec 23 '17 at 15:01
  • 1
    @Henry, your statement "My gut feeling is that you can achieve the formula if M(beta) grows linearly or more with beta" was completely correct; as was all your other comments (although I feel CLRS inaccurately used Θ in the formula, when O would have been the one that was true generally) I cleared up some of my misunderstandings, and wrote everything in the answer – xdavidliu Dec 23 '17 at 15:53

1 Answers1

3

First, to get it out of the way: what the title of my question is asking for, to make the conversion with a logarithmic number of divisions and multiplications, is not possible as far as I know; and that was only an assumption I made based on a misunderstanding of a reading of the question.

I corresponded with the authors of the textbook Modern Computer Arithmetic, and they said that the algorithm indeed calls division Θ(β) times, not Θ( lg β), and at deeper recursive levels, M does in fact act on smaller-sized arguments, not on the constant, top-level β as I had incorrectly assumed in my question. In particular, the tree's top level call has M(β/2), the next level has 2M(β/4), then 4M(β/8), etc. for a total of lg β levels. As long as M(β) = Ω(β), the tree summation is O(M(β) lg β), though in general not Ω(M(β) lgβ), and hence not Θ(M(β) lgβ). For example, for polynomial M(β) = Θ(β^α), the tree summation is Θ(β lg β) = Θ(M(β) lg β) for α = 1, and Θ(β^α) = Θ(M(β)) for α > 1.

Hence, if we only assume M(β) = Ω(β), then the runtime would more accurately be described as O(M(β) lg β), not Θ(M(β) lg β) as in the CLRS exercise. (In my correspondance with one of the authors of Modern Computer Arithmetic, they suggested that CLRS meant that "give an efficient algorithm" meant to assume M(β) is linear or quasilinear, but CLRS is usually pretty explicit about the assumptions we are supposed to make, and "give an efficient algorithm" is just a somewhat generic phrase that they use pretty often in the text to exercises and problems, so I feel like this might be a minor typo on the part of CLRS.

Update: I submitted this minor correction to the CLRS errata page, and it is up now:

Page 933, Exercise 31.1-13. Add the restriction that M(β) = Ω(β), and change the desired time bound from Θ(M(β) lg β) to O(M(β) lg β). Reported by David Liu. Posted 25 December 2017. Severity level: 3 To be corrected in the eighth printing of the third edition.

xdavidliu
  • 2,411
  • 14
  • 33
  • CLRS isn't being sloppy in that way. You are concerned with asymptotic complexity. It is straightforward to prove that M(n) is in Omega(n) because of the simple fact that every bit of the input needs to be examined. CLRS "proves" that M(n) is O(n log n) by using the FFT. Use that. (But note that CLRS is being very sloppy about how much precision is required in the FFT method and therefore how long individual operations take. See Schoenhage, Strassen '71 or Bernstein's "multidigit multiplication for mathematicians" for more a more careful treatment.) – tmyklebu Dec 23 '17 at 17:38
  • the sloppiness I'm concerned with isn't their leaving out M(n) = Omega(n) from the statement of the problem, but rather the claim that the runtime of the divide and conquer algorithm is Theta(M(n) lg n). That's simply incorrect, since the runtime is *not* in general Omega(M(n) lg n). To reach that conclusion, one would also need to assume that M(n) = O(n) – xdavidliu Dec 23 '17 at 17:52
  • You do not need M(n) = O(n); you need M(n) = O(n log^a(n)), which is true. (And maybe you need some regularity assumption on how M behaves as it's growing; something like C > M(n^alpha) / M(n)^alpha > c > 0 for alpha in some range.) – tmyklebu Dec 23 '17 at 20:19
  • it's probably more accurate to say it *can* be true, since you could always choose to use a simple O(n^2) algorithm for division rather than the faster O(n lg n), but then again, the wording of the question says "give an efficient algorithm...". Usually CLRS is very good about stating all the assumptions, which is why I pointed this out. – xdavidliu Dec 23 '17 at 21:37
  • There is an O(n log(n) log log(n)) algorithm for multiplication, so M(n) is O(n log(n) log log(n)). You are free to multiply numbers however you like, but your choices affect only how long it takes you to multiply two numbers, not M(n) or its asymptotic growth. – tmyklebu Dec 23 '17 at 22:11