Hidden treasures of Sysutils, Part 2

by Mark G. Wiseman

The Sysutils namespace of the VCL provides several functions and global variables that you may find useful when working with times, dates, numbers, currencies, files, file names, and strings. In Part 1 of this series, I introduced you to variables and functions that worked with dates and times. In this article, you will learn about the variables and functions that can help you when dealing with numbers and currency.

As I mentioned in Part 1, these variables and functions are not really hidden. Most of them can be found in the online help—if you know where to look.

Locales

With the exception of HexDisplayPrefix (explained later), the global variables discussed in this article are initialized with locale settings obtained from Windows. The VCL function, GetFormatSettings() initializes these variables at application startup.

The VCL also calls GetFormatSettings() whenever your program receives a WM_WININICHANGE message, so be careful when working with the global variables because they can change at any time.

The code examples in this article and the companion program are based on the United States locale settings, the settings specific to my system.

Numbers and currency

I am sure you know that the VCL was originally written in Object Pascal for Delphi. The Sysutils functions and variables that work with numbers and currency were originally written to work with Delphi data types. With the exception of two data types, the Delphi data types all correspond with C++ data types and are represented in C++ with the use of typedef. For example, the LongInt data type from Delphi is defined in the VCL header file sysmac.h as

typedef signed long Longint;

The two exceptions to the use of typdef are the Delphi data types Currency and Comp. These two data types are represented in C++ as classes with the names, as I am sure you’ve guessed, of Currency and Comp.

Currency is obviously a class that represents currency values. The actual currency value is stored within the class as an __int64. Like AnsiString, Currency is a full-featured class that you should seriously consider using.

The Comp class represents a 64-bit integer. The Comp class is included in the VCL only for backward compatibility and is of no use to C++ programmers. I will not discuss this class or the functions in Sysutils that work with it.

Numbers

In Sysutils, there are functions and variables for working with decimal and hexadecimal numbers.

Number variables

There are two variables that are used when formatting numbers. Here are their declarations:

char ThousandSeparator;
char DecimalSeparator;

These variables are set by the VCL function GetFormatSettings().

On my system, ThousandSeparator contains ‘,’ and DecimalSeparator contains ‘.’.

Number functions

There are several functions in Sysutils that deal with numbers. Some of those functions are:

AnsiString IntToStr(int Value);
AnsiString IntToStr(__int64 Value);
AnsiString FloatToStr(Extended Value);
AnsiString FloatToStrF(
  Extended Value, TFloatFormat Format, 
  int Precision, int Digits);
AnsiString FormatFloat(
  const AnsiString Format, 
  Extended Value);
int StrToInt(const AnsiString S);
__int64 StrToInt64(const AnsiString S);
int StrToIntDef(
  const AnsiString S, int Default);
__int64 StrToInt64Def(
  const AnsiString S, __int64 Default);
Extended StrToFloat(const AnsiString S);

The IntToStr() function is overloaded to accept either an int or an __int64 and returns an unformatted AnsiString. For example:

int i = 1234;
AnsiString s = IntToStr(i);
__int64 i64 = 1234567890;
AnsiString s64 = IntToStr(i64);

The FloatToStr() function takes an Extended data type as an argument. The Extended data type is a native Delphi type that corresponds to a long double in C++:

typedef long double Extended;

Since Extended is really just a long double, you can pass a float, a double or a long double into FloatToStr(). Here are some examples:

float f = 1234.5;
String sf = FloatToStr(f);
double d = 1234.5678;
String sd = FloatToStr(d);
long double ld = 1234567890.12345;
String sld = FloatToStr(ld);

The only formatting done by FloatToStr() is to place the character contained in the DecimalSeparator variable in the correct location.

The FloatToStrF() function is similar to FloatToStr(), but it gives you more control over the formatting of the returned AnsiString. In addition to taking an Extended as an argument, FloatToStrF() also takes as arguments a TFloatFormat, an int specifying precision, and an int that specifies the number of digits in the returned AnsiString. TFloatFormat is an enum that tells FloatToStrF() how to format the number. The acceptable values for TFloatFormat are ffGeneral, ffExponent, ffFixed, ffNumber, and ffCurrency. The online help for the VCL contains a detailed explanation of each of these enums. Just look under “TFloatFormat” in the help index.

If you need even more control over formatting than that provided by FloatToStrF(), then you need to use the FormatFloat() function. This function accepts an AnsiString that specifies the formatting used when converting an Extended value to an AnsiString. You can specify different formats for positive numbers, negative numbers and numbers that are equal to zero. Here is an example:

String fs = "#,##0.00;(#,##0.00);'-zero-'";
double n = 12736.987;
sn = FormatFloat(fs, n);
// sn = 12,736.99
n = 0;
sn = FormatFloat(fs, n);
// sn = -zero-
n = -27456;
sn = FormatFloat(fs, n);
// sn = (27,456.00)

Look in the online help for FormatFloat() to learn about all the different format specifications you can use with this powerful function.

The two functions StrToInt() and StrToInt64() both take an AnsiString as an argument and return an int or __int64 respectively. If the AnsiString doesn’t represent a valid number, an exception will be thrown. Here are some examples:

String s = "87456";
int i = StrToInt(s);
__int64 i64 = StrToInt64(s);

You may find StrToIntDef() and StrToInt64Def() more useful. These two functions also take an AnsiString as an argument and return either an int or an __int64 just as the StrToInt() and StrToInt64() functions do. However, they also take a second argument that specifies a default value to be returned if the AnsiString does not contain an acceptable value. No exception is thrown if the string value is invalid. The following examples both return the “default”:

String s = "Not a number";
int i = StrToIntDef(s, 5213);
__int64 i64 = StrToInt64Def(s, 789426);
// i = 5213
// i64 = 789426

The StrToFloat() function takes an AnsiString and returns an Extended. (Remember, you can use a long double in place of Extended.) If the AnsiString argument contains a decimal separator, it must match the separator stored in the DecemalSeparator variable. Unfortunately, the AnsiString may not contain any other non-numeric characters, not even the character in the ThousandSeparator variable. If the AnsiString does not meet these requirements, an exception will be thrown.

Hexadecimal variable

There is one global variable related to hexadecimal numbers:

AnsiString HexDisplayPrefix;

Unlike the other number-related variables in Sysutils, HexDisplayPrefix is not set in the GetFormatSettings() function and is not dependent on the Windows locale. The VCL sets HexDisplayPrefix to “0x” at application startup.

Hexadecimal functions

There is one overloaded function that works with hexadecimal numbers:

AnsiString IntToHex(
  int Value, int Digits);
AnsiString IntToHex(
  __int64 Value, int Digits); 

The IntToHex() function is overloaded to accept either an int or an __int64 and returns an AnsiString formatted as a hexadecimal number. Surprisingly, IntToHex does not attach the prefix specified in the HexDisplayPrefix variable. If you want to use HexDisplayPrefix, you have to write additional code. For example:

int i = 1024;
String s = IntToHex(i, 8);
// s = 00000400 Huh?
int i64 = 987654321;
String s64 = IntToHex(i64, 8);
// s64 = 3ADE68B1
String s = 
  HexDisplayPrefix + IntToHex(99, 4);
// s = 0x0063 Ahh!

Currency

As I mentioned, the Delphi data type Currency is represented in C++Builder as a class named Currency. The Currency class defines a complete set of arithmetic and comparison operators. The class also has stream insertion and extraction operators and conversion operators for AnsiString, double, and int. The class is defined in the syscurr.h header file.

Internally, Currency stores the actual value as an __int64, so you can use it to represent a very large amount of money.

Currency variables

There are four currency related variables:

AnsiString CurrencyString;
Byte CurrencyFormat;
Byte NegCurrFormat;
Byte CurrencyDecimals;

All of these variables are set by the GetFormatSettings() function using locale information provided by Windows.

The CurrencyString variable contains the currency symbol used when formatting currency. On my system, CurrencyString is equal to “$”. The CurrencyFormat and NegCurrFormat variables contain numbers that represents a currency format. Look at the VCL online help for an explanation of these numbers.

The number of digits to the right of the decimal point is contained in the CurrencyDecimals variable.

Currency functions

The functions Sysutils provides for working with Currency are:

AnsiString CurrToStr(Currency Value);
AnsiString CurrToStrF(Currency Value, 
  TFloatFormat Format, int Digits);
AnsiString FormatCurr(
  const AnsiString Format, 
  Currency Value);
Currency StrToCurr(const AnsiString S);

The CurrToStr() function takes a Currency value and returns an unformatted AnsiString. Since the Currency class provides conversion operators for int and double, you can also use these data types as input to CurrToStr(). Here are some examples:

Currency c = 15128.62;
String s = CurrToStr(c);
String s = CurrToStr(15128.62);
String s = CurrToStr(15128);

The CurrToStrF() returns a formatted AnsiString. In addition to the Currency argument, CurrToStrF() also takes a TFloatFormat argument and an int argument that specifies the number of digits used to format the AnsiString. I don’t really understand why Borland decided to require the TFloatFormat argument. You need to set this argument to ffCurrency to get the format you would expect. For example:

Currency c = 15128.6;
String s = CurrToStrF(c, ffCurrency, 0);
// s = $15,129
s = CurrToStrF(c, ffCurrency, 2);
// s = $15,128.60

The FormatCurr() function is nearly identical to the FormatFloat() function except that it takes a Currency argument instead of an Extended argument:

Currency c = 15128.6;
String s = FormatCurr(
  "#,##0.00;(#,##0.00)", c);
// s = 15,128.60
s = FormatCurr(
  "$#,##0.00;($#,##0.00)", c);
// s = $15,128.60

Notice that FormatCurr() will not automatically use the value from CurrencyString.

The CurrToStr() function takes an AnsiString argument and returns a Currency object. The AnsiString cannot contain a currency symbol or any thousand separators and the decimal separator must match the value in the DecimalSeparator variable. If the AnsiString is invalid, CurrToStr() throws an exception.

Conclusion

In Part 1 of this series, I explained the functions and variables that Sysutils provides for working with dates and times. In this article, I have discussed numbers and currency. In Part 3, I will tell you about functions that work with file names.

In the meantime, I would encourage you to examine the header file sysutils.hpp and the Pascal source file sysutils.pas to see what hidden treasures you can find.