2

Perhaps there's an ingenious bitwise operation I'm forgetting, but is there a smarter way of expressing the following?

int r = foo(); // returns an int
r -= r > 0; // same as `if(r > 0) r--;`

I want to combine the lines into one expression.

ayane_m
  • 962
  • 2
  • 10
  • 26
  • 1
    Define “smarter”. `r = max(r - 1, 0);` is the usual phrasing of a “clamping” operation like this. – Jon Purdy Mar 17 '16 at 06:27
  • @JonPurdy I mean "smarter" as in eliminating the conditional. Clamping the value isn't my goal; `r` can be a negative value, in which case that won't work. – ayane_m Mar 17 '16 at 06:31
  • 3
    Ah, right. Then why not simply write what you mean? `if (r > 0) --r;` – Jon Purdy Mar 17 '16 at 06:36
  • @JonPurdy Because I really want to avoid those yucky, yucky conditionals. Using them for a silly operation like this reeks of newbie status! ^.^ – ayane_m Mar 17 '16 at 06:41
  • 1
    How is this [tag:language-agnostic]? Your sample looks like C, and the question as such doesn't make much sense outside the realm of declarative low-level languages. (Based on the question title, I expected to be able to contribute `awk '$1 > 0 { --$1 } 1'` which in the world of Awk scripts is definitely a "one-liner", but this is clearly not what you are actually asking.) – tripleee Mar 17 '16 at 06:56
  • 5
    Trying to find a "clever" (i.e. more or less esoteric) way to obfuscate what should be expressed clearly does not sound like a worthwhile goal, anyway. – tripleee Mar 17 '16 at 06:59
  • Your way seems pretty smart. – Chiel Mar 17 '16 at 16:50
  • @Chiel: But smart in a negative sense. It only works because booleans can be mixed up with integers in C and C++ . – Frank Puffer Mar 17 '16 at 16:55
  • @FrankPuffer. That is true, and `r -= static_cast(r > 0)` doesn't look too sexy either... – Chiel Mar 17 '16 at 17:03

4 Answers4

4

The most readable way is the smartest way:

if (r > 0)
    --r;

It's readable, and it is most likely to produce the fastest code - because lots of developers don't make vain attempts to look smart, and people creating optimising compilers detect common patterns and optimise them.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • 1
    Unfortunately, it's not clear that your "fastest code" argument plays out in practice. I tried both your code and the OP's `r -= (r > 0)` with `gcc -O3` on x86-64. Yours generates a conditional branch, while with the OP's code, we get a conditional set and no branch. Testing which is "actually" faster is hard without context, but all other things equal, one expects branches to be slower. – Nate Eldredge Mar 17 '16 at 17:16
  • if this is in the inner loops of graphics rendering code then yes reducing branches is important. Otherwise write clear correct code – pm100 Mar 17 '16 at 18:40
  • @pm100: Is it really that important? As far as I know, branch prediction is a very basic feature of modern processors. – Frank Puffer Mar 17 '16 at 18:56
  • read this - scroll down to 'so what can be done' you will see that replacing branches by conditionals or bitwise ops makes a big difference http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array – pm100 Mar 17 '16 at 19:50
1

Personally I would prefer if (r > 0) r--; for readability, but if you really want to avoid conditionals:

r = r > 0 ? r-1 : r;

which is still a conditional assignment, alternatively either:

r > 0 && r--;

or:

r <= 0 || r--;

This works because, if the left side already determines the result, the right side will not be executed.

By the way, don't expect any performance differences. Most compilers will generate more or less equally performant code from all of these options.

Frank Puffer
  • 8,135
  • 2
  • 20
  • 45
  • 2
    When `r > 0`, wouldn't `r = r--;` be undefined? `r = r > 0 ? r-1 : r;` would be safer. – Remy Lebeau Mar 17 '16 at 16:50
  • "Most compilers will generate more or less the same code from all of these options." Did you try it? For me, `gcc -O3` on x86-64 generates significantly different code for your first example (a conditional set and subtract) than for your others (conditional jump). Same for `clang`. – Nate Eldredge Mar 17 '16 at 17:27
  • @NateEldredge: No, I did not try it. What I actually meant is that this is definitely not the kind of code where the programmer should try to optimize. Have slightly changed my wording. – Frank Puffer Mar 17 '16 at 17:36
  • Is `r = r > 0 ? r-1 : r;` different from `r -= r > 0 ? 1 : 0;`? – ayane_m Mar 18 '16 at 04:47
  • @yuki96: The effect is the same, but for some - probably irrational - reason I don't like to use `-=`or `+=` together with `? :`. – Frank Puffer Mar 18 '16 at 07:32
0

If the test was r >= 0, then one possibility would be to isolate the sign bit, then to subtract (1-signBit).

In C, something like this (but I guess this can be transposed in many languages)

int r = foo();
return r - (int)((~ ((unsigned) r)) >> (sizeof(r)*8-1));

Of course you now depend on internal representation of int and have greatly obfuscated the code...

For r > 0, it's more involved... Maybe something like

unsigned int r = foo();
return (int) (r - ((~(r | (r-1))) >> (sizeof(r)*8-1)));

I did not even tried those snippets, and the answer is for fun, but if you take it seriously, you'll have to provide a great deal of explanations for justifying such hack, including tests, generated code analysis and benchmarks.

If each and every minor operation like this is going thru such a process, it's gonna be a pain to write code, and even more for whoever will have to maintain it (read/understand/extend...). I hope it's not premature optimization, and in this case maybe the right decision would be to write a small piece of assembler.

aka.nice
  • 9,100
  • 1
  • 28
  • 40
  • Yikes, that looks messy. I was hoping something quick and dirty like `r = ~-r;` would exist, but I guess not. – ayane_m Mar 18 '16 at 02:15
  • Ah, looking into my answer, I might missed the `unsigned` cast. @yuki96 you might be right avoiding conditional JUMP, but for clearness of the code you might be ok with conditional MOVE. `cmov` (or its analog) is very cheap and probably best option around. If Nate is right, `r = r > 0 ? r-1 : r;` might be the best option – Severin Pappadeux Mar 18 '16 at 03:04
-1

Well, say, for 32bit integers with 2-complement, something like this might works (untested!)

const int mask = 1 << 31;

int r = foo();

r -= ((r ^ mask) >> 31);

basically, you check if leftmost bit is zero and convert it to 1 if it is, otherwise it will still be zero, then shift it so you have 1 for positive and 0 for negative, and substract

Severin Pappadeux
  • 18,636
  • 3
  • 38
  • 64