23

I understand the differences in capacity and values they can represent but it seems as though people always use Int32 regardless of whether it is appropriate. No one ever seems to use the unsigned version (uint) even though a lot of the time it fits better as it describes a value that cannot be negative (perhaps to represent an ID of a database record). Also, no one ever seems to use short/Int16 regardless of the required capacity of the value.

Objectively, are there cases where it's better to use uint or short/Int16 and if so, which are they?

  • 13
    Popularity is not always a viable metric for evaluating software design decisions. Just because a practice is popular doesn't mean that it is an appropriate practice for your particular application, or that it is even a good practice. – Robert Harvey Dec 3 '14 at 16:46
  • Related reading: What are the best practices regarding unsigned ints? – user22815 Dec 3 '14 at 18:11
  • 1
    The short answer, I think, is that programmers have gotten used to the signed semantics and are inclined to assume those, even when dealing with unsigned types (and thus unsigned semantics). Most people assume it's a matter of the programmer being lazy or uneducated, but the programmer in question may in fact be very educated and very careful and wants to avoid subtle pitfalls. If you like, take a look at soundsoftware.ac.uk/c-pitfall-unsigned and anteru.net/2010/05/17/736. – Theodoros Chatzigiannakis Dec 3 '14 at 18:45
  • In an unsigned number, the sign is more null than either positive or negative. If you think of it as something which can never be negative or is always positive, you're going to be surprised (and often angry) at the results because it doesn't really work that way, especially when compared with or subtracted to/from signed values. – Adam D. Ruppe Dec 3 '14 at 19:39
  • 1
    In my experience the lot of programmers, who have ever programmed in C language tend to care about bytes, still in these days, of GBs of memory and storage space. – user1451111 Jul 27 '18 at 16:33
25

I suspect you are referring to a perspective colored by your own experiences where you have not worked around folks who use integral types properly. This may well be a common occurrence, but it's been my experience that people commonly do use them correctly as well.

The benefit is memory space and cpu time, possibly IO space as well depending on whether the types are ever sent over the wire or to a disk. Unsigned types give you compiler checks to ensure you won't do certain operations that are impossible, plus extending the available range while maintaining the smaller size for heightened performance where it may be necessary.

The correct use is as you would expect - anytime you know for certain you can use them permanently (do not constrain without certainty or you will regret it later).

  • If you're trying to represent something that could never reasonably be negative (public uint NumberOfPeople) use an unsigned type.
  • If you're trying to represent something that could never reasonably be greater than 255 (public byte DamagedToothCount), use a byte.
  • If you're trying to represent something that could reasonably be greater than 255, but never a significant number of thousands, use a short (public short JimmyHoffasBankBalance).
  • If you're trying to represent something that could be many hundreds of thousands, millions even, but unlikely to ever reach multiple billions, use an int (public int HoursSinceUnixEpoch).
  • If you know for certain this number may have an unboundedly large value or you think it may have multiple billions but you're not certain how many billions, long is your best bet. If long's not big enough you have an interesting problem and need to start looking at arbitrary precision numerics (public long MyReallyGreatAppsUserCountThisIsNotWishfulThinkingAtAll).

This reasoning can be used throughout in choosing between signed, unsigned, and varied sizes of types et al, just think about the logical truths of the data you're representing in reality.

  • 11
    +1, although I have to make it clear that phone “numbers” are not numbers but strings of digits and optionally formatting. You seem to be aware of this, but we don't want to set a bad example, now do we? Also, arbitrarily restricting the range of some value is a short-sighted antipattern – int everywhere unless you know for a fact that the problem domain actually restricts the value – no bank would want to hard-limit accounts to 33K quid (and think of the fun when that overflows…!). – amon Dec 3 '14 at 17:12
  • 3
    New Life Goal: Sizable over-draft that underflows the integral type of my bank account. – recursion.ninja Dec 3 '14 at 18:27
  • 11
    There are good reasons not to use unsigned types in certain places, for example, when arithmetic is mixed between signed and unsigned. See What are the best practices regarding unsigned ints?. – user22815 Dec 3 '14 at 18:28
  • 19
    I disagree with the reasoning here. Unsigned types are often a mistake because of subtraction and comparisons being unexpected if you are used to ints (they work in a consistent way but it is not "always positive"). I'd avoid them unless you have a very specific reason to use them. Also, why does the size matter for byte vs short vs int? You often don't even save space, since structs will pad those members or arrays to a certain alignment. I would use a byte only if size is really important (unlikely especially to C# code I've seen) or if you specifically want wraparound at 255 for something. – Adam D. Ruppe Dec 3 '14 at 19:33
  • 4
    "benefit is memory space and cpu time"... I don't see any case where tiny-types would actually save CPU time. Integer operations never get faster than they are on the machine-sized types, i.e. as far as CPU is concerned you might as well use long. Memory saving can of course indirectly save time by improving cache-line efficiency and so on, but OTOH the alignment issues with small types can indirectly cost time. – leftaroundabout Dec 4 '14 at 1:11
16

Sure, there are cases where it's better to use uint or short or Int16. When you know that your data range will fit within the constraints of that variable type, then it's okay to use that type.

In memory constrained environments or when dealing with large amounts of objects, it can make sense to use the smallest size variable. For example, there is a significant difference in size for a million element array of ints vs. shorts.

Oftentimes, that doesn't happen in actual code for one or more of the following reasons:

  • Data constraints were not known ahead of time
  • There was a chance that the data constraints weren't solid or were known to likely be changed
  • There was a hope of re-using the function with a broader data range
  • Developer didn't take the time to think through the constraints
  • Memory savings were insignificant to justify using a smaller variable type

There's a lot more possible reasons, but they boil down to this: The time involved in deciding upon and using a different variable type didn't provide enough benefit to justify doing so.

8

In C, in contexts not involving integer promotion, unsigned values were specified to behave as members of a "wrapping" abstract algebraic ring (so for any X and Y, X-Y will yield a unique value which, when added to Y, will yield X), while signed integer types were specified as behaving like integers when computations stayed within a certain range, and allowed to do anything at all when computations went beyond that. Numerical semantics in C#, however, are totally different. When within a checked numerical context, both signed and unsigned types behave like integers provided computations stay in range, and throw OverflowException when they don't; in an unchecked context, they both behave like algebraic rings.

The only time it's generally worthwhile to use any data type smaller than Int32 is when it's necessary to packing or unpacking things for compact storage or transport. If one needs to store half a billion positive numbers, and they'll all be in the range 0 to 100, using one byte each rather than four will save 1.5 gigabytes of storage. That's a big savings. If a piece of code needs to store a total of a couple hundred values, however, making each of them one byte rather than four would save about 600 bytes. Probably not worth bothering with.

With regard to unsigned types, the only times they're really useful are when performing information interchange, or when subdividing numbers into pieces. If, for example, one needs to do math on 96-bit integers, it will likely be much easier to perform the computations on groups of three unsigned 32 bit integers, than on groups of signed integers. Otherwise, there aren't a whole lot of situations where the range of a signed 32- or 64-bit value would be inadequate, but the same size of unsigned value would suffice.

4

It is generally a bad idea to use unsigned types because they overflow in unpleasant ways.x = 5-6 is suddenly a timebomb in your code. Meanwhile the benefits of unsigned types boil down to a single extra bit of precision, and if that bit is worth it for you, you should almost certainly be using a bigger type instead.

There are use cases where a smaller type could make sense but unless you're concerned about memory usage or need to pack data for transmission or cache efficiency or a handful of other concerns it's usually the case that there is no benefit to using a smaller type. Moreover, on many architectures, it's actually slower to use these types so they can actually impose a small cost.

  • 3
    In C, signed overflow is even worse than unsigned overflow (because it's undefined behavior, while unsigned is specified to roll over like an odometer). OTOH, signed over/underflow is much less common in practice than unsigned underflow. – Kevin Dec 3 '14 at 19:48
  • True, but signed overflow is usually more obvious and predictable. – Jack Aidley Dec 3 '14 at 19:49
  • I generally agree, but you do need to be aware, for example, that modern compilers may optimize i+1>i into 1 if i is signed, along with a whole host of other nasty behavior. Unsigned overflow can cause a bug in a corner case. Signed overflow can make your entire program meaningless. – Kevin Dec 3 '14 at 19:51
  • @JackAidley I am quite sure what you say makes no sense, as 5-6 yields the same bit pattern, no matter if its unsigned or not. – Ingo Dec 6 '14 at 20:18
  • @Ingo: how often do you look at bit patterns? What matters is the meaning of the bit pattern not the which bits are on or off. – Jack Aidley Dec 7 '14 at 14:29
2

Often forgotten and possibly tangential to your question, when dealing specifically with .NET types, is CLS Compliance. Not all types are available to all languages built on the .NET Framework.

If you're writing code to be consumed by languages other than C# and wish that code to be guaranteed to interoperate with as many .NET languages as possible then you must restrict your type usage to those that are CLS Compliant.

For example, early versions of VB.NET (7.0 and 7.1) did not support unsigned integers (UInteger):

http://msdn.microsoft.com/en-us/library/aa903459(v=vs.71).aspx

Unsigned integers are not CLS compliant and so should be used with care if you are unsure who your class library consumer will be.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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