- Chapter 3: Basic Pascal Syntax
- Integer Types and Floating-Point Types
- Pascal Strings
- Typecasts
- Arrays
- Records
- Pointers
- What You Won't Find in Object Pascal
- Summary
Integer Types and Floating-Point Types
I assume that readers of this book can quickly come to terms with basic Pascal types. I will say a few words on the basics of the subject and then go on to cover some issues that might trip up experienced programmers new to this language or experienced programmers who need a refresher course on Pascal syntax. This approach will not be helpful for a newcomer to programming, but it should be enough information to get experts up to speed on Object Pascal in short order.
I'll start by talking briefly about integers and floating-point types. After getting that basic material out of the way, I'll discuss strings, pointers, and typecasting.
Here are two very basic definitions:
Integers are whole numbers, such as -1, 0, 1, 2, and 5,000,000.
Floating-point numbers are sometimes known as decimal numbers, such as 7.0, 3.2, -5.004, and 32,000.0000000034.
Ordinal Types
A whole series of types in Pascal are based on whole numbers. These include the Byte, Integer, Char, and Boolean types. All of these types are ordinal types.
Understanding the definition of the ordinal types is helpful for programmers who want to set sail on the good ship Pascal. All but the first and last members of ordinal types have a predecessor and a successor. For instance, an ordinal number such as 1 is followed by 2 and preceded by 0. The same cannot be said of a floating-point type. What is the predecessor to 1.0002? Is it 1.0001? Maybe. But perhaps it is 1.00019—or maybe 1.000199. How about 1.000199999999? Ultimately, there is no clearly defined predecessor to a floating-point number, so it is not an ordinal value.
Whole numbers are ordinal numbers. Simple types such as Char and Boolean are also ordinal numbers. For instance, the letter B is succeeded by the letter C and preceded by the letter A. It makes sense to talk about the successor and predecessor of a Char. The same is true of Boolean values. False, which is equivalent to 0, is succeeded by True, which is usually equivalent to 1. False is the predecessor of True, and True is the successor to False.
NOTE
When I use the word integer in a generic sense, I am talking about the numeric ordinal types such as Byte, LongInt, Integer, or Cardinal. When I talk specifically about Integers, with a capital I, then I mean the Pascal type named Integer. C and Java programmers make a similar distinction between the floating-point types and the type named float.
Two integer types exist in Pascal: generic and fundamental. The generic types, called Integer and Cardinal, will transform themselves to fit the compiler you are currently using. If you use Integers on a 16-bit platform, they will be 16 bits in size. Use them on a 32-bit platform with a 32-bit compiler, and they will be 32 bits in size. Use them on a 64-bit platform with a 64-bit compiler, and they will be 64 bits in size.
Integers are always signed values, which means that they use 1 bit to signal whether they are positive or negative and then use the remaining bits to record a number.
NOTE
Some readers might not be clear on the difference between signed and unsigned types. Consider the Byte and ShortInt types, both of which contain 8 bits. A Byte is unsigned, and a ShortInt is signed. The largest unsigned 8-bit number is 255, while its smallest value is 0. The largest signed 8-bit number is 127, while its smallest value is -128. The issue is simply that you can have 256 possible numbers that can be held in 8 bits. These 256 numbers can range from either -128 to 127, or from 0 to 255. The difference between signed and unsigned types is whether one of the bits is used to designate the plus and minus sign. Unsigned numbers have no plus and minus sign and, therefore, are always positive. Signed numbers range over both positive and negative values.
NOTE
At this time, there is only a 32-bit compiler in Kylix. On the Windows platform, at the time of this writing, there is a 16-bit and a 32-bit Delphi compiler.
In contrast to generic types, fundamental types are always a set size, regardless of what platform you use. For instance, a Byte is an 8-bit, unsigned number, regardless of the platform. The same rule applies to LongInts, which are always 32-bit signed numbers, regardless of the platform.
The generic types are shown in Table 3.1, and the fundamental types appear in Table 3.2.
Table 3.1 The Generic Types Are Ordinal Values with a Range That Changes Depending on the Number of Bits in the Native Type Word for Your Platform
Type |
Range |
Format |
---|---|---|
Integer |
-2147483648 to 2147483647 |
Signed 32-bit |
Cardinal |
0 to 4294967295 |
Unsigned 32-bit |
Table 3.2 The Fundamental Type Stays the Same, Regardless of Platform
Type |
Range |
Format |
---|---|---|
ShortInt |
-128 to 127 |
Signed 8-bit |
SmallInt |
-32768 to 32767 |
Signed 16-bit |
LongInt |
-2147483648 to 2147483647 |
Signed 32-bit |
Int64 |
-2^{63} to 2^{63-1} |
Signed 64-bit |
Byte |
0 to 255 |
Unsigned 8-bit |
Word |
0 to 65535 |
Unsigned 16-bit |
LongWord |
0 to 4294967295 |
Unsigned 32-bit |
Most knowledgeable programmers try to use the generic types whenever possible. They help you port your code to new platforms, and they make your code backward compatible with old platforms. Furthermore, the generic Integer type should always be the fastest numeric type on any platform because it fits exactly the size of a word on that particular processor. Thus, the Integer type will usually be the best choice for producing fast code, even though it is larger than the ShortInt or SmallInt types. On 32-bit platforms, LongInts and Integers are equally fast, but when we move to 64-bit computers, LongInts will no longer be the native type, while Integers will continue in the anointed position. In short, Integers will have 64 bits on a 64-bit platform, 16 bits on a 16-bit platform, and 32 bits on a 32-bit platform.
If you have code that assumes that a particular variable has a certain number of bits in it or will always contain only a certain range of numbers, you should use fundamental types to make sure that your code does not break if you move to another platform. Obviously, most of the code that you write will not be dependant on the number of bits in a variable, but if your code does depend on such a thing, choose your types carefully. For instance, if you are writing a routine that needs to handle numbers larger than 32,767, don't use Integers if you think that the code will ever be run on a 16-bit platform. If you do choose Integers, they will not be large enough to hold the values that you want to use. If you choose LongInts, the type will be large enough, even if ported to a 16-bit platform. (Of course, the odds that you will port your code back to a 16-bit platform are low.)
Pascal Routines for Using Ordinal Numbers
The Integer types, by definition, are ordinal numbers. Ordinal numbers can be manipulated with the following routines: Ord, Pred, Succ, High, and Low. If applied to a Char, the Ord function returns its numeric value. For instance, the Ord of A is 65, and the Ord of a space is 32. In the following example, Num will be set to 66:
var MyChar: Char; Num: Integer; begin MyChar := `B'; Num := Ord(MyChar); end;
The Pred routine returns the predecessor of a number. For instance, the Pred of 1 is 0. The Succ of 1 is 2.
High and Low give you the highest and lowest numbers that you can use with a type. Examples of how to use High and Low are shown in the SimpleTypes program, found on your CD and in Listing 3.1.
Listing 3.1 The SimpleTypes Program
unit Main; interface uses SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls, QExtCtrls; type TForm1 = class(TForm) ListBox1: TListBox; RadioGroup1: TRadioGroup; procedure RadioGroup1Click(Sender: TObject); private procedure DoLongInt; procedure DoInteger; procedure DoCardinal; procedure DoLongWord; procedure DoWord; procedure DoShortInt; procedure DoSmallInt; { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.xfm} type TMethodType = (mtInteger, mtCardinal, mtLongInt, mtLongWord, mtWord, mtShortInt, mtSmallInt); procedure TForm1.DoInteger; var Value: Integer; begin ListBox1.Items.Add(`Integer high value: ` + IntToStr(High(Value))); ListBox1.Items.Add(`Integer low value: ` + IntToStr(Low(Value))); ListBox1.Items.Add(`Size of Integer: ` + IntToStr(SizeOf(Value)) + ` bytes or ` + IntToStr(8 * SizeOf(Value)) + ` bits.'); end; procedure TForm1.DoLongInt; var Value: LongInt; begin ListBox1.Items.Add(`LongInt high value: ` + IntToStr(High(Value))); ListBox1.Items.Add(`LongInt low value: ` + IntToStr(Low(Value))); ListBox1.Items.Add(`Size of LongInt: ` + IntToStr(SizeOf(Value)) + ` bytes or ` + IntToStr(8 * SizeOf(Value)) + ` bits.'); end; procedure TForm1.RadioGroup1Click(Sender: TObject); begin case TMethodType(RadioGroup1.ItemIndex) of mtInteger: DoInteger; mtCardinal: DoCardinal; mtLongInt: DoLongInt; mtLongWord: DoLongWord; mtWord: DoWord; mtShortInt: DoShortInt; mtSmallInt: DoSmallInt; end; end; procedure TForm1.DoCardinal; var Value: Cardinal; begin ListBox1.Items.Add(`Cardinal high value: ` + IntToStr(High(Value))); ListBox1.Items.Add(`Cardinal low value: ` + IntToStr(Low(Value))); ListBox1.Items.Add(`Size of Cardinal: ` + IntToStr(SizeOf(Value)) + ` bytes or ` + IntToStr(8 * SizeOf(Value)) + ` bits.'); end; procedure TForm1.DoLongWord; var Value: LongWord; begin ListBox1.Items.Add(`LongWord high value: ` + IntToStr(High(Value))); ListBox1.Items.Add(`LongWord low value: ` + IntToStr(Low(Value))); ListBox1.Items.Add(`Size of LongWord: ` + IntToStr(SizeOf(Value)) + ` bytes or ` + IntToStr(8 * SizeOf(Value)) + ` bits.'); end; procedure TForm1.DoWord; var Value: Word; begin ListBox1.Items.Add(`Word high value: ` + IntToStr(High(Value))); ListBox1.Items.Add(`Word low value: ` + IntToStr(Low(Value))); ListBox1.Items.Add(`Size of Word: ` + IntToStr(SizeOf(Value)) + ` bytes or ` + IntToStr(8 * SizeOf(Value)) + ` bits.'); end; procedure TForm1.DoShortInt; var Value: ShortInt; begin ListBox1.Items.Add(`ShortInt high value: ` + IntToStr(High(Value))); ListBox1.Items.Add(`ShortInt low value: ` + IntToStr(Low(Value))); ListBox1.Items.Add(`Size of ShortInt: ` + IntToStr(SizeOf(Value)) + ` bytes or ` + IntToStr(8 * SizeOf(Value)) + ` bits.'); end; procedure TForm1.DoSmallInt; var Value: SmallInt; begin ListBox1.Items.Add(`SmallInt high value: ` + IntToStr(High(Value))); ListBox1.Items.Add(`SmallInt low value: ` + IntToStr(Low(Value))); ListBox1.Items.Add(`Size of SmallInt: ` + IntToStr(SizeOf(Value)) + ` bytes or ` + IntToStr(8 * SizeOf(Value)) + ` bits.'); end; end.
This program rather laboriously calls High and Low for all the integer types. Notice that it also uses the SizeOf function, which returns the size in bytes of any variable or type. The point of this program is to show you that you can discover this information at runtime.
You can learn even more about a type using Run Time Type Information (RTTI). An introduction to RTTI appears in the upcoming section "Floating-Point Types."
Enumerated Types
All major languages have enumerated types. This is an ordinal type. In fact, enumerated types are really nothing more than a few numbers starting from 0 and rarely ranging much higher than 10. The interesting thing about these numbers is that you can give them names.
Consider this example:
procedure TForm1.Button1Click(Sender: TObject); type TComputerLanguage = (clC, clCpp, clJava, clPascal, clVB); var ComputerLanguage: TComputerLanguage; begin ComputerLanguage := clPascal end;
Here the values clC, clCpp, clJava, clPascal, and clVB are just fancy ways to write 0, 1, 2, 3, and 4. In short, enumerated types are just a way to associate numbers with names. However, if you want to associate numbers with names, you are always in danger of forgetting which number belongs to which name. For instance, you might want to reference Java and accidentally write 3, when what you meant to write was 2. To avoid confusion, the enumerated type enables you to associate a name with a number. Furthermore, you can enforce that relationship through Pascal's strong type checking. For instance, you can't assign even a valid identifier named clTunaFish to the variable ComputerLanguage unless it is part of the TComputerLanguage type.
NOTE
The letters cl prefacing each name are a Pascal convention. The convention says that you put the letters of the name of the type before the name. So, Computer Language becomes cl.
You can use the Ord routine to convert an enumerated value to a number:
i := Ord(clPascal);
In this case, i is set equal to 3. In fact, you can go from the number to the name, but that is a complex operation involving routines found in the TypInfo unit. The TypInfo unit will be discussed in Chapter 4, and in the next section "Floating-Point Types."
Here is an example by Bob Swart that shows a simple way to write out the name of a type:
program BobEnum; {$APPTYPE CONSOLE} type TEnum = (zero, one, two, three); var E: TEnum; begin E := TEnum(2); // E := two; if E = two then WriteLn(`Two!'); ReadLn end.
Floating-Point Types
Pascal has lots of floating point-types for helping you work with decimal numbers, or fractions of whole numbers. Table 3.3 lists the fundamental types you can choose from.
Table 3.3 The Fundamental Floating-Point Types—the Generic Type, Known as a Real, Is Currently Equivalent to a Double
Type |
Range |
---|---|
Real48 |
2.9 x 10^{-39 }to 1.7 x 10^{38} 11-12 6 |
Single |
1.5 x 10^{-45} to 3.4 x 10^{38} 7-8 4 |
Double |
5.0 x 10^{-324} to 1.7 x 10^{308} 15-16 8 |
Extended |
3.6 x 10^{-4951} to 1.1 x 10^{4932} 19-20 10 |
Comp |
-2^{63+1} to 2^63 -1 19-20 8 |
Currency |
-922337203685477.5808 to 922337203685477.5807 19-20 8 |
The most commonly used type is the Double. However, there is a generic floating point type known as a Real. It is currently the same as a Double, much as an Integer is currently the same as a LongInt. If you declare your floating-point values to be Reals, they can be automatically converted to any new optimal floating-point type that might come along.
The Comp type is the floating-point type that is used the least frequently. In fact, it isn't really meant to represent floating-point numbers. In the bad old days of 16-bit computing, this used to be the best way to work with very large whole numbers. Now it has no function other than to support old code. If you need to work with really large Integer types, then you should now use the Int64 type.
NOTE
Back in the aforementioned bad old days, Pascal used to have a set of routines for working with a type of unique floating-point type known as a Real. (This is not the same thing as the current Real type, but it's a strange 48-bit beast that was custom-made by optimization-obsessed Borland programmers. Back in this time, the Real type and its associated code were considered to be very fast. However, those days are now little more than a memory. Modern operating systems and modern processors now have built-in routines that are superior to the once-ground-breaking code that supported the 48-bit Real type.
The current Real is the same as a Double. However, a type known as a Real48 is compatible with the old Real type used long ago, when Windows was a failed project that provided fodder for jokes and everyone believed that Apple might end up ruling the computer desktop. If you are an old Pascal programmer who has some code dependant on the implementation of the old Pascal Real type, then use Real48.
Remember that Real types, which are synonymous with the Double type, are now back in fashion. I'm having trouble adopting to this new state of affairs because I'm used to thinking of Reals as being out-of-date. So, you will find a lot of Doubles in my code, but I am trying to make the move to using Reals instead. I never use Real48s because I have no code dependant on them.
The TBcd and Floating-Point Accuracy
We now broach the treacherous topic of floating-point accuracy. This is a sea in which no ship is safe, and only caution can protect us from the reefs.
All experienced programmers know that floating-point types such as Doubles and Singles lose precision nearly every time they are part of a calculation. This loss of precision is usually not a problem in a standard math or graphics program, but it can be a serious issue in financial calculations. As a result, you should consider using the Currency type to avoid rounding errors. However, this is not a perfect solution.
The best way to avoid problems with rounding errors is to use the TBcd type, found in the FMTBcd unit. BCD stands for "binary coded decimal," and it is a widely used technology to avoid rounding errors when working with floating-point numbers. Borland did not invent the BCD technology any more than it invented the idea of the floating-point type. It's just a technology that it employs in this product.
Here is what the TBcd type looks like:
PBcd = ^TBcd; TBcd = packed record Precision: Byte; { 1..64 } SignSpecialPlaces: Byte; { Sign:1, Special:1, Places:6 } Fraction: packed array [0..31] of Byte; { BCD Nibbles, 00..99 per Byte, high Nibble 1st } end;
Each of the numbers in your floating-point type is stored in a nibble, which is 4 bits in size. You can specify the number of digits in your number in the Precision field, and you can specify the number of places after the decimal in the SignSpecialPlaces field. In practice, you rarely end up working so directly with this type. Instead, you can use a series of routines to make working with the TBcd type a more palatable exercise.
Kylix provides a large number of routines in the FMTBcd unit for manipulating BCD values. A large sampling of these routines is found in Listing 3.2. You should find the time to open the unit itself and examine it as well.
NOTE
In the Kylix editor, if you put your cursor over any unit in your uses clause and then press Ctrl+Enter, the source for that unit should open in your editor.
Listing 3.2 Routines in FMTBcd That You Can Use to Help You Work with the BCD Type
procedure VarFMTBcdCreate(var ADest: Variant; const ABcd: TBcd); overload; function VarFMTBcdCreate: Variant; overload; function VarFMTBcdCreate(const AValue: string; Precision, Scale: Word): Variant; overload; function VarFMTBcdCreate(const AValue: Double; Precision: Word = 18; Scale: Word = 4): Variant; overload; function VarFMTBcdCreate(const ABcd: TBcd): Variant; overload; function VarIsFMTBcd(const AValue: Variant): Boolean; overload; function VarFMTBcd: TVarType; // convert String/Double/Integer to BCD struct function StrToBcd(const AValue: string): TBcd; function TryStrToBcd(const AValue: string; var Bcd: TBcd): Boolean; function DoubleToBcd(const AValue: Double): TBcd; overload; procedure DoubleToBcd(const AValue: Double; var bcd: TBcd); overload; function IntegerToBcd(const AValue: Integer): TBcd; function VarToBcd(const AValue: Variant): TBcd; function CurrToBCD(const Curr: Currency; var BCD: TBcd; Precision: Integer = 32; Decimals: Integer = 4): Boolean; // Convert Bcd struct to string/Double/Integer function BcdToStr(const Bcd: TBcd): string; overload; function BcdToDouble(const Bcd: TBcd): Double; function BcdToInteger(const Bcd: TBcd; Truncate: Boolean = False): Integer; function BCDToCurr(const BCD: TBcd; var Curr: Currency): Boolean; // Formatting Bcd as string function BcdToStrF(const Bcd: TBcd; Format: TFloatFormat; const Precision, Digits: Integer): string; function FormatBcd(const Format: string; Bcd: TBcd): string; function BcdCompare(const bcd1, bcd2: TBcd): Integer;
Most of these routines are encapsulations of technologies for converting back and forth from TBcd values to most major types. For instance, BcdToStr converts a TBcd value to a string, and StrToBcd performs the opposite task. However, there are more routines than the ones I show you here. Perhaps the best way to get up to speed is to simply look at the BCDVariant program, found in Listing 3.3.
Listing 3.3 The BCDVariant Program Gives You a Number of Examples on How to Use the BCD Type
unit Main; interface uses SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; Button3: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure SimpleMathButtonClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses FMTBcd; {$R *.xfm} procedure TForm1.Button1Click(Sender: TObject); var B: TBcd; V: Variant; begin V := VarFMTBcdCreate(36383.530534346, 32, 9); ListBox1.Items.Add(BCDToStr(VarToBCD(V))); end; procedure TForm1.Button2Click(Sender: TObject); var B: TBcd; D: Double; V: Variant; S: String; begin D := 32.346; V := VarFMTBcdCreate(D, 18, 3); B := VarToBcd(V); S := BcdToStr(B); ListBox1.Items.Add(S); end; procedure TForm1.Button3Click(Sender: TObject); var C: Currency; D: Double; B: TBcd; V: Variant; S: String; begin C := 33334.43; CurrToBCD(C, B, 32, 4); D := BcdToDouble(B); S := Format(`%m', [D]); ListBox1.Items.Add(S); end; procedure TForm1.SimpleMathButtonClick(Sender: TObject); var V1, V2, V3: Variant; B1, B2, B3: TBcd; S1, S2: String; begin V1 := VarFMTBcdCreate(3.5011, 32, 12); V2 := VarFMTBcdCreate(3.5020, 32, 12); V3 := V1 + V2; ListBox1.Items.Add(BcdToStr(VarToBcd(V3))); V3 := V1 * V2; ListBox1.Items.Add(BcdToStr(VarToBcd(V3))); V3 := V1 / V2; ListBox1.Items.Add(BcdToStr(VarToBcd(V3))); V3 := V1 - V2; ListBox1.Items.Add(BcdToStr(VarToBcd(V3))); B1 := VarToBcd(V1); B2 := VarToBcd(V2); S1 := BcdToStr(B1); S2 := BcdToStr(B2); BcdAdd(B1, B2, B3); ListBox1.Items.Add(S1 + ` + ` + S2 + `=' + BcdToStr(B3)); BcdMultiply(B1, B2, B3); ListBox1.Items.Add(S1 + ` * ` + S2 + `=' + BcdToStr(B3)); BcdDivide(B1, B2, B3); ListBox1.Items.Add(S1 + ` / ` + S2 + `=' + BcdToStr(B3)); BcdSubtract(B1, B2, B3); ListBox1.Items.Add(S1 + ` - ` + S2 + `=' + BcdToStr(B3)); end; end.
The most important code found here is seen in the lines at the beginning of the SimpleMathButtonClick method:
V1 := VarFMTBcdCreate(3.5011, 32, 12); V2 := VarFMTBcdCreate(3.5020, 32, 12); V3 := V1 + V2; ListBox1.Items.Add(BcdToStr(VarToBcd(V3))); V3 := V1 * V2; ListBox1.Items.Add(BcdToStr(VarToBcd(V3)));
This code uses VarFMTBcdCreate to create two BCD numbers as Variants. Pass the number that you want to encapsulate in a BCD type in the first parameter. In the second parameter, pass the number of digits used to capture your number. For instance, in the first case shown previously, I could safely pass 5 because there are only five digits in 3.5011. Passing 32 is probably overkill. The last parameter is the number of those digits that appear after the decimal point. Again, 12 is more than ample for the job because there are only four numbers after the decimal point. (Obviously, these later two values are simply being used to fill in the Precision and SignSpecialPlaces fields of the TBcd type.)
The big question here is not how to call VarFMTBcdCreate, but why I am calling it. After all, this function returns not a TBcd value, but a Variant. I create Variants because you can directly add, multiply, divide, and subtract TBcd values when they are inside Variants:
V3 := V1 + V2;
Go back again to the declaration for TBcd. Clearly, there is no simple way to add or multiply values of type TBcd. However, if we convert them to Variants, the chore of performing basic math with TBcd values is marvelously simplified!
NOTE
For now, you need know little more than that Variants are like variables declared in a BASIC program, or like variables in Perl or Python. They are very loosely typed—or, at least, appear to be loosely typed. You can assign an Integer, Real, Byte, String or Object, to a Variant. In fact, you can assign almost anything to a Variant.
Kylix will allow you to implement particular kinds of Variants, and then give them interesting characteristics. For instance, you can create a kind of Variant that handles BCD values, and then you can teach this kind of Variant to do all sorts of interesting things In particular, you can teach it to handle addition, multiplication, and division. Clearly, this is the closest thing that Pascal has to the wonders of C++ operator overloading. If you are a Delphi programmer, you might find my definition of Variants at odds with what you learned about Variants in the Windows world. That is because Variants in Linux (and also in Delphi 6) do wondrous things that they did not do in the old Delphi 5 days.
Later in the MathButtonClick method, you see that there is a way to add, subtract, multiply, and divide TBcd variables without converting them to Variants:
BcdAdd(B1, B2, B3); ListBox1.Items.Add(S1 + ` + ` + S2 + `=' + BcdToStr(B3)); BcdMultiply(B1, B2, B3); ListBox1.Items.Add(S1 + ` * ` + S2 + `=' + BcdToStr(B3)); BcdDivide(B1, B2, B3); ListBox1.Items.Add(S1 + ` / ` + S2 + `=' + BcdToStr(B3)); BcdSubtract(B1, B2, B3); ListBox1.Items.Add(S1 + ` - ` + S2 + `=' + BcdToStr(B3));
This code shows you the BcdAdd, BcdMultiply, BcdDivide, and BcdSubtract routines, all of which do precisely what their names imply. However, most programmers would probably prefer the Variant code shown earlier because it is provides a more intuitive syntax.
Again, the point of the TBcd type is to ensure that you have no loss of precision when working with floating-point numbers. It goes without saying that there is overhead associated with the TBcd type and that, if possible, you should stick with Doubles or Reals if speed is an issue for you.
I should perhaps make clear that floating-point types are not inordinately inaccurate. The problems that you encounter with them occur when you do the kind of rounding necessary when working with money. If you don't have to round the values that you are working with to two decimal places, the standard floating-point types will probably meet your needs in all but the most rigorous of circumstances. More specifically, Doubles will generally be accurate to at least seven or eight decimal places, which in most cases is all the accuracy you will need. But if you keep rounding those values back to two decimal places, as you do when working with money, then the process of rounding the numbers will lead to errors of at least one penny.