# Simple Types (Delphi)

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

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

Signed 32-bit

64-bit platforms

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

Signed 64-bit

32-bit platforms

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

Unsigned 32-bit

64-bit platforms

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

Unsigned 64-bit UInt64
32-bit platforms and 64-bit Windows platforms

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

Signed 32-bit

64-bit POSIX platforms include iOS and Linux

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

Signed 64-bit Int64
32-bit platforms and 64-bit Windows platforms

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

Unsigned 32-bit

64-bit POSIX platforms include iOS and Linux

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

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

`-128..127`

Signed 8-bit

`-32768..32767`

Signed 16-bit

`-2147483648..2147483647`

Signed 32-bit

`-2147483648..2147483647`

Signed 32-bit

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

Signed 64-bit

`0..255`

Unsigned 8-bit

`0..65535`

Unsigned 16-bit

`0..4294967295`

Unsigned 32-bit

`0..4294967295`

Unsigned 32-bit

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

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);
```

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
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`
`(-(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 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.