4

Is there a good and fast way in C/C++ to test if multiple variables contains either all positive or all negative values?

Say there a 5 variables to test:

Variant 1

int test(int a[5]) {
    if (a[0] < 0 && a[1] < 0 && a[2] < 0 && a[3] < 0 && a[4] < 0) {
        return -1;
    } else if (a[0] > 0 && a[1] > 0 && a[2] > 0 && a[3] > 0 && a[4] > 0) {
        return 1;
    } else {
        return 0;
    }
}

Variant 2

int test(int a[5]) {
    unsigned int mask = 0;
    mask |= (a[0] >> numeric_limits<int>::digits) << 1;
    mask |= (a[1] >> numeric_limits<int>::digits) << 2;
    mask |= (a[2] >> numeric_limits<int>::digits) << 3;
    mask |= (a[3] >> numeric_limits<int>::digits) << 4;
    mask |= (a[4] >> numeric_limits<int>::digits) << 5;
    if (mask == 0) {
        return 1;
    } else if (mask == (1 << 5) - 1) {
        return -1;
    } else {
        return 0;
    }
}

Variant 2a

int test(int a[5]) {
    unsigned int mask = 0;
    for (int i = 0; i < 5; i++) {
        mask <<= 1;
        mask |= a[i] >> numeric_limits<int>::digits;
    }
    if (mask == 0) {
        return 1;
    } else if (mask == (1 << 5) - 1) {
        return -1;
    } else {
        return 0;
    }
}

What Version should I prefer? Is there any adavantage using variant 2/2a over 1? Or is there a better/faster/cleaner way?

skaffman
  • 398,947
  • 96
  • 818
  • 769
bkausbk
  • 2,740
  • 1
  • 36
  • 52

8 Answers8

9

I think your question and what you're looking for don't agree. You asked how to detect if they're signed or unsigned, but it looks like you mean how to test if they're positive or negative.

A quick test for all negative:

if ((a[0]&a[1]&a[2]&a[3]&a[4])<0)

and all non-negative (>=0):

if ((a[0]|a[1]|a[2]|a[3]|a[4])>=0)

I can't think of a good way to test that they're all strictly positive (not zero) right off, but there should be one.

Note that these tests are correct and portable for twos complement systems (anything in the real world you would care about), but they're slightly wrong for ones complement or sign-magnitude. They might can be fixed if you really care.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • You are right, what I ment was, if they are all positive or negative. – bkausbk Apr 26 '11 at 12:58
  • Correct me if I'm wrong (trying to understand your answer), it will AND all the bits including the MSB (sign bit) and if its 1 then the number < 0 otherwise > 0? – snoofkin Apr 26 '11 at 13:12
  • 1
    Why are they slightly wrong for ones complement and sign magnitude? I can't see why they wouldn't yield a negative result for these. All of these representations have the sign bit set for negative numbers or negative zero (which I think will be regarded non-negative but I'm not sure about this in C++). Can you please elaborate? – Johannes Schaub - litb Apr 26 '11 at 13:18
  • Let me explain: if at least one value is not negative (sign bit not set = most significant bit is not set), the AND operation will be 0 at that most significant bit => the resulting integer is >= 0. – bkausbk Apr 26 '11 at 13:18
  • @Johannes: The issue is with negative zero, which isn't actually negative. It's possible that `a[0]&a[1]` is negative 0, in which case the `<0` test would fail and give a false negative. Likewise, if any of the elements `a[i]` is negative 0, you'd get a false positive. – R.. GitHub STOP HELPING ICE Apr 26 '11 at 13:38
  • 1
    @R.. is it specified somewhere that negative zero < positive zero is true? Or is it entirely implementation dependent? – Johannes Schaub - litb Apr 26 '11 at 13:45
  • Negative zero < positive zero is absolutely **not true** (for integers). Everything in C is specified in terms of *values*, not *representations*, except bitwise operators on signed integers. "Negative zero" as a value is zero. – R.. GitHub STOP HELPING ICE Apr 26 '11 at 13:47
  • Ah nvm I see the problem now. If one of them is a negative zero, and the others are negative, then you would like to get `false`, but you get `true` on ones-complement (`false` on sign-magnitude). – Johannes Schaub - litb Apr 27 '11 at 20:45
5

I guess you mean negative/positive, (un)signed means whether a sign exists at all. This one works for any iterable (this assumes you count 0 as positive):

template <class T>
bool allpos(const T start, const T end) {
    T it;
    for (it = start; it != end; it++) {
        if (*it < 0) return false;
    }

    return true;
}

// usage
int a[5] = {-5, 3, 1, 0, 4};
bool ispos = allpos(a, a + 5);

Note: This is a good and fast way

This may not be the absolutely extremely superduperfastest way to do it, but it certainly is readable and really fast. Optimizing this is just not worth it.

orlp
  • 112,504
  • 36
  • 218
  • 315
  • Yes, indeed this looks realy readable and STL alike and is preferable for c++. – bkausbk Apr 26 '11 at 13:07
  • @Oli Charlesworth: I'm sorry, I'm still beginning with C++, I'll fix it :) – orlp Apr 26 '11 at 13:10
  • So if T is std::vector::iterator it will work? Perhaps you meant the parameters and local variables to be the iterator type and not a pointer? – gregg Apr 26 '11 at 13:37
  • This checks if they are all positive, but not if they are either all positive or all negative like the question stated... – Rui Marques Mar 02 '14 at 16:57
4

Variant 1 is the only readable one.

However, you could make it nicer using a loop:

int test(int *a, int n) {
    int neg = 0;
    for(int i = 0; i < n; i++) {
        if(a[i] < 0) neg++;
    }
    if(neg == 0) return 1;
    else if(neg == n) return -1;
    else return 0;
}
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • 1
    Splendid solution if you ask me. – Neowizard Apr 26 '11 at 13:03
  • If given `{-1, -1, 1, 1, 1}` this function returns 1, while this certainly doesn't answer the question `if multiple variables contains either all positive or all negative values`. – orlp Apr 26 '11 at 13:13
1

I agree with previous posters that loops are simpler. The following solution combines Nightcracker's template and ThiefMaster's full solution, with early-outing if a sign-change is detected while looping over the variables (early-outing). And it works for floating point values.

template<typename T>
int testConsistentSigns(const T* i_begin, const T* i_end)
  {
  bool is_positive = !(*i_begin < 0);
  for(const T* it = i_begin + 1; it < i_end; ++it)
    {
    if((*it < 0) && is_positive)
      return 0;
    }

  if(is_positive)
    return 1;
  return -1;
 }   
Joris Timmermans
  • 10,814
  • 2
  • 49
  • 75
0

In terms of speed, I suggest you profile each of your example in turn to discover which is the fastest on your particular platform.

In terms of ease of understanding, I'd say that the first example is the most obvious, though that's just my opinion.

Of course, the first version is a maintenance nightmare if you have more than 5 variables. The second and third variants are better for this, but obviously have a limit of 32 variables. To make them fully flexible, I would suggest keeping counters of the number of positive and negative variables, in a loop. After the end of the loop, just check that one or other counter is zero.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
0

First off, create a method\procedure. That'll boost readability by a whole lot (no matter how you implement it, it'll be cleaner then all the options above).

Second, I think that the function:

bool isNeg(int x) { return x < 0;}

s cleaner then using bit masks, so I'll go with option 1, and when it comes to speed, let the compiler work that out for you in such low-level cases.

The final code should look something like:

int test(int a[5]) {
  bool allNeg = true;
  bool allPos = true;
  for (i = 0; i < 5; i++){
    if (isNeg(a[i]) allPos = false;
    if (isPos(a[i]) allNeg = false;
  }
  if (allNeg) return -1;
  if (allPos) return 1;
  return 0;
}
Neowizard
  • 2,981
  • 1
  • 21
  • 39
  • 2
    Why not simply leave `x < 0` in the code? If you call a function 5 times it's certainly not more readable than having the actual comparion at that place. – ThiefMaster Apr 26 '11 at 12:57
0

You could find maximum element, if it is negative then all elements are negative:

template<typename T>
bool all_negative( const T* first, const T* last )
{
  const T* max_el = std::max_element( first, last );
  if ( *max_el < T(0) ) return true;
  else return false;
}

You could use boost::minmax_element to find if all elements are negative/positive in one loop:

template<typename T>
int positive_negative( const T* first, const T* last )
{
  std::pair<const T*,const T*> min_max_el = boost::minmax_element( first, last );
  if ( *min_max_el.second < T(0) ) return -1;
  else if ( *min_max_el.first > T(0) ) return 1;
  else return 0;
}

If the sequence is non-empty, the function minmax_element performs at most 3 * (last - first - 1) / 2 comparisons.

Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
0

If you only need to know less/greater than zero one at a time, or can be content with < and >= you can do it easily with find_if like this:

#include <iostream>

template <class Iter>
int all_neg(Iter begin, Iter end)
{
    return std::find_if(begin, end, std::bind2nd(std::greater_equal<int>(), 0)) == end;
}

int main()
{
    int a1[5] = { 1, 2, 3, 4, 5 };
    int a2[5] = { -1, 2, 3, 4, 5 };
    int a3[5] = { -1, -2, -3, -4, -5 };
    int a4[5] = { 0 };

    std::cout << all_neg(a1, a1 + 5) << ":"
              << all_neg(a2, a2 + 5) << ":"
              << all_neg(a3, a3 + 5) << ":"
              << all_neg(a4, a4 + 5) << std::endl;
}

You can also use a more complicated predicate that keeps track of any pos/neg to answer your original question if you really need that level of detail.

Mark B
  • 95,107
  • 10
  • 109
  • 188