Monday, July 19, 2010

Max/Min Values for Signed and Unsigned Ints

There are a variety of ways you can assign the max/min values to a signed/unsigned integer.

A very professional looking way is using the numeric_limits class like so:

#include <limits>

int main() {
int x = numeric_limits<int>::max();
int y = numeric_limits<int>::min();
return 0;
}


But there are many other ways you can do this; and they all take a lot less typing than using numeric_limits, and you don't have to include any extra headers...

Lets first take a look at max/min integer values for different variables (in hexadecimal representation because its easier to memorize):

// u8, u16, u32... mean unsigned int of 8, 16, and 32 bits respectively
// s8, s16, s32... mean signed int of 8, 16, and 32 bits respectively
----------------------------------
| type | max | min |
----------------------------------
| u8 | 0xff | 0x0 |
| u16 | 0xffff | 0x0 |
| u32 | 0xffffffff | 0x0 |
----------------------------------
| s8 | 0x7f | 0x80 |
| s16 | 0x7fff | 0x8000 |
| s32 | 0x7fffffff | 0x80000000 |
----------------------------------


The table is pretty easy to memorize. Essentially, for unsigned ints, the max value is always a bunch of f's in hexadecimal, and the min-value is always 0.
For signed ints, the max value is always 0x7f, followed by a bunch of f's.
And lastly the min value for signed ints is always 0x80, followed by a bunch of 0's.


One thing to notice is the bitwise representation for "-1" always has all-bits set.
That means that for a 32bit integer, "-1" is "0xffffffff", which is the max value for an unsigned integer.

This means that for unsigned integers, we can declare int max/min like this:

int main() {
unsigned int x = -1u; // Unsigned int max
unsigned int y = 0; // Unsigned int min
return 0;
}


In c++, you can put the suffix "u" at the end of an int literal to mean its unsigned. So what that code is doing, is treating the value "-1" as an unsigned int, which means its assigning 0xffffffff to the variable 'x'.

Here's the thing, I've noticed that some compilers end up generating warnings when you treat a negative number as unsigned in operations, so instead of writing '-1u', we can notice that negative one, is really the same as NOT 0, so we can instead write '~0u'. This will do the same thing, and is generally better to do since it won't generate the compiler errors.

So its nicer to do:

int main() {
unsigned int x = ~0u; // Unsigned int max
unsigned int y = 0; // Unsigned int min
return 0;
}


Now that we know this trick for unsigned ints, lets think of signed integers.
The max value for 32bit signed integers is 0x7fffffff.
If we notice though, 0x7fffffff is really (0xffffffff >> 1).
That is, signed int max, is equal to unsigned int max shifted right by one.
But since we already learned the trick on how to represent unsigned int max easily, we can use that to our advantage:

int main() {
int x = ~0u>>1; // Signed int max
return 0;
}


The last thing to notice for signed ints, is that the minimum value for 32bit signed ints in hex is 0x80000000, but it just so happens that that number is signed int max + 1, that is 0x7fffffff + 1.

So we can end up doing this:

int main() {
int x = ~0u>>1; // Signed int max
int y =(~0u>>1)+1; // Signed int min
return 0;
}


That about does it for integers, for floats its not as simple so I didn't talk about them here. Maybe I'll end up making another blog post about the different floating point representations...

Notes:
When doing the '-1' or '~0' tricks where you shift the value; be sure to do the shift as an unsigned operation! That is either do: ~0u>>1 or (unsigned int)~0>>1, DO NOT do ~0>>1. This will do an arithmetic shift, instead of a logical shift, and make the result be -1, instead of 0x7fffffff.

And in-case you're wondering, these tricks are not 'slower' than typing in the numbers manually. C++ compilers do something called constant-propagation and constant-folding, which converts stuff like (-1u>>1)+1 to a constant at compile-time, and therefore there's no extra overhead compared to typing 0x80000000....

Also, this will not apply to most people, but another thing to note is that some very-old/weird hardware can use one's complement integers (instead of two's complement) or some other funky stuff; when dealing with weird hardware its best to use the numeric_limits class instead of bitwise tricks, because numeric_limits assigns its values based on the specific platform. That-said, I don't even know any hardware that uses one's complement for signed integers, so these tricks are almost always safe :p

4 comments:

  1. man you are doing a great jog... I mean sharing your knowledge! thxs

    ReplyDelete
  2. Thanks for the comment, glad you found these posts interesting :D

    ReplyDelete
  3. I agree, I found your posts facinating to read, like all your (very informative) posts on the internets.

    ReplyDelete
  4. Thank you Ryan,
    Compliments like that help encourage me to continue blogging. So thanks again.

    ReplyDelete