Pages

Friday, February 19, 2016

The smallest 2n-multiple ≥ B

When dealing with quantities that are powers of 2, the binary (base-2) arithmetic as well as the typical binary representation of integer values can allow interesting alternatives to the usual base-10 arithmetic we are commonly used to.
 
The obvious prerequisites are binary arithmetic and logical operations.
The relevant formula is:
[ B +  ( A - 1 ) ] & [ ~( A - 1 ) ], where A = 2n for some n ≥ 0.
The above formula will compute the smallest multiple of A greater than or equal to B.
B is a positive integer value which, if large enough, could represent a pointer.
In this process it's imperative that A be a (positive) power of 2.

One particular application is adjusting C++ pointers to a certain alignment modulus.
In other words, this technique is very important to C++ memory management.
B would be the value of a pointer to be aligned to an A-multiple boundary.
If an alignment modulus is 4, then A = 4 and thus n = 2.
You know: alignment is vital.

Of course I shall prove the formula with a minimal (but enough) formalism.
But first let's get to know better how it works.

The trivial case is when n = 0 ( A = 1 ), which just yields B, no big deal.
In this case we are just aligning to a single byte boundary as A = 1.
So no adjustment to B is required and hence B itself is fine.

For a 4-bytes (32-bits) boundary alignment we would use n = 2.
That means we are interested in multiples of 4, that is, A = 4 and hence n = 2.

To get the whole idea of the formula, let's just work with n = 3, that is, A = 8.
Let's assume, for the sake of simplicity, a single-byte magnitude for the B value.
In a memory alignment scenario, B would be the value of a multi-byte pointer.
As a power of 2, the binary representation of A is a 1 followed by n zeros.
As a direct consequence ( A - 1 ) is always a sequence of n ones.
Hence ~( A - 1 ) is a bit-mask to clear out the first n least significant bits.

Look:
If n = 3,
then A = 8 = 10002 and ( A - 1 ) = 1112 and ~( A - 1 ) = 111110002.
And so what?

Well, this surprisingly means that if X is any positive integer number, then X & ~( A - 1 ) is equivalent to clearing out the lowest n least significant bits of X, that is, X >>= n (a right n-shift) followed by X <<= n (a left n-shift), which in binary positive integer arithmetic precisely and respectively means dividing and subsequently multiplying by A. In this way we find the integer (truncated) quotient of X by A and then multiply that found integer quotient by A. This couple of operations give us the greatest multiple of A that doesn't exceed X, or yet from an alternative perspective we are "rounding (or aligning) X down" to the immediately preceding A-multiple in the standard (Natural Numbers) order.

By now it should be starting to get clear why in the original formula we added "something", that is, ( A - 1 ), to B before lastly applying "&  ~( A - 1 )"  just described in the previous paragraph. That is so because, in fact, we are looking for the A-multiple immediately succeeding (not preceding) B. In this process we take advantage of the preceding A-multiple strategy upon B +  ( A - 1 ) and not just B as we shall see below.

But why adding ( A - 1 ), instead of just A, to B? We could have added A to B and perhaps this is the most natural step as we simply imagine a ruler of A-multiples markings with B lying in between two consecutive A-multiples markings.

The problem is that if B itself is already a multiple of A, then it isn't necessary (and it actually is a waste of 1 A-multiple) to add anything to B in order to obtain the least multiple of A "encompassing or succeeding" (that is, greater than) B as that's already B itself. You should note that if B is itself a multiple of A, the operations of adding ( A - 1 ) and "rounding down" still yield B and otherwise (if B is not a multiple of A) the operations yield the A-multiple immediately succeeding B.

The naive approach would be to introduce a conditional flow control statement (such as an if statement) to selectively treat each case (when B is a multiple of A and when it's not). That's perfectly correct but introduces more instructions and thus more overhead in terms of code size and slightly increased execution time which may or may not be significant depending on the scenario. So the naive approach may not be ideal and I say "may" because nowadays optimizing compilers are terrific :-)

So, if simply adding A to B may be too much if B is already a multiple of A, once more, the second tricky part (the first tricky part was the "rounding down" of a value X to an immediate preceding A-multiple) of the original formula is to realize that adding ( A - 1 ) treat both cases (when B is a multiple of A and when it's not) minimizing all overheads.

That is:
 
  • If B is a multiple of A, then [ B +  ( A - 1 ) ] doesn't "reach" the next multiple of A, thus avoiding the aforementioned "waste" of 1 A "parcel" "beyond" B. The result is B itself.

    Proof:
    If B = k·A, k∈ℕ, then
    B + ( A - 1 ) = k·A + ( A - 1 ) = ( k + 1 )·A - 1 < ( k + 1 )·A.
    That is, if B = k·A, then B + ( A - 1 ) < ( k + 1 )·A.

    "Rounding down" B + ( A - 1 ) yields an A-multiple less than ( k + 1 )·A.
    As k·A = B < B + ( A - 1 ) < ( k + 1 )·A the answer is k·A = B
     
  • If B is not a multiple of A note that [ B +  ( A - 1 ) ] is at least the next multiple of A greater than B and not reaching the subsequent to "this next multiple of A greater than B". This is because we are dealing with Natural Numbers (positive integers). In other words ( A - 1 ) is effectively the minimal (enough) amount to add to B in order to "get to" the least (and no other) multiple of A greater than B.
     
    Proof:
    Although B is not a multiple of A it's true that k·A < B < ( k + 1 )·A, k∈ℕ.
    That's the Archimedean Property restricted to the Natural Numbers.
    Also according to the Natural Numbers, if k·A < B then ( k·A ) + 1 ≤ B.
    Then, it's also perfectly true that ( k·A ) + 1 ≤ B < ( k + 1 )·A.
    By adding ( A - 1 ) to each member of the preceding line:
    ( k + 1 )·A ≤ [ B + ( A - 1 ) ] < ( k + 2 )·A - 1.

    "Rounding down" B + ( A - 1 ) yields an A-multiple less than B + ( A - 1 ).
    That answer can only be ( k + 1 )·A and as B < ( k + 1 )·A
    we have found the A-multiple immediately succeeding B

To wrap up this lengthy analysis of the formula, let me recall that it computes a multiple of A, in fact, the smallest A-multiple greater than or equal to B. Thus, if one had to find out exactly what multiplier was involved in this process, it suffices to apply >>= n (a right n-shift) to the result (that is, divide the result by A = 2n).