Large Integers and Currency


Windows and COM sometimes use 64-bit integers, but unfortunately, standard C++ doesn’t support integers of this size. Nevertheless, there are ways to get around the limitations of C++, just as there are ways to get around the limitations of Visual Basic. In C++, the most convenient workaround is to use a vendor-specific type such as the __int64 type supported by Visual C++. A more portable solution is to use the LARGE_INTEGER type, which looks something like this:

typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
LONGLONG QuadPart; // In Visual C++, a typedef to _ _int64
} LARGE_INTEGER;

This union allows C++ programmers to use the structure part if their compiler doesn’t support 64-bit integers, or to use the LONGLONG part if it does. There are no unions in Basic, so the closest you can get is the following type:

Type LARGE_INTEGER
LowPart As Long
HighPart As Long
End Type

The Win32 API even provides functions (such as Int32x32To64) to manipulate the high and low parts of a 64-bit integer, but I wouldn’t touch them with a pole because Visual Basic actually provides a 64-bit integer type called Currency.


But, you say, Currency isn’t an integer type, it’s a fixed-point type. Well, yes, but the bits are the same. It’s just that behind the scenes, COM Automation is moving a decimal point four places to the left on all currency integers. All you have to do to display a Currency value as a true integer is multiply by 10,000 (or use the CURRENCY-MULTIPLIER type library constant).


One of the best places to use Currency is with the QueryPerformanceCounter API function and its friend, QueryPerformanceFrequency. The C version looks like this:

BOOL QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);

The Basic version looks like this:

Declare Function QueryPerformanceCounter Lib “KERNEL32” ( _
lpPerformanceCount As Currency) As Long

The function gives a high accuracy timing count that can be used in place of less accurate timing counts from API functions such as timeGetTime or GetTickCount. You can see it in action in the Profile functions in DEBUG.BAS.

Sub ProfileStart(secStart As Currency)
If secFreq = 0 Then QueryPerformanceFrequency secFreq
QueryPerformanceCounter secStart
End Sub

Sub ProfileStop(secStart As Currency, secTiming As Currency)
QueryPerformanceCounter secTiming
If secFreq = 0 Then
secTiming = 0 ' Handle no high-resolution timer
Else
secTiming = (secTiming - secStart) / secFreq
End If
End Sub

QueryPerformanceCounter returns a counter too accurate to fit in a Long. You can turn this timer number (which might vary in accuracy depending on your hardware) into a recognizable number by dividing by the number of counts per second, as returned by QueryPerformanceFrequency. Normally, you’ll be subtracting a beginning count from an ending count and then dividing by the frequency to get a duration. The result comes out nicely as a fixed-point Currency number representing seconds. Multiply by 1000 to convert to microseconds.


There are other places you could use Currency. For example, Win32 stores file time values as 64-bit integers in a FILETIME structure that looks a lot like a LARGE_INTEGER structure. Windows won’t know or care whether the bits you pass on the stack are Currency, FILETIME, or LARGE_INTEGER. Chapter 11 explains why I use Currency rather than FILETIME.


Another place you could use the Currency type is for API functions that deal with file sizes. Under Win32, a file size is a 64-bit integer, and so file sizes are usually returned in a low part and a high part. I personally have never seen a file larger than four gigabytes, and don’t expect to soon. In most applications, it’s safe (and simple) to assume that the high part of the file length is zero, but if you really need to handle all possible cases, you could receive the file size in a Currency variable and multiply the result by 10,000 rather than getting the high and low parts separately. Unfortunately, some Win32 structures store the high part first, making this trick impossible. It’s more useful for disk sizes anyway. Use it with GetDiskFreeSpaceEx.