0

I came across a C snippet that performs a nifty modular arithmetic operation using bitwise logic:

int a,b,c; 
c = (a + b - 1) & (- b) +b; 

The value of c is the smallest multiple of b greater than a+b (edited in response to John Bollinger's answer). I am trying to explain how this works to myself (I have a tenuous understanding of how modular arithmetic and the & operation could be related) but am falling short on insight. On the other hand it looks like I can express it as

c = (a+b) - ((a+b)%b) + (((a+b)%b)?b:0)

This expression is easy to understand. Moreover the appearance of the modular and ? operations suggests that the individual parts can be expressed as bitwise logic and somehow reduced to the expression at the top. But how? I leave it as an exercise if anyone wants to give it a go (this is NOT homework). Implementation need not be in C, and if there is an online ref that explains this you are welcome to provide it but will not be a complete answer. I'd like to see the transition from the bottom to the top expression in clear steps...

Comments This link suggests that this may apply when b is a power of 2. This other link explains that bitwise & does not distribute over addition.

Assume in the expression ...&(-b), (-b) can be replaced with (nums(int)-b), where nums(int) is the total number of possible ints in the representation.

Feel free to specify your favorite compiler/C version.

Sample code:

int a,b,c;
int alow, ahigh; 

b = 16;
alow = 8; 
ahigh = 20; 
printf("a+b      c    lcm(a+b,b) + ?b  \n");    
for (a=alow;a<ahigh;a++) {
    c = ((a+b-1) & (-b)) +b;
    printf("%5d   %5d    %5d   \n",a+b, c, (a+b) - ((a+b)%b) + (((a+b)%b)?b:0) );
}

Sample output:

  a+b      c    lcm(a+b,b) + ?b
   24      32       32
   25      32       32
   26      32       32
   27      32       32
   28      32       32
   29      32       32
   30      32       32
   31      32       32
   32      32       32
   33      48       48
   34      48       48
   35      48       48
Community
  • 1
  • 1
Buck Thorn
  • 5,024
  • 2
  • 17
  • 27
  • 1
    You say *"c is ... greater than a+b"* but on the second expression, *c=(a+b)-x* where x is positive. (I assume `a>=0`, `b>0`) – axiac Apr 13 '15 at 17:19
  • 1
    @axiac Yes but you add `(((a+b)%b)?b:0)`. The first part is equivalent to removing the remainder of `(a+b)/b` from `a+b`, the second part adds back `b` if the remainder is not zero... – Buck Thorn Apr 13 '15 at 17:23
  • 1
    Oops, I mismatched the parenthesis. Now I noticed it's **-** ((a+b)%b **)** – axiac Apr 13 '15 at 17:25
  • 1
    Experimentation shows that the assertion "The value of c is the next largest number greater than a+b that is divisible by b" does not hold for `a = 5`, `b = 3` (the result is 5, which is neither greater than 8 nor divisible by 5. – John Bollinger Apr 13 '15 at 17:26
  • 2
    Experimentation shows that the assertion also does not hold for `a = 5`, `b = 2`, even though `b` is a power of 2 in that case (the result is 6, which is not greater than 7). – John Bollinger Apr 13 '15 at 17:28
  • I agree with @JohnBollinger that the code in the first snippet does not work as advertised. The second snippet seems to be correct. – user3386109 Apr 13 '15 at 17:36
  • @JohnBollinger yep, missed a `+b` in the top expression - thanks – Buck Thorn Apr 13 '15 at 17:38
  • @user3386109 yep missed a `+ b` – Buck Thorn Apr 13 '15 at 17:41
  • Still not right, you should make an effort to get it right before posting. – user3386109 Apr 13 '15 at 17:46
  • @user3386109 What do you mean "still not right"? Read the fine print: "This link suggests that this may apply when b is a power of 2." – Buck Thorn Apr 13 '15 at 17:47
  • Yes, so try 5 and 2, what do you get? – user3386109 Apr 13 '15 at 17:49
  • @user3386109 which is a and which is b? Maybe you should be more clear? – Buck Thorn Apr 13 '15 at 17:54
  • Ok, given `a=5` and `b=2`, what is the result of your first expression? – user3386109 Apr 13 '15 at 18:04
  • I got a piece of code, I don't know exactly how it works, or what compilers it is limited to run on (what, am I supposed to list them all??), or even what range of inputs the code accepts. Spent a few hours searching for an answer and didn't. Thought I'd share it with the world. Chill people... – Buck Thorn Apr 13 '15 at 18:06
  • The source of the code is 20+ years... older probably than some people reading this... – Buck Thorn Apr 13 '15 at 18:11
  • You're asking why the first snippet is equivalent to the second snippet, but spending two minutes to compile and run the code proves that the first snippet doesn't work. How can you expect to get an answer, and how can you claim that (see below) *"This is working code. It works perfectly."* when you clearly have not run the code. – user3386109 Apr 13 '15 at 18:17
  • Do note that `(a+b)%b == a%b`. The ternary subexpression in your second expression corresponds to the two cases in my answer. – John Bollinger Apr 13 '15 at 19:26

2 Answers2

4

On machines that use two's complement representation for negative numbers, if b is a power of 2 then in the representation of -b, the least-significant log2(b) bits have value zero, and all other bits have value one. Interpreted as an unsigned value, the least significant binary digit in that bit pattern has place value b. (Example: the int8_t with value -4 has bit pattern 11111100 in such a represenation.) This is the most common style of integer representation in use today, and where it matters, the rest of this answer will assume that mode of negative integer representation.

A value such as that can serve as a bitmask to mask off the binary digits having place value less than b from a non-negative integer. Given that we assumed a binary representation and b a power of 2, masking off the bits having lower place value necessarily results in a value divisible by b.

Now suppose a is non-negative and b is a power of 2. The value of a can be expressed as a non-negative multiple of b plus a remainder:

a == n * b + a % b

Now consider the expression (a + b - 1), and suppose that computing its value does not result in integer overflow. There are two cases:

Case 1: a % b == 0

In this case, a == n * b, so

(a + b - 1) == n * b + b - 1

. If we mask off the bits having place value less than b, we get

(a + b - 1) & (-b) == n * b == a

This is certainly the least multiple of b greater than or equal to a.


Case 2: a % b != 0

In this case,

(a + b - 1) == (n + 1) * b + a % b - 1

. If we mask off the bits having place value less than b, we get

(a + b - 1) & (-b) == (n + 1) * b

Since a is strictly between n * b and (n + 1) * b, this is again the least multiple of b greater than or equal to a.


Thus, your original assertion that

The value of [(a + b - 1) & (-b)] is the next largest number greater than a+b that is divisible by b

is an inaccurate characterization for the numeric representation I have supposed (including when b is not a power of 2, though I have not demonstrated that). Instead, under the assumptions described above, the expression computes the least multiple of b that is greater than or equal to a.

Your revised assertion follows directly from the above, however, by adding b to both sides of the expression. If (a + b - 1) & (-b) is the least multiple of b that is at least as large as a, then it follows that (a + b - 1) & (-b) + b is the least multiple of b that is at least as large as a + b.

Equivalent expressions that do not depend on numeric representation (but are still sensitive to overflow) would be

((a + b - 1) / b) * b + b

((a + b) / b) * b + b

((a / b) + 1) * b + ((a % b) ? b : 0)

The last of those maps rather directly onto the two cases presented above, and with a bit of care it can be rearranged to the second expression you present in your question.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Excellent, thank you. I realized it is applying a bit mask but you took the effort to show why these are equivalent which was not clear to me. And you are of course right, my statement was incorrect and I should not have stated that given I had run examples that show it's false, I'll have to edit that out. Whoops – Buck Thorn Apr 13 '15 at 19:50
1

I wouldn't trust any results you get from that. Per the C Standard, section 6.5:

Some operators (the unary operator ~,and the binary operators <<, >>, &, ^, and |, collectively described as bitwise operators) are required to have operands that have integer type. These operators yield values that depend on the internal representations of integers, and have implementation-defined and undefined aspects for signed types.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • 1
    Ok, I didn't narrow down the specs enough: assume uint – Buck Thorn Apr 13 '15 at 17:28
  • This is working code. It works perfectly. The question is about how to get to the algorithm. Perhaps I should try the mathematics site but this is a question regarding how to find insight into an algorithm ie how was a final expression obtained. Perhaps you could do this? – Buck Thorn Apr 13 '15 at 17:42
  • 1
    My question clearly specifies: "Implementation need not be in C", use pseudo-code if you wish. It is not about finding the boundaries of the computer language, it is about developing insight into the algorithm. ' – Buck Thorn Apr 13 '15 at 17:44
  • 1
    This is not an acceptable answer, it in no way addresses the posted question. – Buck Thorn Apr 13 '15 at 17:45
  • 2
    @TryHard The code works on your hardware with your code from your compiler. It's not guaranteed to work on anything else. Negating a uint doesn't make much sense, and it could be interpreted different ways. – Philip Apr 13 '15 at 17:47
  • @Philip Granted, but it runs with `-ansi` flag on gcc on most Linux boxes. I added an edit to explain what the expression means. – Buck Thorn Apr 13 '15 at 17:59
  • 1
    @TryHard: The C code snippet you posted relies, per the C standard itself. on **undefined behavior**. "But it runs..." doesn't make it valid. – Andrew Henle Apr 13 '15 at 18:04
  • @AndrewHenle It works on gcc, that makes it valid. This is not a question regarding mathematical logic, it is about algorithmic insight, which your answer seriously lacks. I've answered hopefully most of the concerns now. – Buck Thorn Apr 13 '15 at 18:09
  • It says *undefined aspects for signed types* .... you have not shown which aspect are undefined – Shafik Yaghmour Apr 13 '15 at 19:03