Simple types - which include ordinal types and real types - define ordered sets of values.
The ordinal types covered in this topic are:
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.
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.
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
Type |
Range |
Format |
.NET Type Mapping |
Integer |
-2147483648..2147483647 |
signed 32-bit |
Int32 |
Cardinal |
0..4294967295 |
unsigned 32-bit |
UInt32 |
Fundamental integer types include Shortint, Smallint, Longint, Int64, Byte, Word, Longword, and UInt64.
Fundamental integer types
Type |
Range |
Format |
.NET Type Mapping |
Shortint |
-128..127 |
signed 8-bit |
SByte |
Smallint |
-32768..32767 |
signed 16-bit |
Int16 |
Longint |
-2147483648..2147483647 |
signed 32-bit |
Int32 |
Int64 |
-2^63..2^63-1 |
signed 64-bit |
Int64 |
Byte |
0..255 |
unsigned 8-bit |
Byte |
Word |
0..65535 |
unsigned 16-bit |
UInt16 |
Longword |
0..4294967295 |
unsigned 32-bit |
UInt32 |
UInt64 |
0..2^64–1 |
unsigned 64-bit |
UInt64 |
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.
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.
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. AnsiChar was originally modeled after the ANSI character set (thus its name) but has now been broadened to refer to the current locale character set.
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 AnsiChar on Win32, and to Char on the .NET platform. 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.
Character 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: Char; I: Integer; begin Letter := High(Letter); for I := 1 to 66 do Inc(Letter); end;
Letter has the value A (ASCII 65).
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 ...;
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;
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);
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 following table shows how the fundamental real types map to .NET framework types.
Fundamental .NET real type mappings
Type |
.NET Mapping |
Real48 |
Deprecated |
Single |
Single |
Double |
Double |
Extended |
Double |
Comp |
Deprecated |
Currency |
Re-implemented as a value type using the Decimal type from the .NET Framework |
The generic type Real, in its current implementation, is equivalent to Double (which maps to Double on .NET).
Generic real types
Type |
Range |
Significant digits |
Size in bytes |
Real |
-5.0 x 10^–324 .. 1.7 x 10^308 |
15–16 |
8 |
Copyright(C) 2008 CodeGear(TM). All Rights Reserved.
|
What do you think about this topic? Send feedback!
|