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

24 March 2020

Converting a float to an integer

Without giving it another thought we often convert a floating-point value to an integer by simply assigning a float to an integer data type variable. We assume the compiler knows what’s best and we trust the compiler does the proper conversion for us. Time for a look behind the scenes.

The FPU and the control register
The FPU is independent of the main processor and contains its own set of registers to perform its task. The FPU registers include eight 80-bit data registers (ST0-ST7), and three 16-bit registers called the control, status, and tag registers. We will ignore the status and tag registers and focus on the control register which is used to access the features of the FPU. The control register controls the floating-point functions within the FPU. The control register uses a 16-bit register where each bit defines a specific setting such as the exceptions to produce, the precision the FPU uses to calculate floating-point values, and the method used to round the floating-point results. The bits are shown below:

Control bits      Description
0-5 Exception masks
6-7Reserved
8-9 Precision control
10-11 Rounding control                
12Infinity control

For the conversion from float to integer we are only interested in bits 10 and 11. The x87 FPU implements four rounding methods in hardware. The possible settings of the rounding control bits are as follows:

00 - Round to nearest (even)
01 - Round down, towards negative infinity
10 - Round up, towards positive infinity
11 - Round toward zero

The "Round to nearest (even)" method is used by default by GFA-BASIC 32, so there's a high chance you're already using it. The current rounding mode of the x87 can be obtained using the fstcw assembler command, as shown in the following sample:

' Status of bits 10 and 11 of control word
Dim cf As Word
. fstcw [cf]
Debug Bin(cf %& %110000000000, 2)  ' = 00

By default, GB clears both rounding bits and converts floating-point values to integers using the “Round to nearest (even)”. For this to happen, GB initializes the FPU with the value 0x372, which clears the rounding bits. The way of rounding can be changed and set to a new value by setting  the control word to a new value using the fldcw instruction. GB changes the rounding bits when it rounds a floating-point value using one of the truncation/rounding functions like Int, Trunc, Floor, Ceil, etc. as we’ll see later in this post.

By default, the rounding control is set to rounding to the nearest (even) which seems to be correct for most calculations. However, this might cause an unexpected behavior, for instance 2.5 is not rounded to 3, but rounded to 2. The FPU prefers the nearest even value when the decimal part is exactly .5. For the same reason 3.5 is rounded to 4. This is useful in case of statistical analyzing where you want to spread numbers evenly when they are exactly in the middle of two integers. However, this might not always be what you want. For instance, by default C/C++ compilers always convert floating point values by truncating them down towards zero (cast to Int).

If you don’t care and if you are happy with the GB’s default rounding, you can easily convert a floating point value to integer by simply assigning the one to the other:

Dim f As Float = 2.5, i As Int
i = f     ' assign float to int
Debug i   ' = 2

This is the fastest possible conversion, it takes only one assembler instruction (fistp) to convert a floating point as shown in the disassembly (without the Debug command). See for more details of disassembling GB-code the blogpost Anatomy of a procedure.

--------  Disassembly -----------------------------------
0 - (Sub Main) (Lines=2)
0559C350: B8 2E 00 00 00                 mov     eax,0x0000002E
0559C355: FF 15 40 1A 4D 00              scall   INITPROC0 ; Ocx: $180277CB
0559C35B: F8                             clc    
0559C35C: FF 55 B4                       call    dpt -76[ebp] ; @Tron
0559C35F: C7 05 10 A7 5C 06 00 00 20 40  mov     dpt [0x065CA710],0x40200000
0559C369: FF 55 B4                       call    dpt -76[ebp] ; @Tron
0559C36C: D9 05 10 A7 5C 06              fld     dpt [0x065CA710]
0559C372: DB 1D 14 A7 5C 06              fistp   dpt [0x065CA714]

0559C378: 8B 4D F0                       mov     ecx,dpt -16[ebp]
0559C37B: 64 89 0D 00 00 00 00           mov     dpt fs:[0x00000000],ecx
0559C382: 8B E5                          mov     esp,ebp
0559C384: 5D                             pop     ebp
0559C385: 5B                             pop     ebx
0559C386: 5F                             pop     edi
0559C387: 5E                             pop     esi
0559C388: C3                             ret
    

The interesting commands are fld and fistp. First fld loads the floating-point value into ST0 – the top of the stack - and then the fistp instruction pops the value off the floating-point stack. fistp converts it to an integer, and then stores it at the address specified. This instruction uses the rounding control settings to determine how they will convert the floating point data to an integer during the store operation.

There is one other assembler instruction that rounds to integer. The frndint instruction rounds the value in ST0 (the top of the stack) to the nearest integer using the rounding algorithm specified in the control register. The result remains in ST0 as a floating point value, it simply does not have a fractional component. GB uses the frndint instruction for its truncation functions Int(), Fix() and others.

The truncation functions
The function Trunc (or Fix) round towards zero. The Int (or Floor) function round towards negative infinity and Ceil rounds to positive infinity. Let’s start with the most often used truncation function Int(), or its synonym Floor(). We’ll use this simple program to look at it’s disassembly.

Dim f As Float = 2.5, i As Int
i = Int(f)   ' truncate down to negative infinity

The disassembly of the Int() function (without the surrounding commands):

0559C4EC: D9 05 10 A7 5C 06   fld     dpt [0x065CA710]
0559C4F2: D9 2D 1C 1A 4D 00   fldcw   V_RNDMINUS
0559C4F8: D9 FC               frndint
0559C4FA: D9 2D 14 1A 4D 00   fldcw   V_RNDNEAR
0559C500: DB 1D 14 A7 5C 06   fistp   dpt [0x065CA714]

First ST0 is loaded with the value in variable f, then the control word of the FPU is loaded with the value from a variable called V_RNDMINUS, which is 0x77. This value sets the rounding bits of the control word to %01. By executing frndint the value in ST0 is rounded to an integer using the setting “Round down towards negative infinity”. Int() rounds 2.5 to 2 and –2.5 to -3. After rounding, the control word is reset to the default value 0x372, which is stored in the runtime variable V_RNDNEAR. Finally, the value in ST0 is moved to the variable i with fistp,

Note Technically fld and fistp aren’t part of the Int() function. How a value ends up in ST0 depends on the code of the program. Similarly, fistp is only inserted if the result of Int() is to be stored in a variable. If Int() is used inside an expression the value remains in ST0.

In the same way you can create samples for the other truncation functions Trunc(), Fix(), and Ceil() and then examine their disassembly output. Trunc() and its synonym Fix() load the control word with the value stored in V_RNDZERO ( = 0xF72). This value sets the rounding bits to %11 to round towards zero. For Ceil() the rounding bits are set to %10, the value for the control word is obtained from the variable V_RNDPLUS ( = 0xB72), and frndint then rounds to positive infinity.

The QRound function
The QRound function is an addition to the truncation/rounding functions. The compiler generates only one instruction for this function: the frndint instruction to round the value in ST0. The compiler does not load the control word prior to executing the rounding.  QRound uses the current control word setting (0x372) and “Round to nearest (even)”. QRound is useful inside a mathematical expression where some interim outcome needs to be rounded (converted) to integer using the current control word setting. Since the interim outcome remains in ST0 (without the fractional part) a (complex) expression can be evaluated more quickly. In short the steps for variable = QRound(float) are:

fld float
frndint
fistp variable

Again, fld and fistp are not part of the function itself. Since QRound is mostly used with the default control word setting rounding is the same as when a float is assigned to an integer variable directly, as demonstrated at the beginning of this blogpost.

Note that using the assembler instruction fldcw prior to QRound you can determine your own float-to-integer conversion. Do not forget to return the value of the control word back to 0x372 afterwards.

Use Round for proper rounding
The Round function generates the same assembler instructions as Int(). However before frndint is executed the value in ST0 is increased with 0.5. The value in ST0 is then rounded towards negative infinity. In short, these steps are:

fld value
fadd 0.5 / fldcw 0x772 / frndint / fldcw 0x372
fistp variable

If your program wants “proper rounding” it should use Round() to convert a floating-point value to an integer.

18 January 2020

High resolution timer wrapped in a COM object

Only recently I needed a timer with a shorter interval than that the Ocx Timer can provide. The Ocx Timer smallest interval is 15.625 ms – 64 ticks per second - where I needed an interval of 10 ms to receive 100 timer-events per second. After some research I decided to use the API function CreateTimerQueueTimer() as a high-resolution timer. For a discussion on available timers see this Code Project article. I didn’t use the multi-media timers because MS advises against it, because these timers increase the system clock’s frequency which leads to a drain of battery-power on mobile devices. Nevertheless, the CreateTimerQueueTimer() API only produces shorter intervals than 15.625 ms if the the application’s system clock is adjusted as well using the multimedia function timeBeginPeriod(). This function cannot be used to increase the resolution of the SetTimer API which is used by the Ocx Timer.

A resource must be deleted
As with most Windows resources the queued timer comes with a create- and a release function. The created timer is released using the Windows API DeleteTimerQueueTimer() and is (usually) invoked when the program terminates. In addition, at the very end of the application, the system timer must be reset using timeEndPeriod(). The most common scenario for a GB program is outlined in this simple program:

$Library "mmsystem.inc"
OpenW 1
Global timerHandle As Handle, param As Large
' Create a 10 ms timer with ID=1 for Me
param = MakeLargeHiLo(Me.hWnd, 1)   ' assemble handle and ID
~timeBeginPeriod(1)
CreateTimerQueueTimer(timerHandle, Null, _
  ProcAddr(TimerQProc), V:param, 0, 10, WT_EXECUTEINTIMERTHREAD)
Do
  Sleep
Until Me Is Nothing
DeleteTimerQueueTimer(Null, timerHandle, Null)
~timeEndPeriod(1)

Proc TimerQProc(ByVal pParameter As Long, ByVal TimerOrWaitFired As Long) Naked
  ' Process timer event
  Dim pL As Pointer Large, hWnd As Handle, ID As Long
  Pointer pL = pParameter
  hWnd = HiLarge(pL), ID = LoLarge(pL)
EndProc

This sample only shows the general structure of a program that uses a Windows timer resource, the structure of the program is the same if it uses some other Windows resource. In this scenario a Windows resource is allocated before entering the message loop and released after the message loop has finished and the last Form has closed. However, when the program unexpectedly stops with a runtime-error the code below the message loop is never executed! This leads to unreleased Windows resources, something you don’t want. When GB raises a runtime error it stops at the line the error occurred and halts further execution of the program. The program’s windows (Forms) remain on the screen waiting to be closed or ‘cleaned up’ by using the wipe-window button in the IDE’s toolbar. Closing the remaining windows this way does not trigger any event subs like - for instance - the Form_Destroy event sub. Consequently, it is  pointless to move the resource delete function to this event sub, because it is not executed once the program stopped with a runtime error.

Each time the program is run within the IDE and stops with a runtime error it does not release the allocated resources. But, this is also true for the GB function mAlloc calls that require a call to mFree to release the memory. We need a way to release allocated resources under all circumstances. 

Using a COM wrapper
When GB stops executing after a runtime error it still releases GB resources, it closes I/O channels and deletes any TempFileName files, and finally it clears all the program’s global variables. For dynamic variables types (String, Object, arrays, hashes) the allocated memory is freed as well. (Therefore, it is sometimes better to use a string to allocate memory than to use mAlloc, strings are freed automatically.) For global variables that hold a COM object GB calls the Release vtable function of the IUnknown interface that each COM object implements. So, if we could wrap the resource handling in a (minimal) COM wrapper and store it in an Object type we are assured the Release function is called and we can properly delete the resource in the object’s Release function. This way we’re able to free the resources under all conditions.

If you’re not familiar with COM objects and the IUnknown implementation you might read a previous post first: COM in GB32 – IUnknown. The rest of this post discusses how to create a minimal COM wrapper for the queued timer APIs.

The minimal COM wrapper
The following full working sample creates a queued timer in the QueTimer function which returns an Object that holds a reference to the minimal COM object it creates. A COM object must at least implement the IUnknown interface that consists of the QueryInterface, AddRef and Release functions. Since this COM object doesn’t support any other interfaces (except IUnknown) we simply return with E_NOTIMPL from the QueryInterface function. The COM object is built manually in code and cannot be created by a function like CreateObject(). As a result QueryInterface is never called. The AddRef and Release functions require a proper implementation since these vtable functions are called by GB’s Set command.
The vtable functions must have the Naked attribute, or at least a $StepOff command, to prevent the GB compiler from inserting Tron code which can result in nasty and hard to find bugs. This is also true for any callback function Windows calls; the QueTimer callback procedure needs the Naked attribute as well.

$Library "mmsystem.inc"

OpenW 1, 0, 0, 300, 300, 48
PrintScroll = 1 : PrintWrap = 1

Global Object tmrQ1, tmrQ2
Set tmrQ1 = QueTimer(Me, 1, 10)   ' ID=1, 10 msec
Set tmrQ2 = QueTimer(Me, 2, 1000) ' ID=2, 1000 msec

Global Long Count, CountToErr
Do
  Sleep
Until Me Is Nothing

Sub Win_1_Message(hWnd%, Mess%, wParam%, lParam%)
  ' Process the WM_TIMER
  Static Long CountToErr
  If Mess% = WM_TIMER
    If wParam% == 1
      Count++
      Print ".";      // do something
    ElseIf wParam% == 2
      TitleW 1, "Timer Events/s:" + Str(Count) : Count = 0
      ' Interrupt GB with a runtime error after 10 sec
      CountToErr++ : If CountToErr = 10 Then Error 3
    EndIf
  EndIf
EndSub

Proc QueTimerProc(ByVal pParameter As Long, ByVal TimerOrWaitFired As Long) Naked
  ' Callback function
  Local hWnd As Handle, id As Long, pObj As Pointer IQueTimer
  Pointer pObj = pParameter             ' holds address of a IQueTimer object
  ~PostMessage(pObj.hWndTarget, WM_TIMER, pObj.TimerID, 0)
EndProc


Function QueTimer(frm As Form, id As Long, mSec As Long) As Object
  ' Create high-resolution timer wrapped in a minimal COM object.
  Global Long g_IQueTimerCnt

  Type IQueTimer         ' definition of object
    lpVtbl As Long
    refcount As Long
    Handle As Handle
    hWndTarget As Handle
    TimerID As Long
  EndType

  ' Set up the IUnknown vtable, same for each object
  Static vTable(0 .. 2) As Long    ' must remain in memory
  If vTable(0) == 0                ' do this only once
    vTable(0) = ProcAddr(IQueTimerVtbl_QueryInterface)
    vTable(1) = ProcAddr(IQueTimerVtbl_AddRef)
    vTable(2) = ProcAddr(IQueTimerVtbl_Release)
  EndIf

  ' Alloc and clear an IQueTimer object (Type) and
  ' assign it to an IQueTimer pointer.
  Local pObj As Pointer IQueTimer
  Pointer pObj = cAlloc(1, SizeOf(IQueTimer))

  ' Initialize the IQueTimer object
  pObj.lpVtbl = ArrayAddr(vTable())     ' set vtable
  pObj.refcount = 1                     ' set refcount
  pObj.hWndTarget = frm.hWnd            ' target window
  pObj.TimerID = id                     ' timer ID

  ' Create the API timerqueue resource and
  ' if succesfull finish the COM object, otherwise
  ' free the allocated memory.
  Local timerHandle As Handle
  If CreateTimerQueueTimer(timerHandle, Null, _
    ProcAddr(QueTimerProc), Pointer(pObj), 0, mSec, WT_EXECUTEINTIMERTHREAD)
    '  store the resource handle in the object
    pObj.Handle = timerHandle

    ' Set the system's clock resolution to 1 ms,
    ' do this only once per application.
    If g_IQueTimerCnt == 0 Then ~timeBeginPeriod(1)
    g_IQueTimerCnt++            ' count the number of instances

    ' Return COM object as Object
    {V:QueTimer} = Pointer(pObj)

  Else    ' something went wrong, release already allocated resource(s)
    ~mFree(Pointer(pObj))     ' free the alloced memory

    ' Do not set returnvalue to return Nothing
  EndIf
EndFunc

Function IQueTimerVtbl_QueryInterface(ByRef This As IQueTimer, _
  ByVal riid As Long, ByVal ppvObject As Long) As Long Naked
  Return E_NOTIMPL
EndFunc

Function IQueTimerVtbl_AddRef(ByRef this As IQueTimer) As Long Naked
  this.refcount++
  Return this.refcount
EndFunc

Function IQueTimerVtbl_Release(ByRef this As IQueTimer) As Long Naked
  this.refcount--
  If this.refcount == 0
    MsgBox "terminating" ' remove comment to see that Release is called
    DeleteTimerQueueTimer(Null, this.Handle, Null)
    g_IQueTimerCnt--     ' decrease instance counter
    ' If all instances are released, reset the system clock
    If g_IQueTimerCnt == 0 Then ~timeEndPeriod(1)
    ~mFree(*this)
  EndIf
  Return this.refcount
EndFunc

' Declares and Constants
Declare Function CreateTimerQueueTimer Lib "kernel32" (ByRef hNewTimer As Handle, _
  ByVal hTimer As Handle, ByVal Callbck As Long, ByVal Parameter As Long, _
  ByVal DueTime As Long, ByVal Period As Long, ByVal Flags As Long) As Long

Declare Function DeleteTimerQueueTimer Lib "kernel32" (ByVal hTimer As Handle, _
  ByVal Timer As Handle, ByVal CompletionEvent As Handle) As Long

Global Const E_NOTIMPL = 0x80004001
Global Const WT_EXECUTEINTIMERTHREAD = 0x00000020

The program creates two high-resolution timers and stores the minimal COM wrappers in the Object variables tmrQ1 and tmrQ2. The second timer is used to display the number of timer events per second produced by timer 1. There is nothing you can do with the Object variables, the minimal COM wrapper does not support any properties or methods. The Set command is the only command that can be used on these objects. The only reason for the existence of these Object variables is to sit and wait to be released so that the resources can be deleted properly. In fact, you could collect all globally used resources into the creation function – here QueTimer() - and release them in the Release vtable function.
To demonstrate the proper calling of the object’s Release the program raises an error after 10 seconds. A message box pops up to show you that Release is invoked after a runtime error.

Finally
If you’re not familiar with the binary layout of a COM object and maybe having trouble understanding how the COM object is build, don’t worry. You can copy paste this code to create your own minimal COM wrapper, simply replace the string ‘QueTimer’ with a name of your own (do not select Whole Word in the Replace dialog box). Then replace the code that creates and deletes the Windows resource with the functions you require. Of course you will need to edit the IQueTimer type that holds the information for a particular COM object.