1

I was reading the code for the Catalan numbers algorithms in C and I found the phrase I don't quite understand. Here it is (recursively):

typedef unsigned long long ull;

ull catalan2(int n) {
int i;
ull r = !n;

for (i = 0; i < n; i++)
    r += catalan2(i) * catalan2(n - 1 - i);
return r;
}

Can someone please tell ma what the phrase r = !n is responsible for here?

Thank you in advance!

4 Answers4

2

This:

ull r = !n;

Is equivalent to this:

ull r;
if (n == 0)
    r = 1;
else
    r = 0;

A tricky way to do it without the branch:

#include <limits.h>
...
ull r = 1-((unsigned)(n|(-n))>>(sizeof(n)*CHAR_BIT-1));
barak manos
  • 29,648
  • 10
  • 62
  • 114
  • It's quite likely that a good compiler will produce better code for the original formulation than the "branchless" version. Aside from any question about readability. (Tried with gcc: 3 non-branching instructions vs. 5.) – rici Oct 13 '14 at 21:24
  • Although the tricky way appears to have trouble with -INT_MIN, may depend on 2's complement, no padding, yadda, yadda, +1 for adding some fun to the answers. IAC the 1st 2/3rds of the answers is clear and correct. – chux - Reinstate Monica Oct 13 '14 at 21:30
  • @rici: This "branch way" might yield reduced performance in comparison with that bit-wise operations trick. It really depends on the underlying HW architecture as well as the designated compiler at hand. But it will for sure lead to **inconsistent performance**, depending on the value of `n` and branch-prediction heuristics. The running time of the "tricky way" is guaranteed to be identical on every execution. – barak manos Oct 14 '14 at 04:00
  • @chux: Thanks. I cant see how this would yield a problem, as `INT_MIN|(-INT_MIN) == INT_MIN` – barak manos Oct 14 '14 at 04:02
  • @barakmanos: To be clear, the code produced by gcc for intel architecture *has no branches*. It doesn't even use a CMOV; instead, it does a test and then sets the low order bit of a cleared register from the appropriate condition bit. (However, CMOV is not a branch either, and does not incur failed-branch-prediction penalties.) So it will also have consistent execution time, roughly half of your solution. – rici Oct 14 '14 at 04:12
  • @rici: OK, but just to be clear on my side, "it does a test" sounds like branching. – barak manos Oct 14 '14 at 04:38
  • @barakmanos: `test` is an instruction. Specifically, the code is `xorl %eax, %eax; testl %edi, %edi; sete %al`, which (1) clears the result register; (2) sets the ZF condition code from a comparison of the argument register with itself; (3) sets the low-order byte of the result register from the condition code ZF. (`testl` sets other condition codes as well. But no branches are involved; it's pure bit manipulation.) – rici Oct 14 '14 at 04:47
  • @rici: OK, thank you for the knowledgeable information. I've always been pretty sure that whenever `ZF` is used, a branch follows. – barak manos Oct 14 '14 at 04:53
  • `INT_MIN|(-INT_MIN)` _may_ result in `INT_MIN`. That behavior is not defined by C. [Ref](http://stackoverflow.com/a/8917528/2410359) – chux - Reinstate Monica Oct 14 '14 at 14:19
  • Memoizing the naive recursion would obsolete this optimization anyway. – David Eisenstat Oct 14 '14 at 14:27
  • @chux: Can we not assume that `INT_MIN` is all 1s, hence the bit-wise OR will yield `INT_MIN`? – barak manos Oct 14 '14 at 14:43
  • @DavidEisenstat: What **on earth** does "memorizing the naive recursion" mean in the context of this answer? (I'm honestly puzzled). – barak manos Oct 14 '14 at 14:44
  • @barak manos 1) "Can we not assume that INT_MIN is all 1s" - not with 2's complement. With 2's complement, `-1` is all 1's 2) `-INT_MIN` by itself may throw an exception. 3) `INT_MIN`, even with 2's complement may be `0b100...001` leaving `0b100...000` as a trap representation. – chux - Reinstate Monica Oct 14 '14 at 18:26
  • @chux: Ooops, that was dumb question to begin with, sorry. What I meant to ask is "can't we assume that `INT_MIN`'s most significant bit is 1, hence the result of the bit-wise OR will always have its MSB set to 1, hence the result of the shift operation will always have its LSB set to 1"? – barak manos Oct 14 '14 at 18:45
  • @barak manos 1) It is a reasonable assumption, but C11 §6.5 4 "Some 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." 2) bit-wise OR with `(-INT_MIN)` may have throw an exception due to `(-INT_MIN)` before the or-ing occurs. – chux - Reinstate Monica Oct 14 '14 at 18:52
1

x = !y means "evaluate y as a boolean and return the contrary boolean value"

So if y is zero, then it is false and we return a true value (ie, 1). Otherwise, y is true and we return zero.

Jon Kiparsky
  • 7,499
  • 2
  • 23
  • 38
1

ull r = !n; is equivalent to ull r = n == 0 ? 1 : 0;. It's accounting for the empty tree when n == 0; the for loop counts nonempty trees.

David Eisenstat
  • 64,237
  • 7
  • 60
  • 120
0

r != n means that: assign 0 if n is non-zero else assign 1 to r.

haccks
  • 104,019
  • 25
  • 176
  • 264