55

I use unsigned ints everywhere, and I'm not sure if I should. This can be from database primary key id columns to counters, etc. If a number should never be negative, then I will always used an unsigned int.

However I notice from other's code that no one else seems to do this. Is there something crucial that I'm overlooking?

Edit: Since this question I've also noticed that in C, returning negative values for errors is commonplace rather than throwing exceptions as in C++.

9
  • 26
    Just watch out for for(unsigned int n = 10; n >= 0; n --) (loops infinitely) Aug 1, 2011 at 10:55
  • 5
    In C and C++, unsigned ints have precisely defined overflow behaviour (modulo 2^n). Signed ints don't. Optimisers increasingly exploit that undefined overflow behaviour, leading to surprising results in some cases.
    – user8709
    Aug 1, 2011 at 17:34
  • 4
    Good question! I too was once tempted to use uints t restrict range but found that the risk/inconvenience did outweigh any benefit/convenience. Most libraries, as you said, accept regular ints where a uint would do. This makes it hard to work with, but also begs the question: is it worth it? In practice (assuming that you do not go about things in a dumb way), you will rarely have a value of -218 come in where a positive one is expected. That -218 must have come from somewhere, right? and you can trace its origin. Happens rarely. Do utilize assertions, exceptions, code contracts to assist you.
    – Job
    Aug 13, 2011 at 14:33
  • @William Ting: If this is about C/C++ only, you should add the appropriate tags to your question.
    – CesarGon
    Aug 13, 2011 at 16:15
  • 2
    @Chris: How significant is the infinite loop problem in reality? I mean, if it does make its way into release, then the code obviously wasn't tested. Even when you need some hours to debug it the first time you make this error, the second time you should know what to look for first when your code doesn't stop looping.
    – Secure
    Aug 14, 2011 at 14:14

11 Answers 11

32

Is there something crucial that I'm overlooking?

When calculations involve both signed and unsigned types as well as different sizes, the rules for type promotion can be complex and lead to unexpected behaviour.

I believe this is the main reason why Java omitted unsigned int types.

2
  • 3
    Another solution would be to require you to manually cast your numbers as appropriate. This is what Go seems to do (I've only played around with it a tiny bit though), and I like it more than Java's approach. Jul 14, 2012 at 0:02
  • 2
    That was a good reason for Java not to include 64-bit unsigned type, and maybe a decent reason not to include a 32-bit unsigned type [though the semantics of adding signed and unsigned 32-bit values wouldn't be hard--such an operation should simply yield a 64-bit signed result]. Unsigned types smaller than int would pose no such difficulty, however (since any computations will promote to int); I have nothing good to say about the lack of an unsigned-byte type.
    – supercat
    Dec 3, 2014 at 22:36
19

I think that Michael has a valid point, but IMO the reason why everybody uses int all the time (especially in for (int i = 0; i < max, i++) is that we learned it that way. When every single example in a 'how to learn programming' book uses int in a for loop, very few will ever question that practice.

The other reason is that int is 25% shorter than uint, and we are all lazy... ;-)

4
  • 2
    I agree with the educational issue. Most people seem never to question what they read: If it's in a book, it cannot be wrong, right ? Aug 14, 2011 at 13:14
  • 2
    That's also presumably why everyone uses postfix ++ when incrementing, despite the fact that its particular behaviour is rarely needed and might even lead to pointless churning over copies if the loop index is an iterator or other non-fundamental type (or the compiler is really dense). Sep 16, 2018 at 10:02
  • 1
    Just don't do something like "for (uint i = 10; i >= 0; --i)". Using only ints for loop variables avoids this possibility. Sep 18, 2018 at 16:17
  • @underscore_d I've created an account for this site just to upvote this comment. I hate unnecessary post-increments especially in loops. But at the same time it works as a good criterion to know whether the writer is experienced, because no experienced C++ programmers should write codes in such a way.
    – ynn
    Apr 28, 2020 at 20:33
12

Encoding range information into types is A Good Thing. It enforces using reasonable numbers at compile time.

Many architectures seem to have specialized instructions for dealing with int -> float conversions. The conversion from unsigned can be slower (a tiny bit).

10

Mixing signed and unsigned types can get you into a world of pain. And you can't use all unsigned types because you will encounter things that either have a valid range that includes negative numbers or need a value to indicate an error and -1 is most natural. So the net result is that many programmers use all signed integers types.

1
  • 2
    Maybe it's a better practice to not mix valid values with error indication in the same variable and to use separate variables for this. Granted, the C standard library doesn't set a good example here.
    – Secure
    Jul 15, 2012 at 9:25
7

I use unsigned int in C++ for array indices, mostly, and for any counter which starts from 0. I think it is good to say explicitly "this variable cannot be negative".

2
  • 16
    You should probably be using size_t for this in c++
    – JohnB
    Aug 1, 2011 at 13:10
  • 2
    I know, I just can't be bothered to.
    – quant_dev
    Aug 1, 2011 at 13:14
7

For me types are much about communication. By using explicitly an unsigned int you tell me that signed values are not valid values. This allows me to add some information when reading your code in addition to the variable name. Ideally I a non anonymous type would tell me more, but it gives me more information than if you had used ints everywhere.

Unfortunately not everybody is very conscious about what their code communicates, and that is probably the reason you see ints everywhere even though the values are at least unsigned.

3
  • 4
    But I might want to restrict my values for a month to 1 through 12 only. Do I use another type for it? What about a month? Some languages actually allow restricting values like that. Others, such as .Net/C# provide Code Contracts. Sure, non-negative integers occur rather frequently, but most languages that support this type do not support further restrictions. So, should one use a mix of uints and error checking, or just do everything through error checking? Most libraries do not ask for uint where it would make sense to use one, hence using one and casting can be inconvenient.
    – Job
    Aug 15, 2011 at 3:17
  • @Job I would say you should use some kind of compiler/interpreter enforced restriction on your months. It may give you some boilerplate to set up, but for the future you have an enforced restriction that prevents error and communicates much clearer what you are expecting. Preventing errors and easing communication are much more important than inconvenience while implementing.
    – daramarak
    Apr 12, 2013 at 6:31
  • 1
    "I might want to restrict my values for a month to 1 through 12 only" If you have a finite set of values like months, you should use an enumeration type, not raw integers.
    – jscs
    Nov 23, 2017 at 22:04
3

You should care about this when you're dealing with an integer that might actually approach or exceed the limits of a signed int. Since the positive maximum of a 32 bit integer is 2,147,483,647 then you should use an unsigned int if you know it will a) never be negative and b) might reach 2,147,483,648. In most cases, including database keys and counters, I will never even approach these kinds of numbers so I don't bother concerning myself with worrying whether the sign bit is used for a numeric value or to indicate the sign.

I would say: use int unless you know you need an unsigned int.

1
  • 2
    When working with values that can reach the maximum values, you should start to check the operations for integer overflows, regardless of the sign. These checks are usually easier for unsigned types, because most operations have well defined results without undefined and implementation defined behaviour.
    – Secure
    Jul 15, 2012 at 9:21
3

I'm inclined to agree with Joel Etherton's reasoning, but come to the opposite conclusion. The way I see it, even if you know that numbers are unlikely to ever approach the limits of a signed type, if you know that negative numbers won't happen, then there is very little reason to use the signed variant of a type.

For the same reason why I have, in a few select instances, used BIGINT (64 bit integer) rather than INTEGER (32-bit integer) in SQL Server tables. The probability that the data would hit the 32-bit limit within any reasonable amount of time is miniscule, but if it happens, the consequences in some situations could be quite devastating. Just be sure to map types between languages properly, or you are going to end up with interesting weirdness really far down the road...

That said, for some things, such as database primary key values, signed or unsigned really doesn't matter, because unless you are manually repairing broken data or something along those lines, you aren't ever dealing with the value directly; it's an identifier, nothing more. In those cases, consistency is probably more important than the exact choice of signedness. Otherwise, you end up with some foreign key columns that are signed and others that are unsigned, with no apparent pattern to it - or that interesting weirdness again.

1
  • If you are working with data extracted from an SAP system, I strongly recommend BIGINT for ID fields (like CustomerNumber, ArticleNumber etc). As long as nobody uses alphanumeric strings as IDs, that is... sigh
    – Treb
    Aug 18, 2011 at 19:45
3

It's a tradeoff between simplicity and reliability. The more bugs that can be caught at compile time, the more reliable the software. Different people and organizations are on different points along that spectrum.

If you ever do any high-reliability programming in Ada, you even use different types for variables like distance in feet vs. distance in meters, and the compiler flags it if you accidentally assign one to the other. That's perfect for programming a guided missile, but overkill (pun intended) if you're validating a web form. There's not necessarily anything wrong with either way as long as it fits the requirements.

2

I would recommend that outside space-constrained data-storage and data-interchange contexts, one should generally use signed types. In most cases where a 32-bit signed integer would too small but a 32-bit unsigned value would suffice for today, it won't be long before the 32-bit unsigned value isn't big enough either.

The primary times one should use unsigned types are when one is either assembling multiple values into a larger one (e.g. converting four bytes to a 32-bit number) or decomposing larger values into smaller ones (e.g. storing a 32-bit number as four bytes), or when one has a quantity which is expected to "roll over" periodically and one needs to deal with it (think of a residential utility meter; most of them have enough digits to ensure that they won't possibly roll over between readings if they're read three times a year, but not enough to ensure they won't roll over within the useful life of the meter). Unsigned types often have enough 'weirdness' that they should only be used in cases where their semantics are necessary.

9
  • 1
    "I would recommend [...] generally use signed types." Hm, you forgot to mention the advantages of signed types and only gave a list of when to use unsigned types. "weirdness"? While most unsigned operations have well defined behaviour and results, you enter undefined and implementation defined behaviour when using signed types (overflow, bit shift, ...). You have a strange definition of "weirdness" here.
    – Secure
    Jul 15, 2012 at 9:15
  • 1
    @Secure: The "weirdness" to which I refer has to do with the semantics of comparison operators, especially in operations which involve mixed signed and unsigned types. You are correct that the behavior of signed types is undefined when using values large enough to overflow, but the behavior of unsigned types can be surprising even when dealing with relatively small numbers. For example, (-3)+(1u) is greater than -1. Also, some normal mathematical associative relations that would apply to numbers don't apply to unsigned. For example, (a-b)>c does not imply (a-c)>b.
    – supercat
    Jul 16, 2012 at 14:34
  • 1
    @Secure: While it's true that one can't always rely on such associative behavior with "large" signed numbers either, the behaviors do work as expected when dealing with numbers that are "small" relative to the domain of signed integers. By contrast, the above mentioned non-association is problematic with unsigned values "2 3 1". Incidentally, the fact that signed behaviors have undefined behavior when used out of bounds can allow for improved code generation on some platforms when using values smaller than the native word size.
    – supercat
    Jul 16, 2012 at 14:37
  • 1
    Had these comments been in your answer in the first place, instead of a recommendation and "name-calling" without giving any reasons, I wouldn't have commented it. ;) Though I still not agree with "weirdness" here, it's simply the definition of the type. Use the right tool for the given job, and know the tool, of course. Unsigned types are the wrong tool when you need +/- relations. There is a reason why size_t is unsigned and ptrdiff_t is signed.
    – Secure
    Jul 16, 2012 at 20:35
  • 2
    @Secure: If what one wants is to represent a sequence of bits, unsigned types are great; I think we agree there. And on some small micros, unsigned types can be more efficient for numerical quantities. They are also useful in cases where deltas represent numerical quantities but the actual values don't (e.g. TCP sequence numbers). On the other hand, any time one subtracts unsigned values one has to worry about corner cases even when the numbers are small; such maths with signed values only present corner cases when numbers are big.
    – supercat
    Jul 17, 2012 at 15:19
2

I use unsigned ints to make my code and its intent more clear. One thing I do to guard against unexpected implicit conversions when doing arithmetic with both signed and unsigned types is to use an unsigned short (2 bytes usually) for my unsigned variables. This is effective for a couple reasons:

  • When you do arithmetic with your unsigned short variables and literals (which are of type int) or variables of type int, this ensures the unsigned variable will always be promoted to an int before evaluating the expression, since int always has a higher rank than short. This avoids any unexpected behavior doing arithmetic with signed and unsigned types, assuming the result of the expression fits into a signed int of course.
  • Most of the time, the unsigned variables you're using won't exceed the max value of an unsigned 2-byte short (65,535)

The general principle is that the type of your unsigned variables should have a lower rank than the type of the signed variables in order to ensure promotion to the signed type. Then you won't have any unexpected overflow behavior. Obviously you can't ensure this all the time, but (most) often it's feasible to ensure this.

For example, recently I had some for loop something like this:

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

The literal '2' is of type int. If i was an unsigned int instead of an unsigned short, then in the sub-expression (i-2), 2 would be promoted to an unsigned int (since unsigned int has a higher priority than signed int). If i = 0, then the sub-expression equals (0u-2u) = some massive value due to overflow. Same idea with i = 1. However, since i is an unsigned short, it gets promoted to the same type as literal '2', which is signed int, and everything works fine.

For added safety: in the rare case where the architecture you're implementing on causes int to be 2 bytes, this might cause both operands in the arithmetic expression to be promoted to unsigned int in the case where the unsigned short variable doesn't fit into the signed 2-byte int, the latter of which has a max value of 32,767 < 65,535. (See https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned for more details). To guard against this you can simply add a static_assert to your program as follows:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

and it won't compile on architectures where int is 2 bytes.

Not the answer you're looking for? Browse other questions tagged or ask your own question.