RAD Studio (Common)
ContentsIndex
PreviousUpNext
Simple Types

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

The ordinal types covered in this topic are:

  • Integer types
  • Character types
  • Boolean types
  • Enumerated types
  • Real (floating point) 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 whole numbers. The generic integer types are Integer and Cardinal; use these whenever possible, since they result in the best performance for the underlying CPU and operating system. The table below gives their ranges and storage formats for the Delphi compiler.

Generic Integer Types

 

Generic integer types

Type  
Range  
Format  
Integer  
-2147483648..2147483647  
signed 32-bit  
Cardinal  
0..4294967295  
unsigned 32-bit  

 

Fundamental Integer Types

Fundamental integer types include Shortint, Smallint, Longint, Int64, Byte, Word, Longword, and UInt64.  

Fundamental integer types  

Type  
Range  
Format  
Shortint  
-128..127  
signed 8-bit  
Smallint  
-32768..32767  
signed 16-bit  
Longint  
-2147483648..2147483647  
signed 32-bit  
Int64  
-2^63..2^63-1  
signed 64-bit  
Byte  
0..255  
unsigned 8-bit  
Word  
0..65535  
unsigned 16-bit  
Longword  
0..4294967295  
unsigned 32-bit  
UInt64  
0..2^64–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 operand. Hence 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 fundamental character types are AnsiChar and WideChar. 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. 

The generic character type is Char, which is equivalent to WideChar on Win32. Because the implementation of Char is subject to change, it's 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. 

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 AnsiChar; 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 four 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 two bytes (one word), and a LongBool variable occupies four bytes (two 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 longer expression that returns Boolean value } var OK: Boolean; ... 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 

typetypeName= (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 forth. 

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're writing an application and you create an event handler like

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

you'll get a compilation error; the compiler interprets Click within the scope of the procedure as a reference to TForm's Click method. 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 can't 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; X := Small; // Ord(X) = 5 Y := Size(6); // Ord(X) = 6 Inc(X); // Ord(X) = 7

Any value that isn't explicitly assigned an ordinality has ordinality one greater than that of the previous value in the list. If the first value isn't 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 enums in Delphi code if you enable the $SCOPEDENUMS compiler directive.  

Scoped enumerations are characterized by prefixing a reference to an enumerated type element with its type name. For instance, given the following unit and program, you can see how a scoped enum (TMyEnum) allows the assignment of the variable Value where specifying "First" alone would have triggered a type-mismatch error.

unit Unit1;
    
interface
    
type
  TMyEnum = (first, second, third);
    
    
implementation
    
end.

program Project1;
    
{$APPTYPE CONSOLE}
    
uses
  SysUtils,
  Unit1 in 'Unit1.pas';
    
var
  First: Integer;
  Value: TMyEnum;
    
begin
  try
    Value := TMyEnum.First;
    
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

 

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 to 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,

 ... 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;

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

Fundamental Win32 real types  

Type  
Range  
Significant digits  
Size in bytes  
Real48  
2.9 x 10^–39 .. 1.7 x 10^38  
11-12  
6  
Single  
1.5 x 10^–45 .. 3.4 x 10^38  
7-8  
4  
Double  
5.0 x 10^–324 .. 1.7 x 10^308  
15-16  
8  
Extended  
3.6 x 10^–4951 .. 1.1 x 10^4932  
10-20  
10  
Comp  
-2^63+1 .. 2^63–1  
10-20  
8  
Currency  
-922337203685477.5808.. 922337203685477.5807  
10-20  
8  

The generic type Real, in its current implementation, is equivalent to Double.  

Generic real types  

Type  
Range  
Significant digits  
Size in bytes  
Real  
-5.0 x 10^–324 .. 1.7 x 10^308  
15–16  
8  

Note: The six-byte Real48
type was called Real in earlier versions of Object Pascal. If you are recompiling code that uses the older, six-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 six-byte type. The following remarks apply to fundamental real types.
  • 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.
  • Extended offers greater precision than other real types but is less portable. Be careful using Extended if you are creating data files to share across platforms.
  • 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. On the Win32 platform, it is stored as a scaled 64-bit integer with the four 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.

Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!