Why you really should upgrade from Delphi 7

Delphi 7 is probably the most popular version of Delphi that has ever shipped. So much so that several developers staunchly stand by their favourite of many years and refuse to move up.

But Delphi has really grown with some truly fabulous things getting added in the time since the old stalwart shipped. I have compiled a list of five reasons why I believe no Delphi developer should remain on Delphi 7. Unlike most of these “top 5” lists, mine is not a list of five magic bullet features, but rather five areas of importance.

So here is my list – in random order – of the top five reasons to upgrade from Delphi 7:

1. The new IDE. Yes, I know it is different from what you’ve grown used to in Delphi 7 and yes, I know it looks a lot like Visual Studio. But is a whole lot faster than Visual Studio 2008 and much more productive than Delphi 7. Stability is not a problem on Delphi 2007 and Delphi 2009 and several specific improvements make a fantastic difference:

  • The new component toolbox. If you really want to, you can use the legendary Andreas Hausladen’s component palette replacement. But you really shouldn’t. The old, horizontal layout is hugely wasteful and gets more and more inefficient as the number of components grow. The new one is more compact, reads easier, can be filtered, works great with the mouse wheel, works great with the keyboard and requires less mouse movement when exploring.
  • The editor has learnt some interesting new tricks, including code templates. You really want that. And Syncedit. And regions. And XML comments.
  • The CPU window can now show only the disassembly view if that is all you need. How sweet is that?
  • Build events can copy compiled files where they’ll be needed, append location info, run unit tests or do whatever you need them to. And multiple build configurations can be configured or stacked to create almost any system that makes sense to you. That is a huge upgrade right there.

2. New toys! The language has had a lot of new features added. A lot of those were added because they were needed by the .NET CLS. But some of what were added were completely novel. Like class helpers, which is a language construct to implement the Decorator pattern with. This feature is so cool that Microsoft aped it in C# 3.0 with extension methods. Add to that support for generics and anonymous methods and you end up with more expressive constructs that can lead to better – and less – code.

3. A lot of modernisation in the VCL. Unfortunately, the DBGrid still looks like a fugitive from Windows 3.1, but we now have support for a whole lot of modern features, like:

  • Vista glass and task dialogs.
  • Ribbon controls, AKA Office Fluent Interface.
  • Customisable hints that can provide any level of info, right up to the thumbnail previews that you see in Office 2007.
  • Lots of little UI touches, like text hints and balloon tips on edit controls.
  • TStringList can now manage the lifetime of the objects you stored (OwnsObjects property, like TObjectList).
  • Much improved, generics-savvy data structures. Dictionaries, stacks, queues, lists – all type-safe and all with object-aware versions that can manage the lifetime of the objects they store. I love ‘em.
  • Unicode! If you only need to develop for a single language this won’t be such a big benefit, but to thousands of applications this feature is a godsend.

4. It’s a good deal. Back when I used Delphi 7, C++Builder was separate and cost the same. I work at a company with a Software Assurance subscription and we get Delphi, C++Builder and Prism all for one single price. And I’m sure you’ve seen the Buy One Get One offer? I recon RAD Studio+Change Manager looks like an absolute winner.

5. Time marches on. The longer you put it off, the harder it gets to upgrade. If you took every version from Delphi 2005 onward, every upgrade would be small enough to be manageable. Even the Unicode port was relatively painless once our third-party controls became available. But jumping from Delphi 7 to Delphi 2009 is already quite a large amount of work. That is only going to keep growing as you continue to procrastinate.

And how about changes in the underlying operating system? Sure you can keep writing your own libraries to use the new features or keep ignoring them, but neither is a very good idea. And the rules keep changing – you don’t store setting files in Program Files anymore, when the mouse hovers over your application’s task button you should now show a preview and so on. Name and shame time: I love MediaMonkey, but their Delphi 7 app is starting to reek like an old-age home. My PC at home runs Windows 7 and MediaMonkey is a decade behind how my other apps behave.

Lastly, the community moves on too. The Delphi community is one of Delphi’s great strengths and through the years I have picked up probably thousands of code snippets that have made my work better in some way – even improve my way of thinking as a programmer. The longer you stay behind, the greater the percentage of advice, solutions, tips and humour that will be irrelevant to you.

To me, that is probably the scariest thought of all.

Advertisements

Comments (21)

Hellishly Horrible Hack #1: Changing an object’s functionality without recompiling

Yup, you read that right. This one comes with a big disclaimer. It’s been tested and works but don’t read on if you are squeamish.

The set-up is like this:

We have an application that is deployed via XCopy and a second team in another city who is watching this with a hawk-eye. They have a stable system and don’t want changes that they are not absolutely certain they want. Lots to complain about in this kind of scenario, but that’s what we have to work with.

Now a requirement landed on my desk to change a piece of functionality without sending them a new version of the package (which is used everywhere) that it resides in. Fortunately, this unit (call it One) is only called from one other unit (call it Two).

Unit Two in turn is being used from the main executable – a very tiny little thing that basically loads all the runtime packages that do the actual work. So our hawkish friends are happy to take a change to the main executable, but not to this core package.

My first thought was to add the new functionality to a copy of One, which can be called directly from the executable and be folded back into the core package for the next major version of the application.

This seemed fine, until I realised that the object in unit Two is globally accessible and referenced from all over. Lots and lots of callers that cannot be recompiled. Yikes.

After the laughter gave way to despair, I finally decided that this calls for a hack. You know, the kind of thing you always sagely warn others against. After all, exactly how else do you change the functionality of Two without recompiling it?

Let me rephrase that. How do I call my new code in One, while leaving the internal state of Two identical to how it would have been if I’d used it to call One?

So what is an object anyway?

In Delphi, as you certainly know, object variables are really pointers to the actual objects, so MyList: TList creates a new pointer variable to which you can assign the memory address of a TList object. Some programmers prefer the word reference simply because Delphi handles the dereferencing of the pointer on your behalf, but a pointer is a pointer no matter what you call it.

So what does the object pointer point to exactly?

Well, it points to a record structure. Seriously. The first field of this record structure is a pointer to your virtual method table. Incidentally, if you declare a variable of a class reference type (TClass, TPersistClass…) that is also actually a pointer to the VMT. And if you call ClassType on any object variable, you also get a pointer to the VMT (typecast to a TClass – a pointer is a pointer, right?)

The second field in your record structure is the first field of your class. So, the hypothetical

TMyObject = class
private
  ID: Integer;
  Age: Integer;
  Income: Double;
end;

is identical to

TMyObject = ^TMyRec;
TMyRec = record
  VMTPtr: TClass;
  ID: Integer;
  Age: Integer;
  Income: Double;
end;

Before the flame war starts, let me point out that these two declarations are different in semantics, purpose and the warm fuzzy feelings they give to programmers, but they are identical in memory.

I can get more into this in a future post, but the thing to note here is that our object variable is in the end just another block of memory with a predictable layout. So if I needed to change the internal state of my object, I could do it without paying attention to scope identifiers, methods, implemented interfaces, or the weather.

So I could create a second class with my new functionality as long as the in-memory position of the fields I want to manipulate are the same.

TMyNewObject = class
private
  FID: Integer;
  FAge: Integer;
  FIncome: Double;
  FMonthlyWage: Boolean;
public
  procedure CalculateIncome(ID: Integer; IsMonthly: Integer);
end;

Notice that I have added a field to my new class as well as a method that can implement my new functionality. I’ve kept the other fields the same but could have changed them in any way that would leave the positions the same for those I use in TMyNewObject.

So now my executable can create an instance of TMyNewObject, call CalculateIncome and… then what? Well, remember the whole thing about the TMyObject instance being globally accessible? I need to find a way to assign the values stored in TMyNewObject back to TMyObject without being hindered by the private scope of those fields. But an object is just another block of memory, right?

To copy memory from the one to the other, I need three things:

  1. Two object instances, namely Obj: TMyObject and NewObj: TMyNewObject.
  2. The in-memory size of the data used by the TMyObject instance.
  3. A function to copy from NewObj^ to Obj^.

Requirement 1 is easily met, requirement 2 is met by calling Obj.InstanceSize and requirement 3 is met by using the following:

Move(Pointer(NewObj)^, Pointer(Obj)^, Obj.InstanceSize);

And that’s it! Well, sort of.

This will work just dandy as long as you use only simple types like Integer, Char, Double or Boolean. It does not, however work for managed types like strings or referenced types like objects. And it has a little type confusion issue as well.

String, interface and dynamic array types are managed types and if we just copy NewObj’s data over Obj’s data, we lose Obj’s pointer to this managed memory which means we leak memory. Also, and possibly worse, the memory manager has the impression that only one reference to your managed object exists, so it will free that object as soon as you free either Obj or NewObj. If you then free the other one, you’ll likely receive the dreaded “Invalid pointer operation” message.

A similar issue exists with any pointer or object types, because you lose the pointer to Obj’s data.

The solution here is to not copy the memory from NewObj to Obj, but rather to swap it. You know, like switching the values of two integer variables:

C := A;
A := B;
B := C;

We’ll put this in a reusable function. We’ll also let this new function automatically ensure that it only copies the memory the two objects have in common – so the lesser of Obj.InstanceSize and NewObj.InstanceSize.

The type confusion issue I mentioned relates to the VMT pointer. Remember I said that is actually used for the class type? So if you copy the entire memory block from NewObj to Obj, you are actually changing the runtime type. Certain constructs will still work as expected (confusingly, the is-operator is just fine) but others return type info from TMyNewObject (like ClassInfo or ClassName). Worse, your virtual methods could be all messed up.

So instead of casting like I did in the Move-call above, I’ll use this inline function to skip the VMT pointer:

function GetObjData(Obj: TObject): Pointer; inline;
begin
  Result := Pointer(Integer(Obj) + SizeOf(TClass));
end;

And then we get a perfectly generic function that doesn’t leak memory or cause pointer errors:

procedure SwitchObjects(Object1, Object2: TObject);
var
  Buffer: Pointer;
  BufSize: Integer;
  BuffOffset: Integer;
begin
  BuffOffset := SizeOf(TClass);
  BufSize := Min(Object1.InstanceSize, Object2.InstanceSize) - BuffOffset;
  GetMem(Buffer, BufSize);
  try
    Move(GetObjData(Object1)^, Buffer^, BufSize);
    Move(GetObjData(Object2)^, GetObjData(Object1)^, BufSize);
    Move(Buffer^, GetObjData(Object2)^, BufSize);
  finally
    FreeMem(Buffer);
  end;
end;

So for my nightmare scenario that I described at the start of this post, the solution turned out to be simple. Instead of directly calling the needed method on my global object, I passed it as parameter to a function that:

  1. Creates my new functionality object.
  2. Calls the new functions.
  3. Switches content with the global object.
  4. And returns.

This is about as clean as the hack can get, I’d say. Obviously, the new functionality will be folded back into the core package for the next major release of our software, but for now everyone is happy.

What truly ugly hacks have you had to apply?

Comments (3)

Translating flags in API calls, part 2

As promised, this is part 2 of my article on translating flags in API calls. If you haven’t read part 1 yet, please do so now.

Sometimes, you get API calls that make real hefty use of flags. One such example is DrawFrameControl, which has one flag to specify the type of control to draw as well as a second flag to specify the style of that control. The values are all declared as DFC_ or DFCS_ constants and they may be freely combined. Only, not all combinations work together and a lot of developers combine the flags using the plus-operator instead of or, which certainly doesn’t help.

Following on from the last post, we can create several overloads, each of which contain only the list of values relevant to the type of control:

function DrawFrameControl(DC: HDC; const Rect: TRect;
  uType: TDrawFrameControlType; uState: TDrawFrameControlCaptionStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';
function DrawFrameControl(DC: HDC; const Rect: TRect;
  uType: TDrawFrameControlType; uState: TDrawFrameControlMenuStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';
function DrawFrameControl(DC: HDC; const Rect: TRect; uType: TDrawFrameControlType; uState: TDrawFrameControlScrollStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';
function DrawFrameControl(DC: HDC; const Rect: TRect; uType: TDrawFrameControlType; uState: TDrawFrameControlButtonStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';

Once you then specify your control in uType, the values you pass to uStyle will select the correct overloaded version of this function.

Of course, if it was that simple I wouldn’t have done a separate post about this. In the case of this function (and several others), the Windows API conspires against us.

First, we have the shared states. All your control types can have the following states:

DFCS_INACTIVE = $100;
DFCS_PUSHED = $200;
DFCS_CHECKED = $400;
DFCS_TRANSPARENT = $800;
DFCS_HOT = $1000;
DFCS_ADJUSTRECT = $2000;
DFCS_FLAT = $4000;
DFCS_MONO = $8000;

There is no really sensible way of combining these with your other control-specific states, other than to add these to each of your types. So the State type for button controls will look like this:

TDfcButtonStyle = (DFCSBUTTONRADIOIMAGE, DFCSBUTTONRADIOMASK,
  DFCSBUTTONRADIO, DFCSBUTTON3STATE, DFCSBUTTONPUSH,
  DFCSINACTIVE = 8, DFCSPUSHED,
  DFCSCHECKED, DFCSTRANSPARENT, DFCSHOT,
  DFCSADJUSTRECT, DFCSFLAT, DFCSMONO,
  DFCSBUTTONRANGE = 31);
TDfcButtonStyles = set of TDfcButtonStyle;

And you’d have something similar for all four types (buttons, captions, menus/popup menus and scroll bars). Once again, notice that DFCSINACTIVE gets a value of 8 instead of $100, because we are interested in the bit position.

The issue then of course becomes on of naming the values. You see, if you have DFCSFLAT in both TDfcButtonStyle and TDfcCaptionStyle the compiler will see them both and complain about the identifier being re-declared.

My favourite solution to this, is using the {$SCOPEDENUMS} compiler directive. This basically makes the Delphi compiler treat enumerated types in a way that is very similar to that of C# – the actual enumerated values are only in scope when you specify the type. I don’t like needing to think up unique two-character prefixes for each type anyhow, but in this case I find scoped enumerations to be invaluable.

Not only is TDfcButtonStyle.DFCSHOT now a distinct value from TDfcCaptionStyle.DFCSHOT, but I can in fact clean the names up to look more like it was thought up with mortals in mind:

TDfcButtonStyle = (ButtonRadioImage, ButtonRadioMask,
  ButtonRadio, Button3State, ButtonPush,
  Inactive = 8, Pushed, Checked, Transparent, Hot,
  AdjustRect, Flat, Mono, Range = 31);
TDfcButtonStyles = set of TDfcButtonStyle;

No doubt this technique will have lots of traditionalists up in arms, but I think it looks much cleaner and more readable.

The second issue is a little worse and in a twist of irony, is even worse when scoped enumerations are used.

You’ll notice there is no DFCBUTTONCHECKED or ButtonChecked in the examples above. That’s because this Windows constant has a value of zero. And Delphi set types don’t handle zero at all. You could pass [] for that parameter, but then it is no longer obvious that you are trying to draw a checkbox. Also, the compiler will have a torrid time distinguishing between the DrawFrameControl overloads.

I found two ways of getting this to work, but they both have the word hack scribbled all over them.

Firstly, you could add ButtonCheck to the above enumeration at some unused position, say 6. This works and is transparent to the client code, but the success of the technique depends entirely on the API function you’re calling. If it has an undocumented feature, or it changes in the next Windows version or it keels over when an unknown flag is passed in, you’re toast. Experience shows it to be mostly reliable, but it is still not to be fully trusted.

The second solution – the one that doesn’t like scoped enumerations – is to declare the missing value as a typed constant:

DFCSBUTTONCHECK: TDrawFrameControlButtonStyles = [];

This is future proof but has another issue – inconsistent use. Compare the calls to draw a checked radio button and a checked check box:

DrawFrameControlTS(PaintBox.Canvas.Handle, PaintRect,
  DFCBUTTON, DFCSBUTTONCHECK + [DFCSCHECKED]);
DrawFrameControlTS(PaintBox.Canvas.Handle, PaintRect,
  DFCBUTTON, [DFCSBUTTONRADIO, DFCSCHECKED]);

And no, I don’t have a magic trick that will make this work better.

So the take-home for this two-part article is this:

It is possible to create type-safe imports for Windows API functions that are every bit as natural to use as native Delphi functions. Some functions make strange use of flags and these can either use some of the described techniques to work around the hardship, or could be imported using good ol’ Cardinal.

As always, the situation and your own better judgement should serve as a guide.

Comments (2)

Translating flags in API calls, part 1

OK, so we have all seen the way that flags are passed to API calls. Take the ShowWindow function. In Delphi it is declared as this:

function ShowWindow(hWnd: HWND; nCmdShow: Integer): BOOL; stdcall;

Now, there is nothing wrong with the declaration – it will work, is well documented and is of course not seen by most Delphi developers, since the calls are all wrapped up in the Delphi VCL.

Thing is, that the nCmdShow parameter is declared as in integer (even in the Windows API docs) but functionally, it acts as an enumerated type. That is to say, it has a defined set of values, and only those values are permissible. What’s more, those values are sequential from 0 to 10.

Sounds familiar? Sound a little like an enumerated type, now doesn’t it? You can look up the imports used in Delphi in Windows.pas, but here is an alternative version:

function ShowWindow(hWnd: HWND; nCmdShow: TShowWindowValues): BOOL; stdcall;

and TShowWindowValues declared as:

type
  {$Z4}
  TShowWindowValues = (SW_HIDE = 0,
    SW_SHOWNORMAL = 1, SW_NORMAL = SW_SHOWNORMAL,
    SW_SHOWMINIMIZED = 2, SW_SHOWMAXIMIZED = 3,
    SW_MAXIMIZE = 3, SW_SHOWNOACTIVATE = 4,
    SW_SHOW = 5, SW_MINIMIZE = 6,
    SW_SHOWMINNOACTIVE = 7, SW_SHOWNA = 8,
    SW_RESTORE = 9, SW_SHOWDEFAULT = 10,
    SW_MAX = SW_SHOWDEFAULT);
  {$Z1}

OK, so a few things to note:

  1. The {$Z4} directive is necessary because the API type for the parameter is a four-byte integer and Delphi’s enumerated types are one byte in size by default.
  2. I specified the numeric values even though they are not specifically needed in this case (everything is sequential). This of course won’t work at all in Delphi 3 and earlier.
  3. Two of the values are assigned a value held by one of their siblings. So even when two values are equivalent, you can still use an enum.
  4. Given that the function is now type safe, it is even possible to change the names of the identifiers – fix the case and remove the underscores – if you feel like it, without confusing anyone.

How about flags that are combined?

These work differently and the values are not sequential. The idea being that the values can be mixed using the or operator. Consider the DrawEdge function:

function DrawEdge(hdc: HDC; var qrc: TRect; edge: UINT; grfFlags: UINT): BOOL; stdcall;

The edge parameter takes a combination of four values. These values are declared as:

BDR_RAISEDOUTER = 1;
BDR_SUNKENOUTER = 2;
BDR_RAISEDINNER = 4;
BDR_SUNKENINNER = 8;

Powers of two are used, so that when these values are added together (or combined with or) you would retain all the values. Because these are never meant to be used on their own, but rather in combinations, I like using sets:

TBorder = (BDR_RAISEDOUTER, BDR_SUNKENOUTER, BDR_RAISEDINNER, BDR_SUNKENINNER);
TBorders = set of TBorder;

At first glance, this may seem like a disaster. The values of the three enum values are 0, 1, 2 and 3 respectively, which is not as they are declared in Windows.pas.

But the way that set types work is identical to the way flags do. While the value of BDR_RAISEDINNER may be 2, the value of [BDR_RAISEDINNER] is in fact 4. Two to the power of two is four, remember?

Some readers may know already that the declarations above don’t work. The concept is fine, but the type sizes are all wrong. TBorders is one byte in size, not four. And {$Z4} does nothing to change it.

You see, set values are stored as bit flags, but the size is determined by the range of the ordinal type. I have four values in TBorder, so TBorders only uses the four least significant bits of a byte. So how do we get TBorders to be four bytes in size? Easy, we add a value that should be stored in bit 31:

TBorder = (BDR_RAISEDOUTER, BDR_SUNKENOUTER, BDR_RAISEDINNER, BDR_SUNKENINNER, BDR_Range = 31);
TBorders = set of TBorder;

The extra BDR_Range value stretches TBorders to four bytes and we have a type safe version of DrawEdge.

There is more to come, but I think that will wait for the next post.

First, there is something that I want to clear up. Suppose you read this and you like the concept, now what? ShowWindow and DrawEdge both have their definitions in Windows.pas and I am in no way suggesting that these must be changed or that we should all import them separately.

The one (secondary) point here is that the declarations in Windows.pas are almost certainly generated by some tool. If Nick and the boys were to become real inspired they could change the tool to generate type safe overloads. There are probably some good reasons why this was not done from the start (you couldn’t assign numeric values to enum elements before Delphi 4 is one that comes to mind), but it is still a good idea.

More generally though, there are many APIs that Delphi doesn’t have imports for. Lots of programmers import these and some even share large repositories of them. When you find the need to do an API import, there is nothing stopping you from making it type safe.

In part 2, I’ll discuss what to do when your API call has a conditional set of values for a parameter – check out DrawFrameControl if you want a preview.

Update: Part 2 has now been published.

Comments (1)

12 Delphi code templates to save keystrokes

Sometimes, typing is just no fun. As a programmer there are certain things that you need to type over and over (and over and over) again, with only the smallest of differences.

For example, how often have you declared a property that looks like this:

property SomeProp: Integer read GetSomeProp write SetSomeProp;

Next, you press Ctrl+C to auto-complete the declaration and then only do you get to writing the beef of what you’re trying to accomplish. OK, so it isn’t painful, but it is monotonous.

Suppose you could have the declaration above created by only supplying the property name and type? Like this:

Code template for properties

That’s the cool thing about code templates, which had its debut in Delphi 2006 Delphi 2005 (I think). Delphi ships with a number of ready-made templates, most of which are very nicely done. We can add a few more to make for less repetitive property declarations. But that’s not the best bit.

Delphi 2009 has support for generics, which allow (among many other things) the quick creation of type-safe enumerators, lists and hash tables. If you don’t have Delphi 2009 yet, that’s no reason to write these from scratch every time and it certainly is no reason to just throw your valuable data into lists designed for pointers.

Enter my code templates for type-safe object lists, enumerators and TBucketLists. Suppose I need an enumerator for TClientAccountList which contains TClientAccount objects, I only need to place my cursor among the private field declarations of TClientAccountList and type:

enumarray

Then press Tab. There are three placeholders, namely enumerator class name (TAccountEnumerator), list item class name (TClientAccount) and item list class name (TClientAccountList):

    type
      TAccountEnumerator = record
      private
        FIndex: Integer;
        FOwner: TClientAccount;
      public
        function GetCurrent: TClientAccountList; inline;
        function MoveNext: Boolean; inline;
        property Current: TClientAccountList read GetCurrent;

        constructor Create(AOwner: TClientAccount);
      end;

You also get a public GetEnumerator method and the implementations of these declarations, which you unfortunately will need to move to the implementation section of the unit yourself – I couldn’t find a way of doing this from within the template.

If you think the enumerator above looks a little strange, check out Hallvard Vassbotn’s excellent write-up on enumerators.

The code template for object lists obviously includes enumerators and work in a similar way.

So before I give you the downloads, a couple of instructions.

For Delphi 2007 and 2009, the XML files should be stored to My Documents\RAD Studio\code_templates and for Delphi 2006 you place it in \Program Files\Borland\BDS\4.0\Objrepos\code_templates\delphi.

Some of my templates expand automatically. If you find that annoying, (or would like another one to do that) change the following line near the top:

<template name=”namehere” invoke=”auto”>

to

<template name=”namehere” invoke=”manual”>

or vice versa.

Also, because there are many different permutations of property declarations, I used a naming convention (imagine!):

prop[i][f/c][f/c/nothing]

So they all start with prop, if your property is an array property, you add an i (for index) and then either f (for field) or c (for code) for each of the read and write specifiers.

If that only server to confuse you, some examples may help:

  1. propf is a read-only property that reads a private field.
  2. propicc is an array property with a get-function and a set-procedure.
  3. propfc is a property that reads from a field but writes to a procedure.
  4. and so on.

Enough talking. Here is a zip file with all twelve code templates. Feel free to use them any which way you please.

Comments (5)

What’s wrong with floating-point?

Every so often, floating-point data types come under renewed criticism. There are plenty of good reasons to whisper behind their backs, but are they really all bad?

The basic problem is that floating-point types represent any value as an approximation. It works really well too. In fact, for most whole numbers that you’re likely to encounter the approximation is exact, so 1 is always 1 and 10124 is always 10124.

It gets messy in two scenarios. First, when the value is greater than the precision for your floating point type you start losing least significant digits. The precision for Double is about sixteen digits, so this is the kind of problems you run into:

MyVal := 12345678901234567; // Seventeen digits
if MyVal = 12345678901234567 then
  ShowMessage('This doesn'' show')
else
  ShowMessage('But this does');

Note that Double can represent far larger numbers than this, but only at this 53 bit precision. Integer on the other hand has a fixed upper value (MaxInt) after which it wraps around and your huge number is suddenly very small. Try it:

MyVal := MaxInt + 1;
if MyVal = -MaxInt-1 then
 ShowMessage('Where''d my big number go?');

The second problem – and the one I believe is more common – arises when working with fractions. The problem here is that not all fractions can be represented in a finite number of digits. Working with decimal (base 10) numbers, you can immediately point to 1÷3. This hands you a value that repeats forever and will never be completely accurate, no matter how many threes you add to the end of it. The same happens in binary systems and on numbers you don’t expect – for example, the decimal value 0.1 repeats forever when you convert it to binary. And that means that the approximation is off and all calculations using that value are inaccurate.

It works often enough that many programmers get away with code that compares floating point values using the equality operators (=, <, >). In fact, when you round it for display or storage the value is normally spot-on what you expect it to be. Things go well until you compare two values that should be the same but were calculated in different ways. To remedy this, one should always compare values within ranges, like so:

if abs(x - y) < 0.0001 then
  DoSomething();

The above is a little unintuitive, and Delphi now sports a couple of functions to make it all look pretty:

function CompareValue(const A: Double; const B: Double; Epsilon: Double = 0): TValueRelationship; overload;
function IsZero(const A: Double; Epsilon: Double = 0): Boolean; overload;
function SameValue(const A: Double; const B: Double; Epsilon: Double = 0): Boolean; overload;

Each of these have a few overloads to safely work with Single and Extended values as well. Think of these as you would of utility functions that compare the contents of objects – a necessary way of dealing with these data types. Direct use of the equality operators should be banned for safety’s sake.

So, how about the alternatives? The advice most often given is to store real values as integers, fractions or BCD values.

Integers can be used to implement a fixed-point number, but pose a problem in their limited scale and ease of use. If you’re going to work with fewer that ten digits in total, an Integer could work just fine. As long as you remember to consistently multiply, divide and round as needed. See the problem? For most values that this works for, plain old Double will probably give you results that are at least as good with far less greying of the hair. You could of course wrap this in a record with some overloaded operators, but I have personally not had the need.

Currency is a special Integer case, which is implemented as a 64 bit signed Integer with four places after the decimal point.

A lot of the inaccuracy with floating-point arithmetic arise when dividing two integer values. Popular solution to this? Just keep the original integer values in a record structure. You know what this means don’t you? Even more maintenance work than the fixed-point integer solution listed above. You need to make sure when adding and subtracting these numbers that you work with a common denominator and probably write some code to simplify your fractions. To make matters worse this can only store rational numbers, so PI will be a very poor approximation. Again, this has never seemed like a solution worth implementing to me.

Finally, there is BCD. Every four binary fits store a single decimal digit, so four bits can store up to decimal  nine, eight up to ninety-nine and so on. BCD can store huge numbers. No, even bigger than that. In the Delphi implementation, a total of sixty-four digits. That’s a lot. The tradeoff is memory footprint and also some performance penalty because every BCD value takes a whopping 34 bytes of memory, which could be a lot if you’re passing them on the stack as parameters to functions. Moreover, BCD isn’t a pleasure to work with natively – you need special functions to add them, multiply them, even assign them. It gets a little easier (and still less efficient) if you declare and manipulate them as Variants. Check out the FmtBcd unit for info on working in high precision.

Personally, I use Double as my workhorse type for real numbers with Extended as my backup. They’re more than accurate enough for nearly all scenarios, give adequate performance and use clean, simple syntax. Life is Zen as long as you use SameValue, CompareValue and IsZero religiously. I reserve BCD for cases where exceptional many-decimal accuracy is a must and use Integer for integers only.

What heuristics do you apply?

Leave a Comment

« Newer Posts