4

In C# or C++ how can I implement a branch-free sort of three (integer) numbers?

Is this possible?

Servy
  • 202,030
  • 26
  • 332
  • 449
Csharp Csh
  • 489
  • 1
  • 5
  • 9
  • 7
    Huh? Are these random sentences? – Christian Rau Nov 11 '11 at 18:12
  • 1
    I don't understand your question. Please show us your best guess. What do you mean sorting without a condition? – Mooing Duck Nov 11 '11 at 18:12
  • I'm not trying to be mean, but your sentences are so ill-formed as to be barely understandable. Try cleaning up your grammar and being somewhat more explicit about what it is you're trying to achieve and you will be more likely to get an answer. As @MooingDuck said, examples are very helpful. – N_A Nov 11 '11 at 18:14
  • 2
    Explain what you mean by "without condition". – Igby Largeman Nov 11 '11 at 18:18
  • 8
    I'm pretty sure the question is, "How to sort 3 integer values without using conditional operators". A more practical version would be "how to write a branch-free sort for 3 values". It certainly does suck around here if you English isn't that great, but I guess the same is true of most international software development. – Steve Jessop Nov 11 '11 at 18:21
  • @SteveJessop - I agree with your assessment of the question. I've edited it accordingly and voted to reopen. – Flexo Nov 11 '11 at 18:30
  • Related post - [Simpler way of sorting three numbers](https://stackoverflow.com/q/4367745/465053) – RBT Jun 27 '18 at 00:07

3 Answers3

11

No conditionals. Only a cast to uint. Perfect solution.

int abs (int a) 
{
    int b = a;
    b = (b >> (sizeof(int)*CHAR_BIT-1) & 1);
    return 2 * b * (a) + a; 
}
int max (int a, int b) { return (a + b + abs(a - b)) / 2; }
int min (int a, int b) { return (a + b - abs(a - b)) / 2; }


void sort (int & a, int & b, int & c)
{       
   int maxnum = max(max(a,b), c);
   int minnum = min(min(a,b), c);
   int middlenum = a + b + c - maxnum - minnum;
   a = maxnum;
   b = middlenum;
   c = minnum;
}
Lee Louviere
  • 5,162
  • 30
  • 54
  • 1
    Doesn't work, because (among other more easily fixable errors) `abs` doesn't return the absolute value of `a`. – Steve Jessop Nov 13 '11 at 18:39
  • corrected all errors. I don't like relying on knowing the size of an int, but without knowing that, I'd need a while loop, and that's a conditional. Since it does 2 * b first, it's safe. However, if you wanted to make sure, you can add parenthesis around 2 * b – Lee Louviere Nov 14 '11 at 18:32
  • @Xaade: why not just ask the compiler what the size of an int is :) – R. Martinho Fernandes Nov 14 '11 at 18:42
  • The usual existential doubt applies about the result of `>>` for a negative value. Using C# would probably resolve the issue, it's allowed by the question ;-) – Steve Jessop Nov 14 '11 at 18:58
  • 1
    `b = (b >> (sizeof(int)*CHAR_BIT-1) & 1);` has implementation behavior for negative numbers. `2 * b * (a) + a;` should be `2 * b * (-a) + a;` and would have undefined behavior in case of arithmetic overflow. `return (a + b + abs(a - b)) / 2;` potential arithmetic overflow, same for `int middlenum = a + b + c - maxnum - minnum;`... not really a perfect solution I'm afraid. – chqrlie Aug 27 '18 at 06:52
7

You can do this in C++ with:

#include <iostream>

void sort(int *in) {
  const int sum = in[0]+in[1];
  const int diff = abs(in[1]-in[0]);
  in[0] = (sum + diff) / 2;
  in[1] = (sum - diff) / 2;
}

int main() {
  int a[] = {3,4,1};
  sort(a);
  sort(a+1);
  sort(a);
  std::cout << a[0] << "," << a[1] << "," << a[2] << std::endl;

  int b[] = {1,2,3};
  sort(b);
  sort(b+1);
  sort(b);
  std::cout << b[0] << "," << b[1] << "," << b[2] << std::endl;
}

The trick is in expressing the min/max elements as arithmetic operations, not branching and then calling sort on pairs enough times to "bubble sort" them.


I've made a totally generic version, using template meta-programming to call sort the right number of times. It all gets inlined exactly as you'd hope with gcc 4.7.0 on my x86 box (although call is unconditional on x86 anyway). I've also implemented an abs function that avoids branches on x86 (it makes a few assumptions about integers that make it less portable, it's based on gcc's __builtin_abs implementation for x86 though):

#include <iostream>
#include <limits.h>

void myabs(int& in) {
  const int tmp = in >> ((sizeof(int) * CHAR_BIT) - 1);
  in ^= tmp;
  in = tmp - in;
}

template <int N, int I=1, bool C=false>
struct sorter {
  static void sort(int *in) {
    const int sum = in[I-0]+in[I-1];
    int diff = in[I-1]-in[I-0];
    myabs(diff);
    in[I-0] = (sum + diff) / 2;
    in[I-1] = (sum - diff) / 2;
    sorter<N, I+1, I+1>=N>::sort(in);
  }
};

template <int N,int I>
struct sorter<N,I,true> {
  static void sort(int *in) {
    sorter<N-1>::sort(in);
  }
};

template <int I, bool C>
struct sorter<0,I,C> {
  static void sort(int *) {
  }
};

int main() {
  int a[] = {3,4,1};
  sorter<3>::sort(a);
  std::cout << a[0] << "," << a[1] << "," << a[2] << std::endl;
}
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • Two swaps aren't sufficient to bring all of the 6 permutations in the correct order. For example, `1 2 3` is "sorted" into `2 3 1`. – fredoverflow Nov 11 '11 at 19:08
  • 1
    I'm pretty sure the default implementation of abs branches, but you can avoid the branch by just hex ANDing to remove the bit for negativity. Also depending on whether the OP considers a function call to be a branch you might want to explicitly inline the method. But those are trivial - good answer! – Dracorat Nov 11 '11 at 19:08
  • @FredOverflow - you're right, I've fixed that by adding the extra swap. – Flexo Nov 11 '11 at 19:13
  • 1
    @Dracorat - I added another version which includes `myabs` which is branch free. (GCC was doing it like that anyway under the covers though). Calling a function is unconditional and so can't really be considered branching. – Flexo Nov 11 '11 at 20:24
  • 1
    Well, I can't +1 your code more than once =) – Dracorat Nov 11 '11 at 21:43
7

You can write max, min and swap branch-free functions. Once you have these functions, you can use them to write sort function as:

void sort(int &a, int &b, int &c)
{
    int m1 = max(a,b,c);
    int m2 = min(a,b,c);
    b = a + b + c - m1 - m2;
    swap(m1, a);
    swap(m2, c);
}

And here are the helper functions:

void swap(int &a, int &b)
{
   int tmp = a; a = b; b = tmp;
}

int max( int a, int b, int c ) {
   int l1[] = { a, b };
   int l2[] = { l1[ a<b ], c };
   return l2[ l2[0] < c ];
}
int min( int a, int b, int c ) {
   int l1[] = { a, b };
   int l2[] = { l1[ a>b ], c };
   return l2[ l2[0] > c ];
}

Test code:

int main() {
        int a,b,c;
        std::cin >> a >> b >> c;
        sort(a,b,c);
        std::cout << a <<"," << b << "," << c << std::endl;
        return 0;
}

Input:

21 242 434

Output (descending order):

434, 242, 21

Demo : http://ideone.com/3ZOzc

I have taken the implementation of max from @David's answer from here, and implemented min with little twist.

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    on x86 that will become branch statements, the < and && are both implemented as such – Flexo Nov 11 '11 at 18:51
  • @awoodland: Not really. Just compile that with optimizations. – Nawaz Nov 11 '11 at 18:52
  • 2
    it really does, for example with `g++ -O4 -S` (4.7.0) min has two `jle` instructions and max has two `jge` instructions which are both looking at the results of `cmpl`s – Flexo Nov 11 '11 at 18:57
  • The answer you cited is incorrect `cmpl` followed by a `cmovg` *is* a branch. `cmovge` reads the condition codes from the `cmpl` and the code after it does depend on the result before. Looking at condition codes is the very definition of branching. – Flexo Nov 11 '11 at 19:04
  • @awoodland: Alright. I changed my solution, taking David's implementation of `max`. – Nawaz Nov 11 '11 at 19:06
  • You're gonna hate me for this, but there's still a branch with maximum optimizations (`jle` and `jge`) from the `>` and `<` operators. Like this you can get it to become `seta` with a bit of fiddling, but that's also a branch still. – Flexo Nov 11 '11 at 19:10