Simple Types (Delphi)
Contents
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 |
---|---|---|---|
Ordinal expression |
Ordinality of expression's value |
Does not take Int64 arguments. | |
Ordinal expression |
Predecessor of expression's value |
||
Ordinal expression |
Successor of expression's value |
||
Ordinal type identifier or variable of ordinal type |
Highest value in type |
Also operates on short-string types and arrays. | |
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 |
---|---|---|---|---|
32-bit platforms |
|
Signed 32-bit |
||
64-bit platforms |
|
Signed 64-bit |
||
32-bit platforms |
|
Unsigned 32-bit |
||
64-bit platforms |
|
Unsigned 64-bit | UInt64 | |
32-bit platforms and 64-bit Windows platforms |
|
Signed 32-bit |
||
64-bit POSIX platforms include iOS and Linux |
|
Signed 64-bit | Int64 | |
32-bit platforms and 64-bit Windows platforms |
|
Unsigned 32-bit |
||
64-bit POSIX platforms include iOS and Linux |
|
Unsigned 64-bit |
UInt64 |
- Note: 32-bit platforms include 32-bit Windows, 32-bit macOS, 32-bit iOS, iOS Simulator and 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 |
---|---|---|---|
|
Signed 8-bit |
||
|
Signed 16-bit |
||
|
Signed 32-bit |
||
|
Signed 32-bit |
||
|
Signed 64-bit |
||
|
Unsigned 8-bit |
||
|
Unsigned 16-bit |
||
|
Unsigned 32-bit |
||
|
Unsigned 32-bit |
||
|
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).
Note: The AnsiChar type is not supported by the Delphi mobile compilers, but is used by the Delphi desktop compilers. For more information, see Migrating Delphi Code to Mobile from Desktop.
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 |
---|---|
|
|
|
|
|
|
|
|
|
|
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 | 32bit Intel Windows | 3.37e-4932 .. 1.18e+4932
|
10-20 | 10 |
64-bit Intel Linux 32-bit Intel macOS 32-bit Intel iOS Simulator |
3.37e-4932 .. 1.18e+4932
|
10-20 | 16 | |
other platforms | 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 |
10-20 | 8 |
The following remarks apply to 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.
- 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 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 64-bit 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
- Delphi Data Types for API Integration
- About Data Types (Delphi)
- String Types (Delphi)
- Structured Types (Delphi)
- Pointers and Pointer Types (Delphi)
- Procedural Types (Delphi)
- Variant Types (Delphi)
- Type Compatibility and Identity (Delphi)
- Data Types, Variables, and Constants Index (Delphi)
- Variables (Delphi)
- Declared Constants
- Internal Data Formats (Delphi)
- 64-bit Windows Data Types Compared to 32-bit Windows Data Types
- About Floating-Point Arithmetic
- Floating-Point Comparison Routines
- Floating-Point Rounding Routines
- Floating-Point Number Control Routines
- Delphi Intrinsic Routines