30 April 2020

Float to Integer–CInt & CintRZ

In the previous post Converting float to integer I discussed several possibilities to assign a floating-point value to an integer. The post discussed how the GFA-BASIC 32 compiler handles the different types of conversions; either with or without conversion functions. We saw that GB uses a default conversion that rounds to the nearest even number. When the fractional part of the number is exactly 0.5 the value is rounded down one time and up another time. For instance, 2.5 is rounded down to 2 and 3.5 is rounded up to 4. An application can change this behavior by using an explicit conversion function like Int(), Trunc(), Floor(), Round(), and QRound(). The blog post did not discuss CInt() and CIntRZ(), new functions added to GB 32 to easily convert VB(A) and C/C++ code.

CInt() converts and rounds to the nearest event number; the argument is converted using GB’s default setting of the FPU’s control register. Consequently, for floating-point arguments CInt() is equal to simply assigning a float to an integer:

Dim i As Int, f As Float = -3.5
i = f          ' assign directly, same as
i = CInt(f)    ' explicit conversion, i becomes -4

CIntRZ() rounds the argument down to zero and does what Trunc() does (for compatibility with C/C++).

Dim i As Int, f As Float = -3.5
i = CIntRZ(f)  ' round towards zero, i becomes -3

A Variant argument
The conversion functions like Int(), Trunc(), Floor(), Round(), and QRound() only accept numeric data types and variants for their arguments. The value passed is loaded into the FPU’s ST0 register and then rounded. (These functions also accept integer data types, but that won’t lead to anything useful.)
In case the argument is a variant it’s value is first converted to a floating-point data type (double), which is then loaded into ST0 and rounded. Since these standard GB functions now accept variants as well, the functions get to handle variants containing strings. The process is the same as with numeric variants, the variant-string must first be converted to a floating-point value as is required by these functions. This variant-string to double conversion uses the OLE function VariantChangeTypeEx() API from oleaut32.dll. The VariantChangeTypeEx() function handles coercions between the fundamental types including numeric-to-string and string-to-numeric coercions. One of the parameters of this function is the LCID value to use for the coercion. A LCID value is the locale identifier and specifies how dates, times, and currencies are formatted. The variant-string-to-double coercion uses the GFA-BASIC’s current LCID value. GB sets the LCID value to the user’s ‘Language and Regional’ settings when a program is started. For proper conversions the variant-string must contain a numeric value according to the locale settings. For instance, some European languages separate the integral and fractional part with a comma rather than a point. For instance, the following works with Dutch regional settings:

Dim i As Int, v As Variant = "2,5"
i = Trunc(v)  ' result is 2

If the variant v would contain a dot rather than a comma (“2.5”) the OLE conversion function ignores the dot and returns 25.

Note - you can change GFA-BASIC’s LCID value GB uses with the Mode Lang command.
Note - if you disabled the compiler setting ‘Don’t autoconvert numeric strings to values’ you could even pass a string data type to these standard GB functions. In this particular case the string is first copied to a hidden variant and then converted with VariantChangeTypeEx() to double. In most cases the compiler setting to not autoconvert is enabled (default setting) and the string data type is not accepted by the compiler for these functions.

The arguments of CInt() and CIntRZ()
CInt()
and CIntRZ() are capable of handling more data types than just numeric arguments as the other functions do. The documentation explicitly states that CInt() and CIntRZ() use an OLE function to convert the argument passed to the function. This is not entirely true, it depends on the data type of the argument. The OLE conversion is only applied if the argument is not numeric, ie String or Variant. For numeric arguments CInt() behaves exactly as the direct assignment of a float to an integer and CIntRZ() behaves exactly as Trunc().

As said, on the lowest level CInt() and CIntRZ() only accept floating-point values. Consequently, a string or variant argument must first be converted to a double (the default data-type for these functions). For numeric-variants GB extracts the value (to load in ST0 for conversion to integer) exactly as it handles the non-variant numeric values. In other words, GB does not use an OLE conversion function to extract a numeric value from a variant to coerce it to the floating-point data type.

Using a string argument
Interesting enough, CInt() and CIntRZ() accept the string data type for input. Before these functions process the float-to-int conversion the string must be converted to a floating-point data type. Because of VB(A) compatibility the string must be converted according the ‘Language and Regional’ settings; the conversion must use the LCID value. GB accomplishes this by first copying the string to a hidden temporary variant and than call VariantChangeTypeEx() to convert to double as input for CInt() and CIntRZ().

CInt() and CIntRZ() complement the standard GB function ValInt(). ValInt()converts a string to an integer according the Mode Val setting, CInt() and CIntRZ() use the regional settings for conversion. This is true for all C* conversion functions (CFloat, CDbl, etc) taking a string as input, the string is converted using the regional settings.

i% = ValInt("2.5")  ' = 2
i% = CInt("2,5")    ' = 2