views:

559

answers:

7

As an exercise, I'd like to write a macro which tells me if an integer variable is signed. This is what I have so far and I get the results I expect if I try this on a char variable with gcc -fsigned-char or -funsigned-char.

#define ISVARSIGNED(V) (V = -1, (V < 0) ? 1 : 0)

Is this portable? Is there a way to do this without destroying the value of the variable?

+5  A: 
#define ISVARSIGNED(V) ((-(V) < 0) != ((V) < 0))

Without destroying the variable's value. But doesn't work for 0 values.

What about:

#define ISVARSIGNED(V) (((V)-(V)-1) < 0)
ypnos
#define ISVARSIGNED(V) ((-V < 0) != (V < 0))
plinth
yeah I shouldn't have C-)
ypnos
@plinth, you forgot the "extra" parens around 'V' which make it safe from insane macro expansions
rmeador
Fails for V == 0. Also, if V has the most negative possible value, it's likely to fail, since there is no positive equivalent.
David Thornley
#define ISVARSIGNED(V) ((-(V) < 0) != ((V) < 0)) Noted. Interestingly enough, this test fails on V == 0.
plinth
The second version works for 0, INT_MIN, UINT_MAX and INT_MAX in appropriate types.
plinth
Neither seem to work for unsigned short or unsigned char.
Michael Burr
+5  A: 

If you're using GCC you can use the typeof keyword to not overwrite the value:

#define ISVARSIGNED(V) ({ typeof (V) _V = -1; _V < 0 ? 1 : 0 })

This creates a temporary variable, _V, that has the same type as V.

As for portability, I don't know. It will work on a two's compliment machine (a.k.a. everything your code will ever run on in all probability), and I believe it will work on one's compliment and sign-and-magnitude machines as well. As a side note, if you use typeof, you may want to cast -1 to typeof (V) to make it safer (i.e. less likely to trigger warnings).

Chris Lutz
In C++, it's guaranteed to work by the Standard, regardless of the integer representation (for n bits, the value is 2^n - 1). I don't have the C standard handy (either of them).
David Thornley
Me neither, but I recall reading on Wikipedia (the source of all things true :P ) that the C standard allows the three representations I listed. Not that anyone uses them anymore...
Chris Lutz
+4  A: 
#define ISVARSIGNED(V)  ((V)<0 || (-V)<0 || (V-1)<0)

doesn't change the value of V. The third test handles the case where V == 0.

On my compiler (gcc/cygwin) this works for int and long but not for char or short.

#define ISVARSIGNED(V) ((V)-1<0 || -(V)-1<0)

also does the job in two tests.

mobrule
Best I've seen yet. Portable, standard-conforming, accurate as far as I can see.
David Thornley
Doesn't distinguish between signed/unsigned short and char. Those types are promoted to int when evaluating a < == > expression.
mobrule
If you want to make it work for `short` and `char` you could probably cast the variable to `char` before using it. That should handle most overflow issues. I think...
Chris Lutz
A: 

A distinguishing characteristic of signed/unsigned math is that when you right shift a signed number, the most significant bit is copied. When you shift an unsigned number, the new bits are 0.

#define HIGH_BIT(n) ((n) & (1 << sizeof(n) * CHAR_BITS - 1))
#define IS_SIGNED(n) (HIGH_BIT(n) ? HIGH_BIT(n >> 1) != 0 : HIGH_BIT(~n >> 1) != 0

So basically, this macro uses a conditional expression to determine whether the high bit of a number is set. If it's not, the macro sets it by bitwise negating the number. We can't do an arithmetic negation because -0 == 0. We then shift right by 1 bit and test whether sign extension occurred.

This assumes 2's complement arithmetic, but that's usually a safe assumption.

Jay Conrod
Do you have a source for these assumptions on the behavior of bit shifts?
Chris Lutz
The C99 standard (section 6.5.7) says a right shift of a signed, negative value is implementation defined. My interpretation is that there will be a sign extension on a 2's complement machine. Since C is not specific to 2's complement architectures, they won't come out and say this.
Jay Conrod
You would be more PC by using `CHAR_BITS` instead of the magic `8`. (Yes, I know, there's only a very few of us _not_ working on machines where a byte is 8bit. Still.)
sbi
@sbi Edited to use CHAR_BITS. I'm curious though: what machine are you working that doesn't have 8 bits per byte? And how many bits does it have? I know such systems existed a long time ago, but I didn't know any modern systems didn't have 8-bit bytes.
Jay Conrod
@sbi - If not using `CHAR_BITS` is a bad assumption, then relying on two's compliment is probably also a bad assumption. It all depends on how much effort you really want to put into technical correctness.
Chris Lutz
It is not guaranteed even on a 2's complement implementation that signed right shift is arithmetic. By all means say that this solution works on most implementations, on all implementation you've encountered, etc. But it's not a valid reading of the standard to say that right shift sign-fills on 2's complement machines. If the standard intended to specify that, then don't worry, it would :-)
Steve Jessop
@Jay: I'm working on PCs, and although over the years most of my code has been ported to many, many platforms, I don't believe there ever was one that didn't have an 8bit `char` type. `:)` That's why I wrote it would be more PC to do so. However, I suspect it might be that 16bit char types will be introduced while I'm still programming. (Hasn't .NET a 16bi `char`?)
sbi
@Chris: That's a very good point you made there. `:)`
sbi
@sbi: There are various DSPs with 16, 24(!) or 32 bit chars.
Steve Jessop
@onebyone: Thanks! I keep forgetting the emdedded world is strange and has all the strange things. `:)`
sbi
+1  A: 

This simple solution has no side effects, including the benefit of only referring to v once (which is important in a macro).

#define IS_SIGNED_TYPE(v)   ((typeof (v)-1 <= 0))

It's <= rather than just < to avoid compiler warnings for some cases (when enabled).

Chris Suter
A: 

Why on earth do you need it to be a macro? Templates are great for this:

template <typename T>
bool is_signed(T) {
    static_assert(std::numeric_limits<T>::is_specialized, "Specialize std::numeric_limits<T>");
    return std::numeric_limits<T>::is_signed;
}

Which will work out-of-the-box for all fundamental integral types. It will also fail at compile-time on pointers, which the version using only subtraction and comparison probably won't.

EDIT: Oops, the question requires C. Still, templates are the nice way :P

me22
A: 

A different approach to all the "make it negative" answers:

#define ISVARSIGNED(V) (~(V^V)<0)

That way there's no need to have special cases for different values of V, since ∀ V ∈ ℤ, V^V = 0.

me22