Author Topic: PB Floating point variables  (Read 14609 times)

0 Members and 1 Guest are viewing this topic.

Offline Charles Pegge

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 704
    • Charles Pegge
Re: PB Floating point variables Text encoding/Decoding functions
« Reply #15 on: January 09, 2008, 04:45:59 PM »
Updated: 10 Jan 2008: to prevent encoder from reversing the sign of the original number.

Here are the Extended Number Text encoding/decoding functions as envisaged. They are fairly close to max efficiency at around 100 clocks each (theoretically, I have not timed them). Since they operate on the binary number directly, there are no precision losses.

In a database alpha sorting on the text codes will put the values into ascending order. Negative numbers are handled correctly in this regard. Range selection should also be possible by using encoded range values.

Code: [Select]
' Extended Number Text encoder/decoder functions
' Charles  E V Pegge
' 10 Jan 2008

#COMPILE EXE
#DIM ALL

SUB EncodeExt( n AS EXTENDED, s AS ASCIIZ )
    #REGISTER NONE
    '----------------'
    ! mov esi,n      ' number pointer
    ! mov edi,s      ' encoded string pointer
    ! ADD esi,9      ' offset to end of number
    '----------------'
    ! mov ecx,5      ' 5 loops to process 2 bytes and produce 4 bytes
                     ' in each cycle
    '----------------'
    nex4:            '
    ! mov dl,[esi]   ' load digit (we go from msb to lsb)
    ! mov dh,dl      ' prepare to split the nybbles
    ! shr dl,4       ' most significant nybble in dl
    ! AND edx,&h0f0f ' mask unwanted bits to leave the nybbles
    ! mov eax,edx    ' move the result to eax
    ! dec esi        ' decrement number digit pointer
    '----------------'
    ! mov dl,[esi]   ' get the next 2 nybbles
    ! mov dh,dl      ' split them
    ! shr dl,4       ' dl to contain the highest
    ! AND edx,&h0f0f ' mask off unwanted bits
    ! shl edx,16     ' move along 2 bytes in the string
    ! ADD eax,edx    ' combine with the previous 2 bytes
    ! ADD eax,&h41414141 ' add 'A' to each nybble to encode 'A..P'
    ! mov [edi],eax  ' store the 4 bytes into the string
    ! ADD edi,4      ' advance the string pointer for next.
    '----------------'
    ! dec esi        ' decrement the number pointer
    ! dec ecx        ' decrement the cycle counter
    ! jg nex4        ' repeat cycle until 0 (10 bytes of number 20 bytes of code)
    ! xor byte ptr [edi-20],8 ' invert sign bit
    '----------------'
END SUB


SUB DecodeExt(n AS EXTENDED, s AS ASCIIZ)
    #REGISTER NONE
    '----------------'
    ! mov esi,n      ' number pointer
    ! mov edi,s      ' encoded string pointer
    ! add esi,9      ' offset to last byte of number
    '----------------'
    ! mov ecx,5       '
    nex4:            '
    ! mov eax,[edi]  '
    ! add edi,4      ' advance pointer for next 4 chars
    ! sub eax,&h41414141 ' subtract ascii offsets
    '----------------'
    ! mov dh,al      ' get higher nybble
    ! shl dh,4       ' mov it up *16
    ! mov dl,ah      ' hold it in dl
    ! and dl,&hf     ' mask off unwanded bits
    ! add dl,dh      ' add higher to lower nybble
    ! mov [esi],dl   ' store the recomposed byte
    ! dec esi        ' work backwards towards lsb of number
    ! shr eax,16      ' shift right to get the next 2 chars
    '----------------'
    ! mov dh,al      ' store as higher nybble
    ! shl dh,4       ' shift into position
    ! mov dl,ah      ' move to dl
    ! and dl,&hf     ' mask unwanted bits
    ! add dl,dh      ' add higher to lower
    ! mov [esi],dl   ' store the recomposed byte to the number
    ! dec esi        ' move left for the next lower byte
    ! shr eax,16      ' shift for the next 2 chars
    '----------------'
    ! dec ecx        '
    ! jg nex4        '
    ! xor byte ptr [esi+10], &h80 ' invert sign bit
    '----------------'

END SUB


FUNCTION PBMAIN () AS LONG
    LOCAL n,m,v AS EXTENDED
    LOCAL s,t AS ASCIIZ*21
    n=12345678912.3456789
    'n=0
    'n=1
    'n=.1
    'n=.001
    EncodeExt n,s
    DecodeExt m,s
    EncodeExt m,t
    v=m-n
    MSGBOX s+$CR+t+$CR+"Value Difference check: "+STR$(v,18)
END FUNCTION

« Last Edit: January 10, 2008, 07:09:48 AM by Charles Pegge »

MikeTrader

  • Guest
Re: PB Floating point variables
« Reply #16 on: January 09, 2008, 10:06:32 PM »
THAT is very cool Charles!
Considering that a DOUBLE is probably more than enough range for this project, I could use the EXTENDED data type and just round off all displayed numbers by using STR$ for the first 16 significant digits to avoid displaying rounding errors.

I included some addition and subtraction in the following test even tho division and multiplication are repeated addition and subtraction as I suspect the DECIMAL datatype may have some additional overhead for the mult and div operations.

This test assumes that I am required to pull a couple of values out of the database, perform a calculation and store the result. The DOUBLE can be stored and calculated directly. The DECIMAL would need some sort of TEXT/QUAD encoding method (TEXT encoding Functions used to simulate overhead). The EXTENDED type uses the TEXT encoding.

The results are interesting. The DECIMAL calculation speed varies significantly
For example,
Using 523.34 for Dec1 and 3 for Dec2
        RetVal = VarDecMul(Dec1, Dec2, Dec3) '
        RetVal = VarDecDiv(Dec1, Dec2, Dec3) '
takes 520 clks yet
        RetVal = VarDecMul(Dec1, Dec2, Dec3) '
        RetVal = VarDecDiv(Dec3, Dec2, Dec1) '
takes only 120 Clks!



In this test I used your large EXTENDED number and got:

Clks= 166, DOUBLE VAL = 12345678912.3457

Clks= 197, EXTENDED VAL = 12345678912.3457

Clks= 915, TEXT EXT VAL =-12345678912.3456789  ' <------ I think there is a sign problem?

Clks= 1032, DECIMAL Result =12345678912.3457

Code: [Select]
     

#COMPILE EXE
#DIM ALL
     
#INCLUDE "Decimal.inc"

GLOBAL hDbg AS LONG             
               
             
'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
SUB EncodeExt( n AS EXTENDED, s AS ASCIIZ )
    #REGISTER NONE
    '----------------'
    ! mov esi,n      ' number pointer
    ! mov edi,s      ' encoded string pointer
    ! ADD esi,9      ' offset to end of number
    '----------------'
    ! XOR BYTE PTR [esi],&h80  ' invert sign bit
    ! mov ecx,5      ' 5 loops to process 2 bytes and produce 4 bytes
                     ' in each cycle
    '----------------'
    nex4:            '
    ! mov dl,[esi]   ' load digit (we go from msb to lsb)
    ! mov dh,dl      ' prepare to split the nybbles
    ! shr dl,4       ' most significant nybble in dl
    ! AND edx,&h0f0f ' mask unwanted bits to leave the nybbles
    ! mov eax,edx    ' move the result to eax
    ! dec esi        ' decrement number digit pointer
    '----------------'
    ! mov dl,[esi]   ' get the next 2 nybbles
    ! mov dh,dl      ' split them
    ! shr dl,4       ' dl to contain the highest
    ! AND edx,&h0f0f ' mask off unwanted bits
    ! shl edx,16     ' move along 2 bytes in the string
    ! ADD eax,edx    ' combine with the previous 2 bytes
    ! ADD eax,&h41414141 ' add 'A' to each nybble to encode 'A..P'
    ! mov [edi],eax  ' store the 4 bytes into the string
    ! ADD edi,4      ' advance the string pointer for next.
    '----------------'
    ! dec esi        ' decrement the number pointer
    ! dec ecx        ' decrement the cycle counter
    ! jg nex4        ' repeat cycle until 0 (10 bytes of number 20 bytes of code)
    '----------------'
END SUB

   
'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
SUB DecodeExt(n AS EXTENDED, s AS ASCIIZ)
    #REGISTER NONE
    '----------------'
    ! mov esi,n      ' number pointer
    ! mov edi,s      ' encoded string pointer
    ! ADD esi,9      ' offset to last byte of number
    '----------------'
    ! mov ecx,5       '
    nex4:            '
    ! mov eax,[edi]  '
    ! ADD edi,4      ' advance pointer for next 4 chars
    ! SUB eax,&h41414141 ' subtract ascii offsets
    '----------------'
    ! mov dh,al      ' get higher nybble
    ! shl dh,4       ' mov it up *16
    ! mov dl,ah      ' hold it in dl
    ! AND dl,&hf     ' mask off unwanded bits
    ! ADD dl,dh      ' add higher to lower nybble
    ! mov [esi],dl   ' store the recomposed byte
    ! dec esi        ' work backwards towards lsb of number
    ! shr eax,16      ' shift right to get the next 2 chars
    '----------------'
    ! mov dh,al      ' store as higher nybble
    ! shl dh,4       ' shift into position
    ! mov dl,ah      ' move to dl
    ! AND dl,&hf     ' mask unwanted bits
    ! ADD dl,dh      ' add higher to lower
    ! mov [esi],dl   ' store the recomposed byte to the number
    ! dec esi        ' move left for the next lower byte
    ! shr eax,16      ' shift for the next 2 chars
    '----------------'
    ! dec ecx        '
    ! jg nex4        '
    ! XOR BYTE PTR [esi+10], &h80 ' invert sign bit
    '----------------'

END SUB



'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
SUB time_stamp_count(tick AS QUAD) ' CPU Clock count    Charles e1 V Pegge

  '---------------------------'
  '                           ' approx because it is not a serialised instruction
  '                           ' it may execute before or after other instructions
  '                           ' in the pipeline.
  ! mov ebx,tick              ' var address where count is to be stored.
  ! db  &h0f,&h31             ' RDTSC read time-stamp counter into edx:eax hi lo.
  ! mov [ebx],eax             ' save low order 4 bytes.
  ! mov [ebx+4],edx           ' save high order 4 bytes.
  '---------------------------'

END SUB
           

'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
FUNCTION PBMAIN
               
  LOCAL i, Rounding, nLoops, RetVal, LONGvar AS LONG 
  LOCAL cBeg, cEnd AS QUAD  ' for time stamp, measuring cpu clock cycles
  LOCAL d1, d2 AS DOUBLE
  LOCAL e1, e2 AS EXTENDED
  LOCAL s, sTemp AS STRING
  LOCAL Dec1, Dec2, Dec3 AS Decimal   
  LOCAL z1, z2 AS ASCIIZ*21

               
  hDbg = FREEFILE '
  OPEN "DecimalDebug.txt" FOR OUTPUT LOCK WRITE AS hDbg ' PRINT #hDbg, "MetersToFt="+STR$(MetersToFt)
       
     

    nLoops = 1000000
               
    e1 = 12345678912.3456789## 
    e2 = 523.34##

                     
    d1 = e1
    d2 = e2
    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        d1 = d1 + d2 
        d1 = d1 * d2 
        d1 = d1 / d2
        d1 = d1 - d2 
      NEXT
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DOUBLE VAL =" + STR$(d1) + $CRLF + $CRLF
    '=======================
                 
                   

    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        e1 = e1 + e2 
        e1 = e1 * e2 
        e1 = e1 / e2 
        e1 = e1 - e2
      NEXT
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", EXTENDED VAL =" + STR$(e1) + $CRLF + $CRLF
    '=======================
                 
                   
    EncodeExt e1, z1   
    EncodeExt e2, z2
'   s = s + "TEXT z1 Encoded="+z1 + ", TEXT z2 Encoded="+z2 + $CRLF + $CRLF
    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        DecodeExt e1, z1
        DecodeExt e2, z2
        e1 = e1 + e2 
        e1 = e1 * e2 
        e1 = e1 / e2 
        e1 = e1 - e2
        EncodeExt e1, z1 
      NEXT
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", TEXT EXT VAL =" + STR$(e1,18) + $CRLF + $CRLF
    '=======================

   

    RetVal  = VarDecFromR8(d1, Dec1) ' prepare Dec2
    RetVal  = VarDecFromR8(d2, Dec2) ' prepare Dec2 
    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        DecodeExt e1, z1
        DecodeExt e2, z2
        RetVal = VarDecAdd(Dec1, Dec2, Dec3) '
        RetVal = VarDecMul(Dec3, Dec2, Dec1) '
        RetVal = VarDecDiv(Dec1, Dec2, Dec3) '
        RetVal = VarDecSub(Dec3, Dec2, Dec1) '   
        EncodeExt e1, z1 
      NEXT '
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks 

    CALL VarBstrFromDec(Dec1, 0, 0, sTemp )
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DECIMAL Result =" + ACODE$(sTemp) + $CRLF + $CRLF
    '=======================
               


PRINT #hDbg, s
                   
MSGBOX s,64,"All Done"  : EXIT FUNCTION


  CLOSE hDbg

END FUNCTION

'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

Offline Charles Pegge

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 704
    • Charles Pegge
Re: PB Floating point variables
« Reply #17 on: January 10, 2008, 07:07:27 AM »
Thanks Mike,
The problem was in the Encoder which reversed  the sign of the original number. - Slipped through my tests!

I have also tweaked the test to use one encode and one decode only.

My results with an AMD64dual CPU are:

DOUBLE 71 clocks

EXTENDED 163 clocks

EXTENDED+TEXT ENCODING/DECODING 307 clocks 

DECIMAL 444 clocks
(with the extra bytes this will expand to about 480 clocks)


Code: [Select]
#COMPILE EXE
#DIM ALL

#INCLUDE "Decimal.inc"

GLOBAL hDbg AS LONG


'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
SUB EncodeExt( n AS EXTENDED, s AS ASCIIZ )
    #REGISTER NONE
    '----------------'
    ! mov esi,n      ' number pointer
    ! mov edi,s      ' encoded string pointer
    ! ADD esi,9      ' offset to end of number
    '----------------'
    ! mov ecx,5      ' 5 loops to process 2 bytes and produce 4 bytes
                     ' in each cycle
    '----------------'
    nex4:            '
    ! mov dl,[esi]   ' load digit (we go from msb to lsb)
    ! mov dh,dl      ' prepare to split the nybbles
    ! shr dl,4       ' most significant nybble in dl
    ! AND edx,&h0f0f ' mask unwanted bits to leave the nybbles
    ! mov eax,edx    ' move the result to eax
    ! dec esi        ' decrement number digit pointer
    '----------------'
    ! mov dl,[esi]   ' get the next 2 nybbles
    ! mov dh,dl      ' split them
    ! shr dl,4       ' dl to contain the highest
    ! AND edx,&h0f0f ' mask off unwanted bits
    ! shl edx,16     ' move along 2 bytes in the string
    ! ADD eax,edx    ' combine with the previous 2 bytes
    ! ADD eax,&h41414141 ' add 'A' to each nybble to encode 'A..P'
    ! mov [edi],eax  ' store the 4 bytes into the string
    ! ADD edi,4      ' advance the string pointer for next.
    '----------------'
    ! dec esi        ' decrement the number pointer
    ! dec ecx        ' decrement the cycle counter
    ! jg nex4        ' repeat cycle until 0 (10 bytes of number 20 bytes of code)
    ! xor byte ptr [edi-20],8 ' invert sign bit
    '----------------'
END SUB


'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
SUB DecodeExt(n AS EXTENDED, s AS ASCIIZ)
    #REGISTER NONE
    '----------------'
    ! mov esi,n      ' number pointer
    ! mov edi,s      ' encoded string pointer
    ! ADD esi,9      ' offset to last byte of number
    '----------------'
    ! mov ecx,5      '
    nex4:            '
    ! mov eax,[edi]  '
    ! ADD edi,4      ' advance pointer for next 4 chars
    ! SUB eax,&h41414141 ' subtract ascii offsets
    '----------------'
    ! mov dh,al      ' get higher nybble
    ! shl dh,4       ' mov it up *16
    ! mov dl,ah      ' hold it in dl
    ! AND dl,&hf     ' mask off unwanded bits
    ! ADD dl,dh      ' add higher to lower nybble
    ! mov [esi],dl   ' store the recomposed byte
    ! dec esi        ' work backwards towards lsb of number
    ! shr eax,16      ' shift right to get the next 2 chars
    '----------------'
    ! mov dh,al      ' store as higher nybble
    ! shl dh,4       ' shift into position
    ! mov dl,ah      ' move to dl
    ! AND dl,&hf     ' mask unwanted bits
    ! ADD dl,dh      ' add higher to lower
    ! mov [esi],dl   ' store the recomposed byte to the number
    ! dec esi        ' move left for the next lower byte
    ! shr eax,16      ' shift for the next 2 chars
    '----------------'
    ! dec ecx        '
    ! jg nex4        '
    ! XOR BYTE PTR [esi+10], &h80 ' invert sign bit
    '----------------'

END SUB



'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
SUB time_stamp_count(tick AS QUAD) ' CPU Clock count    Charles e1 V Pegge

  '---------------------------'
  '                           ' approx because it is not a serialised instruction
  '                           ' it may execute before or after other instructions
  '                           ' in the pipeline.
  ! mov ebx,tick              ' var address where count is to be stored.
  ! db  &h0f,&h31             ' RDTSC read time-stamp counter into edx:eax hi lo.
  ! mov [ebx],eax             ' save low order 4 bytes.
  ! mov [ebx+4],edx           ' save high order 4 bytes.
  '---------------------------'

END SUB


'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§'
FUNCTION PBMAIN

  LOCAL i, Rounding, nLoops, RetVal, LONGvar AS LONG
  LOCAL cBeg, cEnd AS QUAD  ' for time stamp, measuring cpu clock cycles
  LOCAL d1, d2 AS DOUBLE
  LOCAL e1, e2 AS EXTENDED
  LOCAL s, sTemp AS STRING
  LOCAL Dec1, Dec2, Dec3 AS Decimal
  LOCAL z1, z2 AS ASCIIZ*21


  hDbg = FREEFILE '
  OPEN "DecimalDebug.txt" FOR OUTPUT LOCK WRITE AS hDbg ' PRINT #hDbg, "MetersToFt="+STR$(MetersToFt)



    nLoops = 1000000

    e1 = 12345678912.3456789##
    e2 = 523.34##


    d1 = e1
    d2 = e2
    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        d1 = d1 + d2
        d1 = d1 * d2
        d1 = d1 / d2
        d1 = d1 - d2
      NEXT
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DOUBLE VAL =" + STR$(d1) + $CRLF + $CRLF
    '=======================



    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        e1 = e1 + e2
        e1 = e1 * e2
        e1 = e1 / e2
        e1 = e1 - e2
      NEXT
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", EXTENDED VAL =" + STR$(e1) + $CRLF + $CRLF
    '=======================


    EncodeExt e1, z1
    EncodeExt e2, z2
'   s = s + "TEXT z1 Encoded="+z1 + ", TEXT z2 Encoded="+z2 + $CRLF + $CRLF
    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        'DecodeExt e1, z1
        'DecodeExt e2, z2
        e1 = e1 + e2
        e1 = e1 * e2
        e1 = e1 / e2
        e1 = e1 - e2
        EncodeExt e1, z1
        DecodeExt e1, z1
      NEXT
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", TEXT EXT VAL =" + STR$(e1,18)+$CRLF +z1+ $CRLF+$CRLF
    '=======================



    RetVal  = VarDecFromR8(d1, Dec1) ' prepare Dec2
    RetVal  = VarDecFromR8(d2, Dec2) ' prepare Dec2
    time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
      FOR i = 1 TO nLoops
        EncodeExt e1, z1
        DecodeExt e1, z1
        RetVal = VarDecAdd(Dec1, Dec2, Dec3) '
        RetVal = VarDecMul(Dec3, Dec2, Dec1) '
        RetVal = VarDecDiv(Dec1, Dec2, Dec3) '
        RetVal = VarDecSub(Dec3, Dec2, Dec1) '
        'EncodeExt e1, z1
      NEXT '
    time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks

    CALL VarBstrFromDec(Dec1, 0, 0, sTemp )
    s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DECIMAL Result =" + ACODE$(sTemp) + $CRLF + $CRLF
    '=======================



PRINT #hDbg, s

MSGBOX s,64,"All Done"  : EXIT FUNCTION


  CLOSE hDbg

END FUNCTION

'§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§
« Last Edit: January 10, 2008, 10:54:21 AM by Charles Pegge »