Simple Types (Delphi)

From RAD Studio
Jump to: navigation, search

Go Up to Data Types, Variables, and Constants Index

Simple types - which include ordinal types and real types - define ordered sets of values.

Ordinal Types

Ordinal types include integer, character, Boolean, enumerated, and subrange types. An ordinal type defines an ordered set of values in which each value except the first has a unique predecessor and each value except the last has a unique successor. Further, each value has an ordinality, which determines the ordering of the type. In most cases, if a value has ordinality n, its predecessor has ordinality n-1 and its successor has ordinality n+1.

For integer types, the ordinality of a value is the value itself. Subrange types maintain the ordinalities of their base types. For other ordinal types, by default the first value has ordinality 0, the next value has ordinality 1, and so forth. The declaration of an enumerated type can explicitly override this default.

Several predefined functions operate on ordinal values and type identifiers. The most important of them are summarized below.

Function Parameter Return value Remarks

Ord

Ordinal expression

Ordinality of expression's value

Does not take Int64 arguments.

Pred

Ordinal expression

Predecessor of expression's value

Succ

Ordinal expression

Successor of expression's value

High

Ordinal type identifier or variable of ordinal type

Highest value in type

Also operates on short-string types and arrays.

Low

Ordinal type identifier or variable of ordinal type

Lowest value in type

Also operates on short-string types and arrays.


For example, High(Byte) returns 255 because the highest value of type Byte is 255, and Succ(2) returns 3 because 3 is the successor of 2.

The standard procedures Inc and Dec increment and decrement the value of an ordinal variable. For example, Inc(I) is equivalent to I := Succ(I) and, if I is an integer variable, to I := I + 1.

Integer Types

An integer type represents a subset of the integral numbers.

Integer types can be platform-dependent and platform-independent.

Platform-Dependent Integer Types

The platform-dependent integer types are transformed to fit the bit size of the current compiler platform. The platform-dependent integer types are NativeInt, NativeUInt, LongInt, and LongWord. Using these types whenever possible, since they result in the best performance for the underlying CPU and operating system, is desirable. The following table illustrates their ranges and storage formats for the Delphi compiler.

Platform-dependent integer types

Type Platform Range Format Alias

NativeInt

32-bit platforms

-2147483648..2147483647
(-231..2^31-1)

Signed 32-bit

Integer

64-bit platforms

-9223372036854775808..9223372036854775807
(-263..263-1)

Signed 64-bit

Int64

NativeUInt

32-bit platforms

0..4294967295
(0..232-1)

Unsigned 32-bit

Cardinal

64-bit platforms

0..18446744073709551615
(0..264-1)

Unsigned 64-bit UInt64

LongInt

32-bit platforms and 64-bit Windows platforms

-2147483648..2147483647
(-231..231-1)

Signed 32-bit

Integer

64-bit POSIX platforms including iOS, macOS, Android, and Linux

-9223372036854775808..9223372036854775807
(-263..263-1)

Signed 64-bit Int64

LongWord

32-bit platforms and 64-bit Windows platforms

0..4294967295
(0..232-1)

Unsigned 32-bit

Cardinal

64-bit POSIX platforms including iOS, macOS, Android, and Linux

0..18446744073709551615
(0..264-1)

Unsigned 64-bit

UInt64
Note: 32-bit platforms include 32-bit Windows and 32-bit Android.

Platform-Independent Integer Types

Platform-independent integer types always have the same size, regardless of what platform you use. Platform-independent integer types include ShortInt, SmallInt, LongInt, Integer, Int64, Byte, Word, LongWord, Cardinal, and UInt64.

Platform-independent integer types

Type Range Format Alias

ShortInt

-128..127

Signed 8-bit

Int8

SmallInt

-32768..32767

Signed 16-bit

Int16

FixedInt

-2147483648..2147483647

Signed 32-bit

Int32

Integer

-2147483648..2147483647

Signed 32-bit

Int32

Int64

-9223372036854775808..9223372036854775807
(-263..263-1)

Signed 64-bit

Byte

0..255

Unsigned 8-bit

UInt8

Word

0..65535

Unsigned 16-bit

UInt16

FixedUInt

0..4294967295

Unsigned 32-bit

UInt32

Cardinal

0..4294967295

Unsigned 32-bit

UInt32

UInt64

0..18446744073709551615
(0..264-1)

Unsigned 64-bit


In general, arithmetic operations on integers return a value of type Integer, which is equivalent to the 32-bit LongInt. Operations return a value of type Int64 only when performed on one or more Int64 operands. Therefore, the following code produces incorrect results:

var
  I: Integer;
  J: Int64;
... 
  I := High(Integer);
  J := I + 1;

To get an Int64 return value in this situation, cast I as Int64:

...
  J := Int64(I) + 1;

For more information, see Arithmetic Operators.

Note: Some standard routines that take integer arguments truncate Int64 values to 32 bits. However, the High, Low, Succ, Pred, Inc, Dec, IntToStr, and IntToHex routines fully support Int64 arguments. Also, the Round, Trunc, StrToInt64, and StrToInt64Def functions return Int64 values. A few routines cannot take Int64 values at all.

When you increment the last value or decrement the first value of an integer type, the result wraps around the beginning or end of the range. For example, the ShortInt type has the range -128..127; hence, after execution of the code:

var
  I: Shortint;
...
  I := High(Shortint);
  I := I + 1;

the value of I is -128. If compiler range-checking is enabled, however, this code generates a runtime error.

Character Types

The character types are Char, AnsiChar, WideChar, UCS2Char, and UCS4Char:

  • Char in the current implementation is equivalent to WideChar, since now the default string type is UnicodeString. Because the implementation of Char can change in future releases, it is a good idea to use the standard function SizeOf rather than a hard-coded constant when writing programs that may need to handle characters of different sizes.
  • AnsiChar values are byte-sized (8-bit) characters ordered according to the locale character set, which is possibly multibyte.
  • WideChar characters use more than one byte to represent every character. In the current implementations, WideChar is word-sized (16-bit) characters ordered according to the Unicode character set (note that it could be longer in future implementations). The first 256 Unicode characters correspond to the ANSI characters.
  • UCS2Char is an alias for WideChar.
  • UCS4Char is used for working with 4–byte Unicode characters.

A string constant of length 1, such as 'A', can denote a character value. The predefined function Chr returns the character value for any integer in the range of WideChar; for example, Chr(65) returns the letter A.

AnsiChar and WideChar values, like integers, wrap around when decremented or incremented past the beginning or end of their range (unless range-checking is enabled). For example, after execution of the code:

var 
  Letter: AnsiChar;
  I: Integer;
begin
  Letter := High(Letter);
  for I := 1 to 66 do Inc(Letter);
end;

Letter has the value A (ASCII 65).

Boolean Types

The 4 predefined Boolean types are Boolean, ByteBool, WordBool, and LongBool. Boolean is the preferred type. The others exist to provide compatibility with other languages and operating system libraries.

A Boolean variable occupies one byte of memory, a ByteBool variable also occupies one byte, a WordBool variable occupies 2 bytes (one word), and a LongBool variable occupies 4 bytes (2 words).

Boolean values are denoted by the predefined constants True and False. The following relationships hold:

Boolean ByteBool, WordBool, LongBool

False < True

False <> True

Ord(False) = 0

Ord(False) = 0

Ord(True) = 1

Ord(True) <> 0

Succ(False) = True

Succ(False) = True

Pred(True) = False

Pred(False) = True


A value of type ByteBool, LongBool, or WordBool is considered True when its ordinality is nonzero. If such a value appears in a context where a Boolean is expected, the compiler automatically converts any value of nonzero ordinality to True.

The previous remarks refer to the ordinality of Boolean values, not to the values themselves. In Delphi, Boolean expressions cannot be equated with integers or reals. Hence, if X is an integer variable, the statement:

if X then ...;

generates a compilation error. Casting the variable to a Boolean type is unreliable, but each of the following alternatives will work.

 if X <> 0 then ...;    { use an expression that returns a Boolean value }
  ...
 var OK: Boolean;       { use a Boolean variable }
   ... 
 if X <> 0 then
   OK := True;
 if OK then ...;

Enumerated Types

An enumerated type defines an ordered set of values by simply listing identifiers that denote these values. The values have no inherent meaning. To declare an enumerated type, use the syntax:

 type typeName = (val1, ...,valn)

where typeName and each val are valid identifiers. For example, the declaration:

type Suit = (Club, Diamond, Heart, Spade);

defines an enumerated type called Suit, whose possible values are Club, Diamond, Heart, and Spade, where Ord(Club) returns 0, Ord(Diamond) returns 1, and so on.

When you declare an enumerated type, you are declaring each val to be a constant of type typeName. If the val identifiers are used for another purpose within the same scope, naming conflicts occur. For example, suppose you declare the type:

type TSound = (Click, Clack, Clock)

Unfortunately, Click is also the name of a method defined for TControl and all of the objects in VCL that descend from it. So if you are writing an application and you create an event handler like:

 procedure TForm1.DBGridEnter(Sender: TObject);
 var
   Thing: TSound;
 begin 
    ... 
   Thing := Click;
 end;

you will get a compilation error; the compiler interprets Click within the scope of the procedure as a reference to a Click method of a TForm. You can work around this by qualifying the identifier; thus, if TSound is declared in MyUnit, you would use:

Thing := MyUnit.Click;

A better solution, however, is to choose constant names that are not likely to conflict with other identifiers. Examples:

type
  TSound = (tsClick, tsClack, tsClock);
  TMyColor = (mcRed, mcBlue, mcGreen, mcYellow, mcOrange);
  Answer = (ansYes, ansNo, ansMaybe)

You can use the (val1, ..., valn) construction directly in variable declarations, as if it were a type name:

var MyCard: (Club, Diamond, Heart, Spade);

But if you declare MyCard this way, you cannot declare another variable within the same scope using these constant identifiers. Thus:

 var Card1: (Club, Diamond, Heart, Spade);
 var Card2: (Club, Diamond, Heart, Spade);

generates a compilation error. But:

var Card1, Card2: (Club, Diamond, Heart, Spade);

compiles cleanly, as does:

type 
  Suit = (Club, Diamond, Heart, Spade); 
var 
  Card1: Suit;
  Card2: Suit;

Enumerated Types with Explicitly Assigned Ordinality

By default, the ordinalities of enumerated values start from 0 and follow the sequence in which their identifiers are listed in the type declaration. You can override this by explicitly assigning ordinalities to some or all of the values in the declaration. To assign an ordinality to a value, follow its identifier with = constantExpression, where constantExpression is a constant expression that evaluates to an integer. For example:

type Size = (Small = 5, Medium = 10, Large = Small + Medium);

defines a type called Size whose possible values include Small, Medium, and Large, where Ord(Small) returns 5, Ord(Medium) returns 10, and Ord(Large) returns 15.

An enumerated type is, in effect, a subrange whose lowest and highest values correspond to the lowest and highest ordinalities of the constants in the declaration. In the previous example, the Size type has 11 possible values whose ordinalities range from 5 to 15. (Hence the type array[Size] of Char represents an array of 11 characters.) Only three of these values have names, but the others are accessible through typecasts and through routines such as Pred, Succ, Inc, and Dec. In the following example, "anonymous" values in the range of Size are assigned to the variable X.

var 
  X: Size; 
begin
  X := Small;   // Ord(X) = 5 
  X := Size(6); // Ord(X) = 6 
  Inc(X);       // Ord(X) = 7

Any value that is not explicitly assigned an ordinality has the ordinality one greater than that of the previous value in the list. If the first value is not assigned an ordinality, its ordinality is 0. Hence, given the declaration:

type SomeEnum = (e1, e2, e3 = 1);

SomeEnum has only two possible values: Ord(e1) returns 0, Ord(e2) returns 1, and Ord(e3) also returns 1; because e2 and e3 have the same ordinality, they represent the same value.

Enumerated constants without a specific value have RTTI:

type SomeEnum = (e1, e2, e3);

whereas enumerated constants with a specific value, such as the following, do not have RTTI:

type SomeEnum = (e1 = 1, e2 = 2, e3 = 3);

Scoped Enumerations

You can use scoped enumerations in Delphi code if you enable the {$SCOPEDENUMS ON} compiler directive.

The {$SCOPEDENUMS ON or OFF} compiler directive enables or disables the use of scoped enumerations in Delphi code. {$SCOPEDENUMS ON} defines that enumerations are scoped. {$SCOPEDENUMS ON} affects declarations of enumeration types until the nearest {$SCOPEDENUMS OFF} directive. The identifiers of the enumeration introduced in enumeration types declared after the {$SCOPEDENUMS ON} directive are not added to the global scope. To use a scoped enumeration identifier, you should qualify it with the name of the enumeration type introducing this identifier.

For instance, let us define the following unit in the Unit1.pas file

unit Unit1;
interface
// {$SCOPEDENUMS ON} // clear comment from this directive
 type
   TMyEnum = (First, Second, Third);
implementation
 
end.

and the following program using this unit

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils, Unit1 in 'Unit1.pas';
 
var
  // First: Integer;  // clear comment from this variable
  Value: TMyEnum;
begin
  try
    Value := First;   
//  Value := TMyEnum.First; 
//  Value := unit1.First; 
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

Now we can investigate effects of the {$SCOPEDENUMS} compiler directive on the scopes in which the First, Second, and Third identifiers, defined in the TMyEnum enumeration, are visible.

First, Run (F9) on this code. The code runs successfully. This means that the First identifier, used in the

Value := First;

variable, is the global scope identifier introduced in the

TMyEnum = (First, Second, Third);

enumeration type.

Now clear comment from the

{$SCOPEDENUMS ON}

compiler directive in the unit1 unit. This directive enforces the TMyEnum enumeration to be scoped. Execute Run. The E2003 Undeclared identifier 'First' error is generated on the

Value := First;

line. It informs that the {$SCOPEDENUMS ON} compiler directive prevents the First identifier, introduced in the scoped TMyEnum enumeration, to be added to the global scope.

To use identifiers introduced in scoped enumerations, prefix a reference to an enumeration's element with its type name. For example, clear comment in the second

Value := TMyEnum.First;

version of the Value variable (and comment the first version of Value). Execute Run. The program runs successfully. This means that the First identifier is known in the TMyEnum scope.

Now comment the

// {$SCOPEDENUMS ON}

compiler directive in unit1. Then clear comment from the declaration of the First variable

First: Integer; 

and again use the

Value := First;

variable. Now the code in the program Project1 looks like this:

var
  First: Integer;   
  Value: TMyEnum;
begin
  try
    Value := First;

Execute Run. The

 First: Integer;   

line causes the E2010 Incompatible types - 'TMyEnum' and 'Integer' error. This means that the naming conflict occurs between the global scope First identifier introduced in the TMyEnum enumeration and the First variable. You can work around this conflict by qualifying the First identifier with the unit1 unit in which it is defined. For this, comment again the first version of Value variable and clear comment from the third one:

Value := unit1.First;

Execute Run. The program runs successfully. That is, now the First identifier can be qualified with the unit1 unit scope. But what happens if we again enable the

{$SCOPEDENUMS ON}

compiler directive in unit1. The compiler generates the E2003 Undeclared identifier 'First' error on the

Value := unit1.First;

line. This means that {$SCOPEDENUMS ON} prevents adding the First enumeration's identifier in the unit1 scope. Now the First identifier is added only in the TMyEnum enumeration's scope. To check this, let us again use the

Value := TMyEnum.First;

version of the Value variable. Execute Run and the code succeeds.

Subrange Types

A subrange type represents a subset of the values in another ordinal type (called the base type). Any construction of the form Low..High, where Low and High are constant expressions of the same ordinal type and Low is less than High, identifies a subrange type that includes all values between Low and High. For example, if you declare the enumerated type:

type 
  TColors = (Red, Blue, Green, Yellow, Orange, Purple, White, Black);

you can then define a subrange type like:

type 
  TMyColors = Green..White;

Here TMyColors includes the values Green, Yellow, Orange, Purple, and White.

You can use numeric constants and characters (string constants of length 1) to define subrange types:

type 
 SomeNumbers = -128..127;
 Caps = 'A'..'Z';

When you use numeric or character constants to define a subrange, the base type is the smallest integer or character type that contains the specified range.

The LowerBound..UpperBound construction itself functions as a type name, so you can use it directly in variable declarations. For example:

var SomeNum: 1..500;

declares an integer variable whose value can be anywhere in the range from 1 through 500.

The ordinality of each value in a subrange is preserved from the base type. (In the first example, if Color is a variable that holds the value Green, Ord(Color) returns 2 regardless of whether Color is of type TColors or TMyColors.) Values do not wrap around the beginning or end of a subrange, even if the base is an integer or character type; incrementing or decrementing past the boundary of a subrange simply converts the value to the base type. Hence, while:

 type Percentile = 0..99;
 var I: Percentile;
   ... 
   I := 100;

produces an error, the following code:

 ...
 I := 99;
 Inc(I);

assigns the value 100 to I (unless compiler range-checking is enabled).

The use of constant expressions in subrange definitions introduces a syntactic difficulty. In any type declaration, when the first meaningful character after = is a left parenthesis, the compiler assumes that an enumerated type is being defined. Hence the code:

 const X = 50; Y = 10;
 type Scale = (X - Y) * 2..(X + Y) * 2;

produces an error. Work around this problem by rewriting the type declaration to avoid the leading parenthesis:

 type Scale = 2 * (X - Y)..(X + Y) * 2;

Real Types

A real type defines a set of numbers that can be represented with the floating-point notation. The table below gives the ranges and storage formats for the real types on 64-bit and 32-bit platforms.

Real types

Type Platform Approximate Positive Range Significant decimal digits Size in bytes
Real48 all 2.94e-39 .. 1.70e+38 11-12 6
Single all 1.18e-38 .. 3.40e+38 7-8 4
Double all 2.23e-308 .. 1.79e+308 15-16 8
Real all 2.23e-308 .. 1.79e+308 15-16 8
Extended 32-bit Intel Windows 3.37e-4932 .. 1.18e+4932 10-20 10
64-bit Intel Linux
64-bit Intel macOS
3.37e-4932 .. 1.18e+4932 10-20 16
64-bit Intel Windows

ARM platforms (32-bit Android, 64-bit Android, 64-bit iOS, 64-bit macOS)

2.23e-308 .. 1.79e+308 15-16 8
Comp all -9223372036854775808.. 9223372036854775807
(-263.. 263-1)
10-20 8
Currency all -922337203685477.5808.. 922337203685477.5807
(-(263+1)/10000.. 263/10000)
10-20 8


The following remarks apply to real types:

  • Real is equivalent to Double, in the current implementation.
  • Real48 is maintained for backward compatibility. Since its storage format is not native to the Intel processor architecture, it results in slower performance than other floating-point types.
The 6-byte Real48 type was called Real in earlier versions of Object Pascal. If you are recompiling code that uses the older, 6-byte Real type in Delphi, you may want to change it to Real48. You can also use the {$REALCOMPATIBILITY ON} compiler directive to turn Real back into the 6-byte type.
  • Extended offers greater precision on 32-bit platforms than other real types.
On 64-bit Windows and all ARM platforms Extended is an alias for a Double; that is, the size of the Extended data type is 8 bytes. Thus you have less precision using an Extended on these platforms compared to 32-bit platforms, where Extended is 10 bytes. Therefore, if your applications use the Extended data type and you rely on precision for floating-point operations, this size difference might affect your data. Be careful using Extended if you are creating data files to share across platforms. For more information, see The Extended Data Type Is 2 Bytes Smaller on 64-bit Windows Systems.
  • The Comp (computational) type is native to the Intel processor architecture and represents a 64-bit integer. It is classified as a real, however, because it does not behave like an ordinal type. (For example, you cannot increment or decrement a Comp value.) Comp is maintained for backward compatibility only. Use the Int64 type for better performance.
  • Currency is a fixed-point data type that minimizes rounding errors in monetary calculations. It is stored as a scaled 64-bit integer with the 4 least significant digits implicitly representing decimal places. When mixed with other real types in assignments and expressions, Currency values are automatically divided or multiplied by 10000.

See Also