19 July 2022

Numeric/string conversions

Ever wondered how numbers are printed? Check out the following snippet:

Dim i As Int = 2345
Print i

Before the number is printed to the active window, the number is converted to a string using a hidden call of the Str(i) function. The same is true for printing a Date (numeric) value; the date-value is converted to a string before it is output in the Debug Output window:

Dim d As Date = Now
Debug.Print d
Debug Str(d)            // short for Debug.Print

The output is (at the time of writing this blog) is:

18-7-2022 17:03:14
18-7-2022 17:03:14

The snippet demonstrates that the date is converted using a hidden call of the Str() function. It is not possible to print any numeric value without converting it to a string first. GFA-BASIC 32 uses hidden calls to the Str() function to convert the numeric value(s). The need for a numeric-to-string conversion before printing to screen (or printer) is that the underlying output functions require strings. For instance, to put the value on the screen the Print command uses the API function TextOut(hDc, x, y, strAddr, strLen), which requires a string.

Another implicit to string conversion is demonstrated in this example:

Dim s As String, i As Int = 2345, d As Date = Now
s = i & " " & d
Debug.Print s   // shows:  2345 18-7-2022 17:12:44

The & string operator allows to concatenate numeric and string values without converting them to a string explicitly. Still, under the hood, everything is converted to a string first. A nasty habit of the Str() function is to insert a space in front of converted value (a VB compatible setting). To prevent the space insertion use Mode StrSpace "0" somewhere at the beginning of the program.

The Str() and CStr() functions
The Str() function converts the value argument using a classic-style method, the output of the function is not language dependent. The produced string does not contain locale info for punctuation to separate the thousands and decimals in the value. A floating point is converted to a string with a single dot as separator. If the value is too large or too small the exponent notation is used.

s = Str( 1 / 3)    // argument is a Double
Debug s            // 0.333333333333333
s = Str(_minDbl)   // a very small number
Debug s            // -1.79769313486232e+308

The output of Str() can not be manipulated. However, there is another function that converts the numeric value according the user's regional or language settings, the CStr() function.

Dim f As Float = 1000.2345
Debug.Print CStr(f)     // output: 1000,234

Here the output follows the regional setting for the Netherlands where the decimal part is separated with a comma. The CStr() function follows the rules defined by the OLE API function VariantChangeTypeEx() that handles coercions between fundamental data-types, including numeric-to-string and string-to-numeric coercions. One of the parameters of the API is the LCID, a value that uniquely identifies the language to use. GFA-BASIC 32 stores the LCID value at start-up, by querying the user's default LCID from the OS. The LCID value is stored in the Mode structure and cannot be changed directly. The only way to modify the internal LCID is by using the Mode Lang command. By specifying a 3 letter language abbreviation GB32 will change the internal LCID value accordingly.

Dim sMyLanguage As String = Mode(Lang)
Mode Lang "DEU"         // change to German, LCID is changed.

If you're not satisfied with the numeric format produced by CStr() you can switch to the Format() function. The Format() function not only allows language depending conversion, but you can fine tune the output for your particular needs. When your user inputs a value in a language dependent value, for instance by using a TextBox, your program must be able to convert the string to a numeric datatype in GFA-BASIC's internal format using one of the C*() conversion functions, like CInt($).

String to numeric
Converting from string to numeric, is done using the Val*() functions or the other C*()-conversion functions. The Val* functions accept strings containing numeric values in the classical format, ie. the only punctuation allowed is a dot in floating point values. To convert to a Double use either Val() or its synonym ValDbl():

Dim d As Double, sValue As String = "1000.234567"
d = Val(sValue)
d = ValDbl(sValue)

To convert to a 32-bit integer use ValInt() and for a 64-bit integer ValLarge().

The Val() functions are not suited for language dependent formatted values. In the next snippet, the number is formatted according the regional settings of the Netherlands and is then converted to a Double using CDbl().

Dim d As Double, sValue As String = "1.000,23"
d = CDbl(sValue)
Debug d         // output: 1000.23

CDbl() uses the same  API function VariantChangeTypeEx() to process the string and change it into a floating-point value. To parse a language dependent formatted numeric string use any of the following functions:

Bool = CBool(); Byte = CByte(); Currency = CCur(); Date = CDate(); Double = CDbl(); Short = CShort(); Integer = CInt() or Long = CLong(); Handle = CHandle(); Large = CLarge(); Single = CSng() or Single = CFloat(); String = CStr(); Variant = CVar()

These function not only parse strings, but can also be used to convert any other data-type using an explicit cast. For instance:

Dim i As Int, b As Bool = True
i = CInt(b)     // i becomes -1, same as i = b

These explicit casts produce the same code as simply assigning one datatype to another.

CStr() produces a language dependent formatted string using the current LCID value. The C* conversion functions use the LCID language value for conversion to a numeric datatype. Format() offers more possibilities to format a numeric value. Str() and Val*() function use classical formatted values.