9

Do most application developers use signed integers in places where they really mean to use unsigned integers? I do it all the time, so do my co-workers. I haven't seen a lot of other extensive codebases (other than the Delphi VCL) and examples on the internet usually use integer. Whereas the VCL developers use their own data types (which would be the most non-lazy way to go about declaring variables).

Something seems a little dreadful about code like this

TStuffRec = record
   recordID : Integer;
   thingID : Integer;
   otherThingID : Integer;
end;

when it could be written as

TStuffRec = record
   recordID : Cardinal;
   thingID : Cardinal;
   otherThingID : Cardinal;
end;

Functionally, these records almost always work the same (and hopefully will continue to work the same even in 64-bit Delphi). But very large numbers will have conversion issues.

But there are drawbacks to using unsigned ints too. Mainly stemming from how annoying it is to mix the two.

The real question is, is this a thing that actually gets thought about or included in best practices? Is it usually just up to the developer?

2
  • 5
    Peter, are you looking for Delphi-specific answers only?
    – Adam Lear
    Jul 25, 2011 at 20:51
  • 3
    @Anna Understanding how Delphi datatypes work would make for a most excellent answer. I'm reasonably sure that C programmers could comprehend and answer this question though. Jul 25, 2011 at 20:58

4 Answers 4

9

One reason why I don't use unsigned integer types all that much in Delphi is that they can create problems when mixed with signed integers. Here's one that bit me once:

for i := 0 to List.Count - 1 do
  //do something here

I had i declared as an unsigned integer, (after all, it's an index into a list that starts at 0, it never has to be negative, right?), but when List.Count was 0, it would not short-circuit the loop as expected because 0 - 1 evaluates to a really high positive number. Oops!

Between the potential safety problems inherent in mixing signed and unsigned integers, and the range issues, (if you're going to need positive numbers larger than high(signed whatever), it's quite likely that you'll also end up needing positive numbers larger than high(unsigned whatever) too, so moving up to the next larger size instead of switching from signed to unsigned of the same size is usually the correct action,) I really haven't found too many uses for unsigned integers when representing most data.

5
  • 2
    Somewhat related, one of the major risks of using a data type that's potentially smaller than necessary (as opposed to just unsigned vs. signed) is that if the exit condition is larger than you planned for, you can actually end up with an infinite loop as the counter overflows again and again. It sounds stupid in hindsight, but I once wrote a program that was supposed to loop through every possible byte value, and it took about 15 minutes to finally convince myself that it was not possible to do with a byte counter.
    – Aaronaught
    Jul 25, 2011 at 23:40
  • @Aaronaught: Not in Delphi. (At least not unless you do something stupid like disabling the built-in overflow checking.) You'll end up with an exception when the counter overflows, instead of an infinite loop. It's still a bug, but it's a lot easier to track down. Jul 25, 2011 at 23:47
  • If you say so. I always disabled overflow checking in Delphi; after getting endlessly bombarded with false positives from things like hash codes and checksums, I just gave up on that "feature" entirely. But I suppose you're right, it would have caught that specific error.
    – Aaronaught
    Jul 25, 2011 at 23:53
  • @Aaronaught: Well yeah, you'd want to disable it for stuff like hash codes and checksums that are specifically designed to overflow and wrap around. But for general-purpose calculations that are not designed to overflow and wrap around, it's an important safety feature and turning it off is kinda like driving without a seat belt. Jul 26, 2011 at 0:12
  • Perhaps you've forgotten, but the overflow checking and compiler directives were incredibly buggy in older versions of Delphi. I can vividly remember tearing my hair out on multiple occasions after seeing the debugger stop directly in the middle of a {$O-}/{$O+} block to cheerfully report an overflow. After a while I couldn't take it anymore and just disabled it globally. Again, yeah, it would have caught this issue, but I still don't think it's worth the number of false positives. To each his own, of course!
    – Aaronaught
    Jul 29, 2011 at 2:41
3

To be honest I tend to use Integers by habit. I got used to fact that they offer ranges big enough for most situations and allow negative values (such as -1). Indeed, a lot of times using bytes/word/shortint would be more appropriate. Now thinking about it I can focus on these spots:

  • Perspective. Tilemap size is limited to 192x192 tiles, so I could use byte for addressing tiles and loops. But if map size to be increased I will have to go through every use and replace it with e.g. word. When I need to allow off-map objects I would have to go again to change to smallint.

  • Loops. It is often I write a loop "from i:=0 to Count-1", what happens if "i" is byte and Count=0 is that loop runs from 0 to 255. Not that I would want it.

  • Uniforming. It's easier to remember and to apply "var i:integer;" than to stop in each case and think "Hm.. here we are dealing with 0..120 range.. byte.. no, wait, we might need -1 for uninitialized.. shortint.. wait.. what if 128 is not enough.. Arrgh!" or "Why is it smallint in this place, not a shortint?"

  • Combining. When I need to combine two or more classes together they might be using different data types for their purposes, using broader types allows to skip unnecessary conversions.

  • -1. Even when values are on 0..n-1 range I often need to set "no value/unknown/uninitialized/empty" value, which is by common practice -1.

Using Integers allows to skip all these issues, forget about low-level optimization where it is not needed, go higher level and focus on more real problems.

P.S. When do I use other types?

  • Counters, they are never negative and are read-only outside of their class.
  • Performance/Memory reasons, force to use shorter data types in certain places.
1

The best practice is to use a data type that fits the needs for the data being used (expected data).

C# examples: If I only needed to support 0 to 255, I would use a byte.

If I needed to support 1,000,000 negative and positive, then int.

Bigger than 4.2 billion, then use a long.

By choosing the correct type, the program will use the optimal amount of memory as well as different types use different amounts of memory.

Here is a C# int reference from MSDN.

int 
 -2,147,483,648 to 2,147,483,647
 Signed 32-bit integer

uint 
 0 to 4,294,967,295
 Unsigned 32-bit integer

long 
 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
 Signed 64-bit integer

ulong 
 0 to 18,446,744,073,709,551,615
 Unsigned 64-bit integer
5
  • In C# (or .net in general) would long and ulong become 128 bits on a 128-bit machine? Because in Delphi, the Integer datatype is 32 bits on a 32-bit machine and apparently will be 64 bits on a 64-bit machine. Jul 25, 2011 at 21:01
  • 1
    @Peter Turner: No, in C# int is just a shorthand for System.Int32, no matter what machine the code runs on.
    – nikie
    Jul 25, 2011 at 21:07
  • @nikie, is it just like type int System.Int32 or something to that effect? Could it be changed that easily in a future version of the framework? Jul 25, 2011 at 21:12
  • @Peter Turner/nikie (sizeof(int).ToString()); ==> Returns 4 (sizeof(Int64).ToString()); ==> Returns 8 On my 64bit Windows OS. As nikie, stats, an int is really just and Int32.
    – Jon Raynor
    Jul 25, 2011 at 21:14
  • 1
    One thing to note there is that not all types are compliant with the Common Language Specification. uint is one of such non-compliant types which means it should not be used in publicly exposed API to avoid breaking the ability to use that API in .NET languages other than the one the library is written in. This is also why the .NET framework API itself is using int where uint would do.
    – Adam Lear
    Jul 26, 2011 at 1:04
1

Unsigned integer types should only be used to represent cardinal numbers in languages where they represent cardinal numbers. Because of the way the computers that ran C happened to work, unsigned integer types behaved as members of mod-2^n algebraic rings (meaning calculations that overflowed would "wrap" predictably), and the language specifies that in many cases such types are required to behave as abstract algebraic rings even when such behavior would be inconsistent with the behavior of cardinal numbers or mathematical integers.

If a platform fully supported separate types for cardinal numbers and algebraic rings, then I would suggest that cardinal numbers should be processed using cardinal-number types (and things that need to wrap using the ring types). Not only could such types store numbers twice the size of signed types, but a method receiving a parameter of such a type wouldn't have to check whether it was negative.

Given the relative lack of cardinal-number types, however, it's generally best to simply use integers to represent both mathematical integers and cardinal numbers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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