[RDD} Moved enum out of global namespace

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//

USING System.Text
USING XSharp.RDD.Support
USING XSharp.RDD.CDX

BEGIN NAMESPACE XSharp.RDD.Enums
INTERNAL ENUM FlexArrayTypes
    MEMBER NIL      := 0
    MEMBER UChar    := 1       // 1 byte
    MEMBER Char     := 2       // 1 byte
    MEMBER Short    := 3       // 2 bytes
    MEMBER UShort   := 4      // 2 bytes
    MEMBER Long     := 5      // 4 bytes
    MEMBER String32 := 6      // 4 bytes length followed by string
    MEMBER String16 := 7      // 2 bytes length followed by string
    MEMBER Float    := 8      // 10 bytes
    MEMBER Double   := 9      // 8 bytes
    MEMBER Date     := 10     // 4 bytes
    MEMBER Logic    := 11     // 1 byte
    MEMBER Array    := 12     // 2 bytes length followed by array
    MEMBER CodeBlock:= 13     // ?
    MEMBER DateJ    := 14     // 4 bytes
    MEMBER Double2  := 15       // len, dec, 8 byte double
    MEMBER Cyclic   := 16       // Cyclic array, stored how ?
    MEMBER UCHar1   := 17       // byte, dec
    MEMBER Char1    := 18       // char, dec
    MEMBER Short1   := 19       // 2, followed by len
    MEMBER UShort1  := 20       // 2, followed by len
    MEMBER Long1    := 21       // 4, followed by len
    MEMBER Unused   := 22       // no data
    MEMBER Object   := 23       // ?
    MEMBER Null     := 24       // no data
    MEMBER True     := 25
    MEMBER False    := 26
    MEMBER LDouble  := 27
    MEMBER UCHar2   := 28       // byte[1], len, dec
    MEMBER CHar2    := 29       // byte[1], len, dec
    MEMBER Short2   := 30       // short[2], len, dec
    MEMBER UShort2  := 31       // ushort[2], len, dec
    MEMBER Long2    := 32       // long[4], len, dec
    MEMBER ULong2   := 33       // ulong[4], len, dec
END ENUM

INTERNAL ENUM FlexFieldType
    MEMBER Picture      := 0
    MEMBER String       := 1
    MEMBER OleObject    := 2
    MEMBER FirstExtended := IndexBlock
    MEMBER IndexBlock   := 1000
    MEMBER Delete       := 1001
    MEMBER Array16      := 1002
    MEMBER Object16     := 1003
    MEMBER Array32      := 1004
    MEMBER Object32     := 1005
    MEMBER Nil          := 1006
    MEMBER LogicTrue    := 1007
    MEMBER LogicFalse   := 1008
    MEMBER JDate        := 1009
    MEMBER SByte        := 1010
    MEMBER Byte         := 1011
    MEMBER Short        := 1012
    MEMBER Word         := 1013
    MEMBER Long         := 1014
    MEMBER Dword        := 1015
    MEMBER Double       := 1016
    MEMBER Double10     := 1017
    MEMBER Compressed   := 1018
    MEMBER StringLong   := 1019
    MEMBER CompressedLong := 1020
    MEMBER LastExtended := CompressedLong
    MEMBER FirstExtended2  := ItemClipper
    MEMBER ItemClipper  := 10000
    MEMBER LogicLong    := 10001
    MEMBER StringEmpty  := 10002
    MEMBER LastExtended2  := StringEmpty
    MEMBER Illegal      := -1
END ENUM
END NAMESPACE

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//

USING System.Text
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING XSharp.RDD.CDX

INTERNAL ENUM FlexArrayTypes
    MEMBER NIL      := 0
    MEMBER UChar    := 1       // 1 byte
    MEMBER Char     := 2       // 1 byte
    MEMBER Short    := 3       // 2 bytes
    MEMBER UShort   := 4      // 2 bytes
    MEMBER Long     := 5      // 4 bytes
    MEMBER String32 := 6      // 4 bytes length followed by string
    MEMBER String16 := 7      // 2 bytes length followed by string
    MEMBER Float    := 8      // 10 bytes
    MEMBER Double   := 9      // 8 bytes
    MEMBER Date     := 10     // 4 bytes
    MEMBER Logic    := 11     // 1 byte
    MEMBER Array    := 12     // 2 bytes length followed by array
    MEMBER CodeBlock:= 13     // ?
    MEMBER DateJ    := 14     // 4 bytes
    MEMBER Double2  := 15       // len, dec, 8 byte double
    MEMBER Cyclic   := 16       // Cyclic array, stored how ?
    MEMBER UCHar1   := 17       // byte, dec
    MEMBER Char1    := 18       // char, dec
    MEMBER Short1   := 19       // 2, followed by len
    MEMBER UShort1  := 20       // 2, followed by len
    MEMBER Long1    := 21       // 4, followed by len
    MEMBER Unused   := 22       // no data
    MEMBER Object   := 23       // ?
    MEMBER Null     := 24       // no data
    MEMBER True     := 25
    MEMBER False    := 26
    MEMBER LDouble  := 27
    MEMBER UCHar2   := 28       // byte[1], len, dec
    MEMBER CHar2    := 29       // byte[1], len, dec
    MEMBER Short2   := 30       // short[2], len, dec
    MEMBER UShort2  := 31       // ushort[2], len, dec
    MEMBER Long2    := 32       // long[4], len, dec
    MEMBER ULong2   := 33       // ulong[4], len, dec
END ENUM

INTERNAL ENUM FlexFieldType
    MEMBER Picture      := 0
    MEMBER String       := 1
    MEMBER OleObject    := 2
    MEMBER FirstExtended := IndexBlock
    MEMBER IndexBlock   := 1000
    MEMBER Delete       := 1001
    MEMBER Array16      := 1002
    MEMBER Object16     := 1003
    MEMBER Array32      := 1004
    MEMBER Object32     := 1005
    MEMBER Nil          := 1006
    MEMBER LogicTrue    := 1007
    MEMBER LogicFalse   := 1008
    MEMBER JDate        := 1009
    MEMBER SByte        := 1010
    MEMBER Byte         := 1011
    MEMBER Short        := 1012
    MEMBER Word         := 1013
    MEMBER Long         := 1014
    MEMBER Dword        := 1015
    MEMBER Double       := 1016
    MEMBER Double10     := 1017
    MEMBER Compressed   := 1018
    MEMBER StringLong   := 1019
    MEMBER CompressedLong := 1020
    MEMBER LastExtended := CompressedLong
    MEMBER FirstExtended2  := ItemClipper
    MEMBER ItemClipper  := 10000
    MEMBER LogicLong    := 10001
    MEMBER StringEmpty  := 10002
    MEMBER LastExtended2  := StringEmpty
    MEMBER Illegal      := -1
END ENUM


[Runtime] Implemented ShowBitMap() and StretchBitMap()

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
#region functions

/// <summary>
/// </summary>
/// <param name="hWndOwner"></param>
/// <param name="cFiles"></param>
/// <param name="fAsynchWork"></param>
/// <returns>
/// </returns>
UNSAFE	 FUNCTION DoSendMail(hWndOwner AS IntPtr,cFiles AS STRING,fAsynchWork AS LOGIC) AS VOID
    /// THROW NotImplementedException{}
    RETURN

/// <summary>
/// Display a bitmap in a window or control.
/// </summary>
/// <param name="hWnd"></param>
/// <param name="cFileName"></param>
/// <param name="cTitle"></param>
/// <returns>
/// </returns>
FUNCTION ShowBitmap(hWnd AS IntPtr,cFileName AS STRING,cTitle AS STRING) AS LOGIC
    RETURN VOBitmaps.Show(hWnd, cFileName, cTitle)

/// <summary>
/// Display a bitmap stretched or shrunk to fit a window or control.
/// </summary>
/// <param name="hWnd"></param>
/// <param name="cFileName"></param>
/// <param name="cTitle"></param>
/// <returns>
/// </returns>
FUNCTION StretchBitmap(hWnd AS IntPtr,cFileName AS STRING,cTitle AS STRING) AS LOGIC
    RETURN VOBitmaps.Stretch(hWnd, cFileName, cTitle)

INTERNAL _DLL FUNCTION GlobalAlloc( uFlags AS DWORD, dwBytes AS DWORD ) AS IntPtr PASCAL:KERNEL32.GlobalAlloc
INTERNAL _DLL FUNCTION GlobalFree( hMem AS IntPtr ) AS IntPtr PASCAL:KERNEL32.GlobalFree
INTERNAL _DLL FUNCTION GlobalReAlloc( hMem AS IntPtr, dwBytes AS DWORD, uFlags AS DWORD )	AS IntPtr PASCAL:KERNEL32.GlobalReAlloc
INTERNAL _DLL FUNCTION GetFocus() AS IntPtr PASCAL:USER32.GetFocus
INTERNAL _DLL FUNCTION InvalidateRect( hwnd AS IntPtr, lpRect AS RECT, bErase AS LOGIC ) AS LOGIC PASCAL:USER32.InvalidateRect
INTERNAL _DLL FUNCTION GlobalLock( hMem AS IntPtr ) AS IntPtr PASCAL:KERNEL32.GlobalLock
INTERNAL _DLL FUNCTION GlobalUnlock( hMem AS IntPtr ) AS LOGIC PASCAL:KERNEL32.GlobalUnlock
INTERNAL _DLL FUNCTION SetWindowText( hwnd AS IntPtr, lpString AS STRING ) AS LOGIC PASCAL:USER32.SetWindowTextW Unicode
INTERNAL _DLL FUNCTION GetClientRect( hwnd AS IntPtr, lpRect AS RECT ) AS LOGIC PASCAL:USER32.GetClientRect
INTERNAL _DLL FUNCTION GetWindowRect( hwnd AS IntPtr, lpRect AS RECT ) AS LOGIC PASCAL:USER32.GetWindowRect
INTERNAL _DLL FUNCTION SetWindowPos( hwnd AS IntPtr, hwndInsertAfter AS IntPtr, x AS INT, y AS INT, cx AS INT, cy AS INT, uFlags AS DWORD ) AS LOGIC PASCAL:USER32.SetWindowPos
INTERNAL _DLL FUNCTION BeginPaint( hwnd AS IntPtr, lpPaint AS PAINTSTRUCT ) AS IntPtr PASCAL:USER32.BeginPaint
INTERNAL _DLL FUNCTION EndPaint( hwnd AS IntPtr, lpPaint AS PAINTSTRUCT ) AS LOGIC PASCAL:USER32.EndPaint
INTERNAL _DLL FUNCTION SelectPalette( hdc AS IntPtr, hpal AS IntPtr, bForceBackground AS LOGIC ) AS IntPtr PASCAL:GDI32.SelectPalette
INTERNAL _DLL FUNCTION RealizePalette( hdc AS IntPtr ) AS DWORD PASCAL:GDI32.RealizePalette
INTERNAL _DLL FUNCTION CreateCompatibleDC( hDc AS IntPtr ) AS IntPtr PASCAL:GDI32.CreateCompatibleDC
INTERNAL _DLL FUNCTION DeleteDC( hdc AS IntPtr ) AS LOGIC PASCAL:GDI32.DeleteDC
INTERNAL _DLL FUNCTION DeleteObject( hObject AS IntPtr ) AS LOGIC PASCAL:GDI32.DeleteObject
INTERNAL _DLL FUNCTION CreateCompatibleBitmap( hdc AS IntPtr, nWidth AS INT, nHeight AS INT ) AS IntPtr PASCAL:GDI32.CreateCompatibleBitmap
INTERNAL _DLL FUNCTION SelectObject( hdc AS IntPtr, hgdiobj AS IntPtr ) AS IntPtr PASCAL:GDI32.SelectObject
INTERNAL _DLL FUNCTION SetDIBitsToDevice( hdc AS IntPtr, xDest AS INT, YDest AS INT, dwWidth AS DWORD, deHeight AS DWORD, XSrc AS INT, YSrc AS INT, uStartScan AS DWORD, cScanLines AS DWORD,lpvBits AS IntPtr, lpbmi AS BITMAPINFO, fuColorUse AS DWORD )	AS INT PASCAL:GDI32.SetDIBitsToDevice
INTERNAL _DLL FUNCTION StretchDIBits( hdc AS IntPtr, XDest AS INT, yDest AS INT, nDestWidth AS INT, nDestHeight AS INT, XSrc AS INT, YSrc AS INT, nSrcWidth AS INT, nSrcHeight AS INT, lpBits AS IntPtr, lpBitsInfo AS BITMAPINFO,	iUsage AS DWORD, Rop AS DWORD ) AS INT PASCAL:GDI32.StretchDIBits
INTERNAL _DLL FUNCTION CreatePalette( lplgpl AS LOGPALETTE ) AS IntPtr PASCAL:GDI32.CreatePalette
INTERNAL _DLL FUNCTION GetDeviceCaps( hdc AS IntPtr, nIndex AS INT ) AS INT PASCAL:GDI32.GetDeviceCaps
INTERNAL _DLL FUNCTION CreateHalftonePalette( hdc AS IntPtr) AS IntPtr PASCAL:GDI32.CreateHalftonePalette
INTERNAL _DLL FUNCTION GetStockObject( fnObject AS INT ) AS IntPtr PASCAL:GDI32.GetStockObject

#define SETDIB        100
#define STRETCHDIB    200
#define GMEM_MOVEABLE 0x0002
#define SWP_NOMOVE    0x0002
#define SWP_NOZORDER  0x0004
#define DIB_PAL_COLORS 1
#define DIB_RGB_COLORS 0
#define SRCCOPY (DWORD) 0x00CC0020
#define BITSPIXEL 12
#define DEFAULT_PALETTE 15
#define RC_PALETTE 0x0100
#define RASTERCAPS 38

INTERNAL VOSTRUCT RGBQUAD
    MEMBER rgbBlue     AS BYTE
    MEMBER rgbGreen    AS BYTE
    MEMBER rgbRed      AS BYTE
    MEMBER rgbReserved AS BYTE

INTERNAL VOSTRUCT BITMAPINFOHEADER
    MEMBER biSize          AS DWORD
    MEMBER biWidth         AS LONGINT
    MEMBER biHeight        AS LONGINT
    MEMBER biPlanes        AS WORD
    MEMBER biBitCount      AS WORD
    MEMBER biCompression   AS DWORD
    MEMBER biSizeImage     AS DWORD
    MEMBER biXPelsPerMeter AS LONGINT
    MEMBER biYPelsPerMeter AS LONGINT
    MEMBER biClrUsed       AS DWORD
    MEMBER biClrImportant  AS DWORD

INTERNAL VOSTRUCT RECT
    MEMBER left   AS LONGINT
    MEMBER top    AS LONGINT
    MEMBER right  AS LONGINT
    MEMBER bottom AS LONGINT

INTERNAL VOSTRUCT BITMAPFILEHEADER ALIGN 2
    MEMBER  bfType 		AS WORD
    MEMBER  bfSize 		AS DWORD
    MEMBER  bfReserved1 	AS WORD
    MEMBER  bfReserved2 	AS WORD
    MEMBER  bfOffBits 	AS DWORD

INTERNAL VOSTRUCT PAINTSTRUCT
    MEMBER hdc AS IntPtr
    MEMBER fErase AS INT // LOGIC
    MEMBER rcPaint IS RECT
    MEMBER fRestore AS INT // LOGIC
    MEMBER fIncUpdate AS INT // LOGIC
    MEMBER DIM rgbReserved[32] AS BYTE

INTERNAL VOSTRUCT BITMAPINFO
    MEMBER bmiHeader IS BITMAPINFOHEADER
    MEMBER DIM bmiColors[1] IS RGBQUAD

INTERNAL VOSTRUCT LOGPALETTE
    MEMBER palVersion    AS WORD
    MEMBER palNumEntries AS WORD
    MEMBER DIM palPalEntry[1] IS PALETTEENTRY

INTERNAL VOSTRUCT PALETTEENTRY
    MEMBER peRed   AS BYTE
    MEMBER peGreen AS BYTE
    MEMBER peBlue  AS BYTE
    MEMBER peFlags AS BYTE

CLASS VOBitmaps
    PROTECTED hDIBInfo AS IntPtr
    PROTECTED hDDBitmap AS IntPtr
    PROTECTED offBits AS WORD
    PROTECTED hOldBitmap AS IntPtr
    PROTECTED hMemDC AS IntPtr

    STATIC METHOD Show(hWnd AS IntPtr,cFileName AS STRING,cTitle AS STRING) AS LOGIC
        RETURN Process(hWnd, cFileName, cTitle, FALSE)

    STATIC METHOD Stretch(hWnd AS IntPtr,cFileName AS STRING,cTitle AS STRING) AS LOGIC
        RETURN Process(hWnd, cFileName, cTitle, FALSE)

    STATIC METHOD Process(hWnd AS IntPtr, cFileName AS STRING, cTitle AS STRING, stretch AS LOGIC) AS LOGIC
        LOCAL bRet := FALSE AS LOGIC
        LOCAL oBitMap := VOBitmaps{} AS VOBitMaps
        oBitMap:hDIBInfo := GlobalAlloc(  GMEM_MOVEABLE, (DWORD)(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD) ) )

        IF hWnd == IntPtr.Zero
            hWnd := GetFocus()
        ENDIF

        IF oBitMap:InitDIB( hWnd,  IIF( stretch, STRETCHDIB, SETDIB ), cTitle, cFileName )
            oBitMap:PaintDIB( hWnd, IIF( stretch, STRETCHDIB, SETDIB ), cFileName )
            bRet := TRUE
        ENDIF

        GlobalFree( oBitMap:hDIBInfo )
        RETURN bRet

    PRIVATE METHOD  InitDIB( hWnd AS IntPtr, wOperation AS SHORT, cWinTitle AS STRING, cFileName AS STRING ) AS LOGIC
        LOCAL lpbi       AS BITMAPINFOHEADER
        LOCAL rectNew    IS RECT
        LOCAL rectClient IS RECT
        LOCAL rectWindow IS RECT
        LOCAL uiDeltaX   AS DWORD
        LOCAL uiDeltaY   AS DWORD

        IF ! ReadDIB( cFileName )
            RETURN FALSE
        ENDIF

        lpbi := (BITMAPINFOHEADER PTR) GlobalLock( hDIBInfo )

        SetWindowText( hWnd, cWinTitle )

        IF wOperation == SETDIB
            rectNew:left	  := 0
            rectNew:top 	  := 0
            rectNew:right	  := (WORD)lpbi:biWidth
            rectNew:bottom	  := (WORD)lpbi:biHeight

            GetClientRect( hWnd, @rectClient )
            GetWindowRect( hWnd, @rectWindow )

            uiDeltaX := (DWORD) ( rectWindow:right -  rectWindow:left - rectClient:right )
            uiDeltaY := (DWORD) ( rectWindow:bottom - rectWindow:top - rectClient:bottom )

            rectNew:bottom += (INT) uiDeltaY
            rectNew:right  += (INT) uiDeltaX

            SetWindowPos( hWnd, NULL, 0, 0, rectNew:right - rectNew:left, rectNew:bottom - rectNew:top + 1,	SWP_NOMOVE | SWP_NOZORDER )
        ENDIF

        InvalidateRect( hWnd, NULL, TRUE )

        GlobalUnlock( hDIBInfo )

        RETURN TRUE

    PRIVATE METHOD ReadDIB( cDibFile AS STRING ) AS LOGIC
        LOCAL hf AS IntPtr
        LOCAL lpbi AS BITMAPINFOHEADER
        LOCAL bf IS BITMAPFILEHEADER
        LOCAL nNumColors AS WORD
        LOCAL result := FALSE AS LOGIC

        hf := FOpen2( cDibFile, FO_READ + FO_DENYNONE )

        IF hf == F_ERROR
            THROW Error.VOError( EG_OPEN, "ReadDIB", "cDibFile", 2, { cDibFile } )
        ENDIF

        lpbi := (BITMAPINFOHEADER PTR) GlobalLock( hDIBInfo )

        IF sizeof( BITMAPFILEHEADER ) != FRead3( hf, @bf, sizeof( BITMAPFILEHEADER ) )
            FClose( hf )
            THROW Error.VOError( EG_READ, "ReadDIB", "cDibFile", 2, { cDibFile } )
        ENDIF

        IF bf:bfType != 0x4d42   /* 'BM' */
            FClose( hf )
            THROW Error.VOError( EG_UNSUPPORTED, "ReadDIB", "cDibFile", 2, { cDibFile } )
        ENDIF

        IF sizeof( BITMAPINFOHEADER ) != FRead3( hf, lpbi, sizeof( BITMAPINFOHEADER ) )
            FClose( hf )
            THROW Error.VOError( EG_READ, "ReadDIB", "cDibFile", 2, { cDibFile } )
        ENDIF

        nNumColors := (WORD) lpbi:biClrUsed

        IF nNumColors == 0
            IF lpbi:biBitCount != 24
                nNumColors := (WORD) (1 << lpbi:biBitCount) /* standard size table */
            ENDIF
        ENDIF

        IF lpbi:biClrUsed == 0
            lpbi:biClrUsed := (DWORD) nNumColors
        ENDIF

        IF lpbi:biSizeImage == 0
            lpbi:biSizeImage := (DWORD) ( ((((lpbi:biWidth * lpbi:biBitCount) + 31) & ~31) >> 3) * lpbi:biHeight)
        ENDIF

        GlobalUnlock( hDIBInfo )

        hDIBInfo := GlobalReAlloc( hDIBInfo, lpbi:biSize + nNumColors * sizeof(RGBQUAD) + lpbi:biSizeImage, 0 )

        IF hDIBInfo == 0
            FClose( hf )
            THROW Error.VOError( EG_MEM, "ReadDIB", "cDibFile", 2, { cDibFile } )
        ENDIF

        lpbi := (BITMAPINFOHEADER PTR) GlobalLock( hDIBInfo )

        FRead3( hf, ((BYTE PTR) lpbi) + lpbi:biSize, nNumColors * sizeof( RGBQUAD ) )

        offBits := (WORD) (lpbi:biSize + nNumColors * sizeof( RGBQUAD ) )

        IF bf:bfOffBits != 0
            FSeek3( hf, (INT) bf:bfOffBits, FS_SET )
        ENDIF

        IF lpbi:biSizeImage == FRead3( hf, ((BYTE PTR) lpbi) + offBits, lpbi:biSizeImage )
            result := TRUE
        ENDIF

        GlobalUnlock( hDIBInfo )

        FClose( hf )

        RETURN result
    PRIVATE METHOD PaintDIB( hWnd AS IntPtr, wOperation AS WORD, cDibFile AS STRING ) AS VOID
        LOCAL ps         IS PAINTSTRUCT
        LOCAL hDC        := IntPtr.Zero AS IntPtr
        LOCAL hOldPal   := IntPtr.Zero AS IntPtr
        LOCAL hPalette  := IntPtr.Zero AS IntPtr
        LOCAL wDIBUse   := 0 AS WORD
        LOCAL Rectangle  IS RECT
        LOCAL fNewHeader := FALSE  AS LOGIC
        LOCAL lpHeader  := NULL  AS BITMAPINFOHEADER
        LOCAL lpBmpInfo := NULL  AS BITMAPINFOHEADER
        LOCAL fError     := FALSE AS LOGIC

        hDC 	:= BeginPaint( hWnd, @ps )

        lpBmpInfo := (BITMAPINFOHEADER PTR) GlobalLock( hDIBInfo )

        // Test for RC_PALETTE added my Meinhard, in order to fix bug with StretchBitmap()
        // Not sure why in VO the same code works correctly, without it..
        IF lpBmpInfo:biClrUsed != 0 .AND. ((GetDeviceCaps( hDC, RASTERCAPS ) ~ RC_PALETTE) == RC_PALETTE)
            hPalette := MakeDIBPalette( lpBmpInfo, hDC )

            IF hPalette == IntPtr.Zero
                fError := TRUE
            ENDIF

            IF ! fError
                hOldPal := SelectPalette( hDC, hPalette, FALSE )

                RealizePalette( hDC )

                IF lpBmpInfo:biBitCount < 24
                    lpHeader := MakeIndexHeader( lpBmpInfo )

                    IF lpHeader != IntPtr.Zero
                        fNewHeader := TRUE
                    ELSE
                        fError := TRUE
                    ENDIF

                    wDIBUse := DIB_PAL_COLORS
                ELSE
                    lpHeader := lpBmpInfo
                    wDIBUse  := DIB_RGB_COLORS
                ENDIF
            ENDIF
        ELSE
            lpHeader := lpBmpInfo
            wDIBUse  := DIB_RGB_COLORS
        ENDIF

        IF ! fError
            hMemDC	  := CreateCompatibleDC( hDC )
            hDDBitmap  := CreateCompatibleBitmap( hDC, (WORD) lpBmpInfo:biWidth, (WORD) lpBmpInfo:biHeight )
            hOldBitmap := SelectObject( hMemDC, hDDBitmap )

            IF wOperation == SETDIB
                SetDIBitsToDevice( hDC,; 					// hDC
                    0,;								// DestX
                    0,;								// DestY
                    (WORD) lpBmpInfo:biWidth,;		   // nDestWidth
                    (WORD) lpBmpInfo:biHeight,;		   // nDestHeight
                    0,;								// SrcX
                    0,;								// SrcY
                    0,;								// nStartScan
                    (WORD) lpBmpInfo:biHeight,;		   // nNumScans
                    ((BYTE PTR)lpBmpInfo) + offBits,;		   // lpBits
                    (BITMAPINFO PTR) lpHeader,;			// lpBitsInfo
                    wDIBUse )						// wUsage

            ELSEIF wOperation == STRETCHDIB
                GetClientRect( hWnd, @Rectangle )
                StretchDIBits( hDC, 0, 0, Rectangle:right, Rectangle:bottom,;
                    0, 0, (WORD) lpBmpInfo:biWidth, (WORD) lpBmpInfo:biHeight,;
                    ((BYTE PTR)lpBmpInfo) + offBits,;
                    (BITMAPINFO PTR) lpHeader, wDIBUse, SRCCOPY )
            ENDIF

            SelectPalette( hDC, hOldPal, FALSE )
        ENDIF

        SelectObject( hMemDC, hOldBitmap )
        DeleteDC( hMemDC )
        DeleteObject( hDDBitmap )

        hDDBitmap := IntPtr.Zero
        DeleteObject( hPalette )
        hPalette := IntPtr.Zero

        EndPaint( hWnd, @ps )

        IF fNewHeader
            MemFree( lpHeader )
        ENDIF

        GlobalUnlock( hDIBInfo )

        IF fError
            THROW Error.VOError( EG_CORRUPTION, "PaintDIB", "cDibFile", 3, { cDibFile } )
        ENDIF
    PRIVATE METHOD  MakeDIBPalette( lpBmpInfo AS BITMAPINFOHEADER PTR, hDC AS IntPtr ) AS IntPtr
        LOCAL lpPal   := NULL AS LOGPALETTE
        LOCAL lpRGB   AS RGBQUAD
        LOCAL hLogPal AS IntPtr
        LOCAL i       AS WORD
        TRY
            IF lpBmpInfo:biClrUsed != 0 && lpBmpInfo:biBitCount < 24
                lpPal := (LOGPALETTE PTR) MemAlloc( sizeof(LOGPALETTE) + (WORD)lpBmpInfo:biClrUsed * sizeof(PALETTEENTRY) )

                IF lpPal == NULL
                    RETURN NULL
                ENDIF

                lpPal:palVersion	  := 0x300
                lpPal:palNumEntries := (WORD) lpBmpInfo:biClrUsed

                lpRGB := (RGBQUAD PTR)(lpBmpInfo + lpBmpInfo:biSize)

                i := 0

                DO WHILE i < (WORD) lpBmpInfo:biClrUsed
                    lpPal:palPalEntry[i + 1]:peRed 	:= lpRGB:rgbRed
                    lpPal:palPalEntry[i + 1]:peGreen	:= lpRGB:rgbGreen
                    lpPal:palPalEntry[i + 1]:peBlue	:= lpRGB:rgbBlue
                    lpPal:palPalEntry[i + 1]:peFlags	:= 0
                    i++
                    lpRGB++
                ENDDO

                hLogPal := CreatePalette( (LOGPALETTE PTR) lpPal )

            ELSE
                IF GetDeviceCaps( hDC, BITSPIXEL ) < 24
                    hLogPal := CreateHalftonePalette( hDC )
                ELSE
                    hLogPal := GetStockObject( DEFAULT_PALETTE )
                ENDIF
            ENDIF
        FINALLY
            IF (lpPal != NULL_PTR)
                MemFree(lpPal)
            ENDIF
        END TRY
        RETURN hLogPal

    PRIVATE METHOD  MakeIndexHeader( lpBmpInfo AS BITMAPINFOHEADER PTR ) AS BITMAPINFOHEADER PTR
        LOCAL lpPalInfo := NULL AS BITMAPINFOHEADER PTR
        LOCAL lpTable AS WORD PTR
        LOCAL i AS WORD

        IF lpBmpInfo:biClrUsed != 0
            lpPalInfo := (BITMAPINFOHEADER PTR) MemAlloc( (WORD)lpBmpInfo:biSize + (WORD)lpBmpInfo:biClrUsed * sizeof(WORD) )

            IF lpPalInfo != NULL
                MemCopy( lpPalInfo, lpBmpInfo, sizeof( BITMAPINFOHEADER ) ) // *lpPalInfo := *lpBmpInfo;

                    lpTable := (WORD PTR)(lpPalInfo + lpPalInfo:biSize)

                i := 0
                DO WHILE i < (WORD) lpBmpInfo:biClrUsed
                    lpTable[1] := i  // *lpTable++ := i
                    lpTable++
                    i++
                ENDDO
            ENDIF
        ENDIF

        RETURN lpPalInfo

END CLASS

#endregion

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
#region functions

	/// <summary>
	/// </summary>
	/// <param name="hWndOwner"></param>
	/// <param name="cFiles"></param>
	/// <param name="fAsynchWork"></param>
	/// <returns>
	/// </returns>
UNSAFE	 FUNCTION DoSendMail(hWndOwner AS PTR,cFiles AS STRING,fAsynchWork AS LOGIC) AS VOID
		/// THROW NotImplementedException{}
	RETURN

	/// <summary>
	/// Display a bitmap in a window or control.
	/// </summary>
	/// <param name="hWnd"></param>
	/// <param name="cFileName"></param>
	/// <param name="cTitle"></param>
	/// <returns>
	/// </returns>
UNSAFE	 FUNCTION ShowBitmap(hWnd AS PTR,cFileName AS STRING,cTitle AS STRING) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

	/// <summary>
	/// Display a bitmap stretched or shrunk to fit a window or control.
	/// </summary>
	/// <param name="hWnd"></param>
	/// <param name="cFileName"></param>
	/// <param name="cTitle"></param>
	/// <returns>
	/// </returns>
UNSAFE	 FUNCTION StretchBitmap(hWnd AS PTR,cFileName AS STRING,cTitle AS STRING) AS LOGIC
		/// THROW NotImplementedException{}
	RETURN FALSE

#endregion


[Runtime functions] Fix for https://github.com/X-Sharp/XSharpPublic/issues/779, Descend() incompatibility with VO with strings

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//

USING XSharp
USING System.Text
USING System.Globalization
USING System.Collections.Generic

#define MAXDIGITS               30
#define MAXDECIMALS             15

INTERNAL STATIC CLASS XSharp.ConversionHelpers
    STATIC INTERNAL usCulture AS CultureInfo
    STATIC PRIVATE formatStrings AS Dictionary<INT, STRING>
    STATIC CONSTRUCTOR
        usCulture := CultureInfo{"en-US"}
        formatStrings := Dictionary<INT, STRING>{}

    STATIC METHOD GetFormatString(nLen AS INT, nDec AS INT) AS STRING
        LOCAL nKey AS INT
        LOCAL cFormat AS STRING
        nKey := nLen * 100 + nDec
        IF formatStrings:ContainsKey(nKey)
            RETURN formatStrings[nKey]
        ENDIF
        IF nDec != 0
            cFormat := "0."
            cFormat := cFormat:PadRight(nDec+2, c'0')	// 2 extra for the 0 + Dot
        ELSE
            cFormat := "0"
        ENDIF
        cFormat := cFormat:PadLeft(nLen, c'#')
        cFormat := "{0," + nLen:ToString()+":"+cFormat+"}"
        formatStrings:Add(nKey, cFormat)
        RETURN cFormat

    PRIVATE CONST NOCHAR := c'\0' AS CHAR
    STATIC METHOD NextChar(c AS STRING, nIndex REF INT) AS CHAR
        LOCAL cChar AS CHAR
        LOCAL lStart := nIndex == -1 AS LOGIC
        DO WHILE TRUE
            nIndex ++
            IF nIndex >= c:Length
                RETURN NOCHAR
            END IF
            cChar := c[nIndex]
            IF cChar == c'E'
                RETURN NOCHAR
            END IF
            IF cChar >= c'0' .AND. cChar <= c'9'
                IF cChar == c'0' .AND. lStart
                    nIndex ++
                    LOOP
                END IF
                RETURN cChar
            END IF
        END DO

    STATIC METHOD AdjustPrecision(cNum15 AS STRING, cNum17 AS STRING) AS STRING
        LOCAL cDiff15 := "0", cDiff17 := "0" AS STRING
        LOCAL cResult AS STRING
        LOCAL c15,c17 AS CHAR
        LOCAL n15,n17 AS INT
        LOCAL nMatch AS INT
        LOCAL lDiff  AS LOGIC
        n15 := n17 := -1
        nMatch := 0
        cResult := cNum15
        lDiff := FALSE
        DO WHILE TRUE
            c15 := NextChar(cNum15 , REF n15)
            c17 := NextChar(cNum17 , REF n17)
            IF c15 == NOCHAR .OR. c17 == NOCHAR
                IF lDiff
                    EXIT
                ELSE
                    RETURN cNum15
                END IF
            ELSEIF c15 == c17
                nMatch ++
            ELSE
                IF nMatch >= 14
                    IF nMatch < 17
                        lDiff := TRUE
                        cResult := cResult:Substring(0, n15) + c17:ToString() + cResult:Substring(n15 + 1)
                        nMatch ++
                        cDiff15 += c15:ToString()
                        cDiff17 += c17:ToString()
                    ELSE
                        EXIT
                    END IF
                ELSE
                    RETURN cNum15
                END IF
            END IF
        END DO

        // if the difference of the two numbers is the minimum one, then it was probably just a rounding issue in "G17" representation
        IF Math.Abs( Int32.Parse(cDiff15) - Int32.Parse(cDiff17) ) == 1
            RETURN cNum15
        END IF
    RETURN cResult

    STATIC METHOD FormatNumber(n AS REAL8, nLen AS INT, nDec AS INT) AS STRING
        LOCAL cFormat AS STRING
        LOCAL result AS STRING
        cFormat := GetFormatString(nLen, nDec)
        // G17 returns all 17 relevant digits for a REAL8
        // See https://docs.microsoft.com/en-us/dotnet/api/system.double.tostring?view=netframework-4.7.2
        result := String.Format(usCulture, cFormat, n)
        IF nLen > 15 .AND. result:Length >= 15
            result := AdjustPrecision(result, n:ToString("G17", usCulture))
        END IF
/*      IF result:EndsWith("0") .AND. nDec > 0 .AND. nLen > 15
            VAR cTemp := n:ToString("G17", usCulture)
            VAR parts := cTemp:Split(<CHAR>{c'.'}, StringSplitOptions.RemoveEmptyEntries)
            IF parts:Length > 1
                // If the G17 format contains a decimal part, fetch it.
                VAR cDec := parts[1+ __ARRAYBASE__]
                VAR cInt := parts[0+ __ARRAYBASE__]

                // could end up being represented in exp format. For example
                // Str3(70.00 - 65.01 - 4.99,16,2)
                // causes cTemp above to have a value of "-5.3290705182007514E-15"
                LOCAL nExp AS INT
                nExp := cDec:IndexOf('E')
                IF nExp != -1
                    IF cDec[nExp + 1] == '-'
                        LOCAL cExp AS STRING
                        cExp := System.String{'0', Int32.Parse(cDec:Substring(nExp + 2))}
                        cDec := cExp + iif(cInt[0] == '-' , cInt:SubString(1) , cInt) + cDec
                        cInt := iif(cInt[0] == '-' , "-0" , "0")
                    END IF
                END IF

                parts := result:Split(<CHAR>{c'.'}, StringSplitOptions.RemoveEmptyEntries)
                VAR cOldDec := parts[1+ __ARRAYBASE__]
                IF cDec:Length > cOldDec:Length
                    cDec := cDec:SubString(0, cOldDec:Length)
                ELSEIF cDec:Length < cOldDec:Length
                    cDec := cDec:PadRight(cOldDec:Length,c'0')
                ENDIF
                result := parts[0+ __ARRAYBASE__] + "." + cDec
            ENDIF
        ENDIF*/
        IF result:Length > nLen
            LOCAL nSepIndex AS INT
            nSepIndex := result:IndexOf(usCulture:NumberFormat:NumberDecimalSeparator)
            IF nSepIndex != -1 .AND. nSepIndex <= nLen
                result := result:Substring(0, nLen)
            ELSE
                result := Replicate("*", (DWORD) nLen)
            END IF
        ENDIF
        RETURN result

    STATIC METHOD FormatNumber(n AS INT64, nLen AS INT, nDec AS INT) AS STRING
        LOCAL cFormat AS STRING
        LOCAL result AS STRING
        cFormat := GetFormatString(nLen, 0)
        result := String.Format(usCulture, cFormat, n)
        IF result:Length > nLen
            result := Replicate("*", (DWORD) nLen)
        ENDIF
        RETURN result

    STATIC METHOD AdjustDecimalSeparator(cString AS STRING) AS STRING
        IF cString:IndexOf(".") >= 0
            VAR wSep   := SetDecimalSep()
            IF wSep != 46
                cString := cString:Replace(c'.', (CHAR) wSep)
            ENDIF
        ENDIF
        RETURN cString

    STATIC METHOD GetSignificantWholeDigits(r AS REAL8) AS INT
        LOCAL nRet := IIF(r < 0.0 , 1 , 0) AS INT
        r := Math.Floor(Math.Abs(r))
        DO WHILE r > 0.0
            nRet ++
            r := r /10.0
            r := Math.Floor(r)
        END DO
    RETURN nRet

END CLASS

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/ashexstring/*" />
/// <seealso cref='C2Hex(System.String)' >C2Hex</seealso>
/// <seealso cref='_C2Hex(System.String,System.Boolean)' >_C2Hex</seealso>
FUNCTION AsHexString(uValue AS USUAL) AS STRING
    LOCAL result AS STRING
    IF uValue:IsString
        result := _C2Hex( (STRING) uValue, TRUE)
    ELSEIF uValue:IsNumeric
        IF uValue:IsInt64
            result := String.Format("{0:X16}", (INT64) uValue)
        ELSEIF uValue:IsLong
            result := String.Format("{0:X8}", (INT) uValue)
        ELSEIF uValue:IsFloat
            VAR flValue := (FLOAT) uValue
            VAR r8Value := flValue:Value
            IF r8Value >= System.Int32.MinValue .AND. r8Value <= System.Int32.MaxValue
                result := String.Format("{0:X8}", (INT) uValue)
            ELSEIF r8Value >= 0. .AND. r8Value <= System.UInt32.MaxValue
                result := String.Format("{0:X8}", (DWORD) uValue)
            ELSEIF r8Value >= System.Int64.MinValue .AND. r8Value <= System.Int64.MaxValue
                result := String.Format("{0:X16}", (INT64) uValue)
            ELSEIF r8Value >= 0. .AND. r8Value <= System.UInt64.MaxValue
                result := String.Format("{0:X16}", (UINT64) uValue)
            ELSE
                result := "********"
            ENDIF
        ELSE
            result := ""
        ENDIF
    ELSEIF uValue:IsPtr
        LOCAL i64 := (UIntPtr)uValue AS UIntPtr
        LOCAL u64 := i64:ToUInt64() AS UInt64
        result := String.Format( iif( u64 > System.UInt32.MaxValue, "{0:X16}", "{0:X8}" ), u64 )
    ELSE
        result := ""
    ENDIF
    RETURN result

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/aspadr/*" />
FUNCTION AsPadr(uValue AS USUAL,wLen AS DWORD) AS STRING
    RETURN PadR(AsString(uValue), wLen)

/// <exclude />
FUNCTION _AsString(u AS USUAL) AS STRING
    RETURN	 AsString(u)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/asstring/*" />
FUNCTION AsString(uValue AS USUAL) AS STRING
    LOCAL result AS STRING
    DO CASE
        CASE uValue:IsString
            result := (STRING) uValue
        CASE uValue:IsNumeric
            result := NTrim(uValue)
        CASE uValue:IsSymbol
            result := Symbol2String( (SYMBOL) uValue)
        CASE uValue:IsDate
            result := DToC( (DATE) uValue)
        CASE uValue:IsPtr
            LOCAL i64 := (UIntPtr)uValue AS UIntPtr
            LOCAL u64 := i64:ToUInt64() AS UInt64
            result := String.Format( iif( u64 > System.UInt32.MaxValue, "0x{0:X16}", "0x{0:X8}" ), u64 )
        CASE uValue:IsArray
            VAR aValue := (ARRAY) uValue
            //  {[0000000003]0x025400FC}
            IF aValue == NULL_ARRAY
                result := "{[0000000000]0x00000000}"
            ELSE
                VAR cHashCode := String.Format("{0:X8}", aValue:GetHashCode())
                result := "{["+String.Format("{0:D10}",aValue:Length)+"]0x"+cHashCode+"}"
            ENDIF

        CASE uValue:IsObject
            LOCAL oValue := uValue AS OBJECT
            IF oValue == NULL_OBJECT
                result := "{(0x0000)0x00000000} CLASS "
            ELSE
                VAR oType := oValue:GetType()
                VAR nSize := oType:GetFields():Length *4
                VAR cHashCode := String.Format("{0:X8}", oValue:GetHashCode())
                result := "{(0x"+String.Format("{0:X4}", nSize)+")0x"+cHashCode+"} CLASS " + oType:Name:ToUpperInvariant()
            ENDIF
        OTHERWISE
            result := uValue:ToString()
    ENDCASE
    RETURN result

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/assymbol/*" />
FUNCTION AsSymbol(uValue AS USUAL) AS SYMBOL
    RETURN SYMBOL{(STRING)uValue, TRUE}

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/descend/*" />
FUNCTION Descend(uValue AS USUAL) AS USUAL
    IF uValue:IsString
        RETURN _descendingString( (STRING) uValue)
    ELSEIF uValue:IsLogic
        RETURN ! (LOGIC) uValue
    ELSEIF uValue:IsLong
        RETURN 0 - (INT) uValue
    ELSEIF uValue:IsInt64
        RETURN 0 - (INT64) uValue
    ELSEIF uValue:IsFloat
        RETURN 0 - (__Float) uValue
    ELSEIF uValue:IsDecimal
        RETURN 0 - (Decimal) uValue
    ELSEIF uValue:IsCurrency
        RETURN 0 - (__Currency) uValue
    ELSEIF uValue:IsDate
        RETURN 5231808 - (DWORD)(DATE) uValue
    ENDIF
    RETURN uValue

INTERNAL FUNCTION _descendingString(s AS STRING) AS STRING
    LOCAL encoding AS Encoding
    IF RuntimeState.Ansi
        encoding := StringHelpers.WinEncoding
    ELSE
        encoding := StringHelpers.DosEncoding
    ENDIF
    LOCAL bytes := encoding:GetBytes( s ) AS BYTE[]
    LOCAL nlen := bytes:Length AS INT
    FOR LOCAL i := 1 AS INT UPTO nlen
        IF bytes[i] != 0
            bytes[i] := (BYTE) ( (INT)256 - (INT)bytes[i] )
        ENDIF
    NEXT
    RETURN encoding:GetString( bytes )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/descend/*" />
FUNCTION DescendA(uValue REF USUAL) AS USUAL
    uValue := Descend(uValue)
    RETURN uValue

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/ntrim/*" />
FUNCTION NTrim(nNum AS USUAL) AS STRING
    LOCAL ret AS STRING
    SWITCH nNum:_usualType
    CASE __UsualType.Int64
    CASE __UsualType.Long
      ret := ConversionHelpers.FormatNumber( (INT64) nNum, (INT) RuntimeState.Digits, 0):Trim()
    CASE __UsualType.Date
      ret := AsString( nNum )
    CASE __UsualType.Currency
      ret := ((__Currency) (nNum)):ToString()
    CASE __UsualType.Float
    CASE __UsualType.Decimal
      ret := ConversionHelpers.AdjustDecimalSeparator(_Str1(  (FLOAT) nNum )):Trim()
    OTHERWISE
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNum), 1, nNum)
   END SWITCH
   RETURN ret

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/pad/*" />
FUNCTION Pad( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    RETURN PadR( uValue, nLength, cFillChar )

/// <inheritdoc cref="Pad(XSharp.__Usual,System.Int32,System.String)" />
FUNCTION Pad( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadR( uValue, (INT) nLength, cFillChar )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/padc/*" />
FUNCTION PadC( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    // If they send in an empty string then change to " "
    IF cFillChar == NULL .OR. cFillChar :Length == 0
        cFillChar := " "
    ENDIF

    LOCAL ret     AS STRING
    LOCAL retlen  AS INT

    IF uValue:IsNil
        ret := ""
    ELSEIF uValue:IsNumeric
        ret := NTrim( uValue)
    ELSE
        ret := uValue:ToString()
    ENDIF
    retlen := ret:Length

    IF retlen > nLength
        ret := ret:Remove( nLength )
    ELSE
        VAR leftSpace := System.String{cFillChar[0], ( nLength - retlen ) / 2}
        ret := leftSpace+ret
        ret := ret:PadRight( nLength, cFillChar[0] )
    ENDIF

    RETURN ret

/// <inheritdoc cref="PadC(XSharp.__Usual,System.Int32,System.String)" />
FUNCTION PadC( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadC( uValue, (INT) nLength, cFillChar )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/padl/*" />
FUNCTION PadL( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    // If they send in an empty string then change to " "
    IF cFillChar == NULL .OR. cFillChar :Length == 0
        cFillChar := " "
    ENDIF
    LOCAL ret AS STRING
    IF uValue:IsNil
        ret := ""
    ELSEIF uValue:IsNumeric
        ret := NTrim( uValue)
    ELSE
        ret := uValue:ToString()
    ENDIF
    RETURN IIF( ret:Length > nLength, ret:Remove( nLength ), ret:PadLeft( nLength, cFillChar[0] ) )

/// <inheritdoc cref="PadL(XSharp.__Usual,System.Int32,System.String)" />
FUNCTION PadL( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadL( uValue, (INT) nLength, cFillChar )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/padr/*" />
FUNCTION PadR( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadR( uValue, (INT) nLength, cFillChar )

/// <inheritdoc cref="PadR(XSharp.__Usual,System.UInt32,System.String)" />
FUNCTION PadR( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    // If they send in an empty string then change to " "
    IF cFillChar == NULL .OR. cFillChar:Length == 0
        cFillChar := " "
    ENDIF
    LOCAL ret AS STRING
    IF uValue:IsNil
        ret := ""
    ELSEIF uValue:IsNumeric
        ret := NTrim( uValue)
    ELSE
        ret := uValue:ToString()
    ENDIF
    RETURN IIF( ret:Length > nLength, ret:Remove( nLength ), ret:PadRight( nLength, cFillChar[0] ) )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str/*" />
FUNCTION Str(nNumber ,nLength ,nDecimals ) AS STRING CLIPPER
    IF PCount() < 1 .OR. pCount() > 3
        RETURN ""
    ENDIF

    // Handle integer values
    IF nNumber:IsInteger .and. (PCount() <= 2 .or. (PCount() == 3 .and. nDecimals:IsNumeric .and. nDecimals == 0) )
    	LOCAL cRet AS STRING
    	LOCAL nDigits AS INT

    	cRet := ((INT64)nNumber):ToString()

    	IF nLength:IsNumeric
    		IF nLength < 0
    			nDigits := cRet:Length
    		ELSE
	    		nDigits := nLength
    		END IF
    	ELSE
    		nDigits := (INT)RuntimeState.Digits
    	END IF

    	IF cRet:Length > nDigits
    		cRet := System.String{c'*' , nDigits}
    	ELSEIF cRet:Length < RuntimeState.Digits
    		cRet := cRet:PadLeft(nDigits)
    	END IF

    	RETURN cRet
    ELSEIF nNumber:IsFloat
        LOCAL oFloat AS FLOAT
        oFloat := nNumber
        IF oFloat:Digits != -1 .and. IsNil(nLength)
        	nLength := oFloat:Digits
        END IF
    END IF

    LOCAL result AS STRING
    LOCAL nLen AS DWORD
    LOCAL nDec AS DWORD
    LOCAL lTrimSpaces := FALSE AS LOGIC
    IF nLength:IsNumeric
        IF nLength < 0
            nLen := System.UInt32.MaxValue - 1
            lTrimSpaces := TRUE
        ELSE
            nLen := (DWORD) nLength
        ENDIF
    ELSE
        nLen := System.UInt32.MaxValue
    ENDIF
    IF ! nDecimals:IsNumeric
        nDec := UInt32.MaxValue
    ELSE
        IF nDecimals < 0
            nDec := System.UInt32.MaxValue
        ELSE
            nDec := (DWORD) nDecimals
        ENDIF
    ENDIF
    result := _Str3(nNumber, nLen, nDec)
    IF lTrimSpaces
        result := result:TrimStart()
    END IF
    RETURN ConversionHelpers.AdjustDecimalSeparator(result)

/// <inheritdoc cref="Str" />
/// <returns>The returned string with always have a DOT as decimal separator.</returns>
FUNCTION _Str(nValue ,uLen ,uDec ) AS STRING CLIPPER
    LOCAL nLen,  nDec AS LONG
    LOCAL dwLen, dwDec AS DWORD
    IF PCount() > 0 .AND. ! nValue:IsNumeric
       THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nValue),1, nValue, uLen, uDec)
    ENDIF
    SWITCH PCount()
    CASE 1
        RETURN _Str1( nValue)
    CASE 2
        IF ! uLen:IsNumeric
            THROW Error.DataTypeError( __FUNCTION__, NAMEOF(uLen),2,nValue, uLen, uDec)
        ENDIF
        nLen := uLen
        IF nLen < 0
            dwLen := System.UInt32.MaxValue
        ELSE
            dwLen := (DWORD) nLen
        ENDIF
        IF nValue:IsFractional
            RETURN _Str2(nValue, dwLen)
        ELSE
            RETURN ConversionHelpers.FormatNumber((INT64) nValue, nLen,0)
        ENDIF
    CASE 3
        IF ! uLen:IsNumeric
            THROW Error.DataTypeError( __FUNCTION__, NAMEOF(uLen),2,nValue, uLen, uDec)
        ENDIF
        IF ! uDec:IsNumeric
            THROW Error.DataTypeError( __FUNCTION__, NAMEOF(uDec),3,nValue, uLen, uDec)
        ENDIF
        nLen := uLen
        nDec := uDec
        IF nValue:IsFractional
            nLen := uLen
            IF nLen < 0
                dwLen := System.UInt32.MaxValue
            ELSE
                dwLen := (DWORD) nLen
            ENDIF

            IF nDec < 0 .OR. RuntimeState.Fixed
                dwDec := XSharp.RuntimeState.Decimals
            ELSE
                dwDec := (DWORD) nDec
            ENDIF
            IF nLen < 0 .OR. RuntimeState.DigitsFixed
                dwLen := XSharp.RuntimeState.Digits
                IF nDec != 0
                    dwLen := dwLen + dwDec + 1
                ENDIF
            ENDIF
            VAR res := _Str3(nValue, dwLen, dwDec)
            IF nLen < 0
                res := res:TrimStart()
            ENDIF
            RETURN res
        ELSE
            IF nLen < 0
                nLen := 30
            ENDIF
            IF nDec < 0
                nDec := (INT) SetDecimal()
            ENDIF
            RETURN ConversionHelpers.FormatNumber((INT64) nValue, nLen,nDec)
        ENDIF
    OTHERWISE
        RETURN ""
    END SWITCH

// The following three functions are undocumented in Vulcan but sometimes used in user code
// They are the equivalent of the STR() functions but always return with digit decimal separator
// We route all three to the _Str() function that takes care of this already
/// <exclude/>
FUNCTION __Str(n AS USUAL) AS STRING
    RETURN _Str( n)

/// <exclude/>
FUNCTION __Str(n AS USUAL,nLen AS USUAL) AS STRING
    RETURN _Str( n, nLen)

/// <exclude/>
FUNCTION __Str(n AS USUAL,nLen AS USUAL, nDec AS USUAL) AS STRING
    RETURN _Str( n, nLen, nDec)

INTERNAL FUNCTION _PadZero(cValue AS STRING) AS STRING
    LOCAL iLen := 	cValue:Length AS INT
    cValue := cValue:TrimStart()
    IF cValue:Length > 1 .and. cValue[0] == c'-'
    	cValue := cValue:Substring(1)
        RETURN "-" + cValue:PadLeft((INT) iLen - 1, c'0')
    END IF
    RETURN cValue:PadLeft((INT) iLen, c'0')

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strzero/*" />
FUNCTION StrZero(nNumber AS USUAL,nLength AS INT,nDecimals AS INT) AS STRING
    IF ! ( nNumber:IsNumeric )
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNumber),1,nNumber)
    ENDIF
    LOCAL cValue := Str3(nNumber, (DWORD) nLength, (DWORD) nDecimals) AS STRING
    RETURN _PadZero(cValue)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strzero/*" />
FUNCTION StrZero(nNumber AS USUAL,nLength AS INT) AS STRING
    IF ! ( nNumber:IsNumeric )
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNumber),1,nNumber)
    ENDIF
    LOCAL cValue := Str3(nNumber, (DWORD) nLength, 0) AS STRING // Str3() and Str2() in VO have totally different behavior regarding decimal digits, rounding etc
    RETURN _PadZero(cValue)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strzero/*" />
FUNCTION StrZero(nNumber AS USUAL) AS STRING
    IF ! ( nNumber:IsNumeric )
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNumber),1,nNumber)
    ENDIF
    LOCAL cValue := Str1(nNumber) AS STRING
    RETURN _PadZero(cValue)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/toword/*" />
FUNCTION ToWord(n AS USUAL) AS DWORD
    RETURN (DWORD) n

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strint/*" />
FUNCTION StrInt(liNumber AS LONG,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN Str3( liNumber, dwLength, dwDecimals)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strlong/*" />
FUNCTION StrLong(liNumber AS LONG,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN StrInt(liNumber, dwLength, dwDecimals)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strfloat/*" />
FUNCTION StrFloat(flSource AS FLOAT,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN Str3( flSource, dwLength, dwDecimals )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str1/*" />
FUNCTION Str1(fNumber AS USUAL) AS STRING
    IF fNumber:IsFloat
        RETURN ConversionHelpers.AdjustDecimalSeparator(_Str1( (FLOAT) fNumber))
    ELSE
        RETURN ConversionHelpers.FormatNumber( (LONG) fNumber, (INT) RuntimeState.Digits, 0):Trim()
    ENDIF

INTERNAL FUNCTION _Str1(f AS FLOAT) AS STRING
    VAR nDecimals := f:Decimals
    VAR nDigits   := f:Digits

    SWITCH (Double)f
    CASE Double.NaN
        RETURN Double.NaN:ToString()
    CASE Double.PositiveInfinity
        RETURN Double.PositiveInfinity:ToString()
    CASE Double.NegativeInfinity
        RETURN Double.NegativeInfinity:ToString()
    END SWITCH

    IF nDecimals < 0 .OR. RuntimeState.Fixed
        nDecimals := (SHORT) RuntimeState.Decimals
    ENDIF
    IF nDigits <= 0 .OR. RuntimeState.DigitsFixed
        nDigits := (SHORT) RuntimeState.Digits
        IF ConversionHelpers.GetSignificantWholeDigits(f) > nDigits
            RETURN STRING{ c'*', (INT) nDigits}
        END IF
        IF nDecimals != 0
            nDigits += nDecimals +1
        ENDIF
    ENDIF
    VAR result := ConversionHelpers.FormatNumber(f, nDigits, nDecimals )
    RETURN result

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str2/*" />
/// <returns>A string representation of the value.</returns>
/// <seealso cref="Str" />
/// <seealso cref="Str1" />
/// <seealso cref="Str3" />
FUNCTION Str2(fNumber AS FLOAT,dwLength AS DWORD) AS STRING
    RETURN ConversionHelpers.AdjustDecimalSeparator(_Str2(fNumber, dwLength))

INTERNAL FUNCTION _Str2(f AS FLOAT,dwLen AS DWORD) AS STRING
  IF dwLen == 0 .OR. RuntimeState.DigitsFixed
      dwLen := (DWORD) RuntimeState.Digits
   ELSEIF dwLen  != UInt32.MaxValue
      dwLen := Math.Min( dwLen, MAXDIGITS )
   ENDIF
   VAR nDecimals := f:Decimals
    IF nDecimals < 0 .OR. RuntimeState.DigitsFixed
        nDecimals := (SHORT) RuntimeState.Decimals
    ENDIF
   RETURN ConversionHelpers.FormatNumber(f, (INT) dwLen, nDecimals)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str3/*" />
/// <seealso cref="Str" />
/// <seealso cref="Str1" />
/// <seealso cref="Str2" />
FUNCTION Str3(fNumber AS FLOAT,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN ConversionHelpers.AdjustDecimalSeparator(_Str3(fNumber, dwLength, dwDecimals))

/// <inheritdoc cref="Str3" />
/// <returns>A string with DOT as decimal separator.</returns>
FUNCTION _Str3(f AS FLOAT,dwLen AS DWORD,dwDec AS DWORD) AS STRING

   LOCAL lUnspecifiedDecimals := dwDec == UInt32.MaxValue AS LOGIC
   IF dwDec == UInt32.MaxValue
        IF RuntimeState.Fixed
            dwDec := (DWORD) RuntimeState.Decimals
        ELSE
            dwDec := (DWORD) f:Decimals
        ENDIF
   ELSE
      dwDec := Math.Min( dwDec, MAXDECIMALS )
   ENDIF

   // dwLen == UInt32.MaxValue     : Nil passed for 2nd param of Str()
   // dwLen == UInt32.MaxValue - 1 : Negative value for 2nd param of Str()

   IF dwLen == 0 .OR. dwLen == UInt32.MaxValue .or. dwLen == UInt32.MaxValue - 1
        IF dwDec > 0
        	LOCAL nSignificant AS INT
        	nSignificant := ConversionHelpers.GetSignificantWholeDigits(f)
            IF .not. dwLen == UInt32.MaxValue - 1
	            IF nSignificant > RuntimeState.Digits
	                RETURN STRING{ c'*', (INT) (RuntimeState.Digits + dwDec +1) } // VO's behavior...
	            END IF
            END IF
            IF dwLen == UInt32.MaxValue - 1
	            dwLen := (DWORD) nSignificant + dwDec + 2
            ELSE
	            dwLen := (DWORD) RuntimeState.Digits + dwDec +1
            END IF
        ELSE
            dwLen := (DWORD) RuntimeState.Digits
        ENDIF
   ELSE
      dwLen := Math.Min( dwLen, MAXDIGITS )
   ENDIF

 IF dwDec > 0 .AND. dwLen != UInt32.MaxValue .and. !lUnspecifiedDecimals .AND. ( dwLen < ( dwDec + 2 ) )
      RETURN STRING{ c'*', (INT) dwLen }
   ENDIF
   RETURN ConversionHelpers.FormatNumber(f, (INT) dwLen, (INT) dwDec)

/// <inheritdoc cref="Val" />
/// <returns>The numeric value as a FLOAT.</returns>
FUNCTION StrToFloat(c AS STRING) AS FLOAT
    RETURN (FLOAT) Val(c)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/val/*" />
FUNCTION Val(cNumber AS STRING) AS USUAL
    LOCAL isCurrency AS LOGIC
    IF String.IsNullOrEmpty(cNumber)
      RETURN 0
    ENDIF
    cNumber := AllTrim(cNumber)
    isCurrency := cNumber:StartsWith("$")
    IF isCurrency
        cNumber := cNumber:Substring(1)
    ENDIF
    VAR result := _VOVal(cNumber)
    IF isCurrency
        RETURN __Currency{ (REAL8) result }
    ENDIF
    RETURN result

//implements the quirks of VO's version of Val()
INTERNAL FUNCTION _VOVal(cNumber AS STRING) AS USUAL
    IF String.IsNullOrEmpty(cNumber)
      RETURN 0
    ENDIF
    cNumber := cNumber:Trim():ToUpper()
    // find non numeric characters in cNumber and trim the field to that length
    VAR pos := 0
    VAR done := FALSE
    VAR hex  := FALSE
    VAR hasdec := FALSE
    VAR hasexp := FALSE
    VAR lNoNumYet := TRUE
    VAR cPrev  := c' '
    VAR cDec   := (CHAR) RuntimeState.DecimalSep

    IF cDec != c'.'
        cNumber := cNumber:Replace(c'.', cDec) // VO behavior...
        cNumber := cNumber:Replace(cDec, c'.')
    ENDIF
    FOREACH VAR c IN cNumber
        SWITCH c
        CASE c'0'
        CASE c'1'
        CASE c'2'
        CASE c'3'
        CASE c'4'
        CASE c'5'
        CASE c'6'
        CASE c'7'
        CASE c'8'
        CASE c'9'
            lNoNumYet := FALSE
        CASE c'-'
        CASE c'+'
            IF lNoNumYet
                lNoNumYet := TRUE
            ELSE
            	done := TRUE
            ENDIF
        CASE c' '
            IF .not. lNoNumYet
            	done := TRUE
            ENDIF
        CASE c'.'
        CASE c','
            IF c == c',' .AND. cDec != c',' // Don't ask, VO...
                done := TRUE
            ELSEIF hasdec
                done := TRUE
            ELSE
                hasdec := TRUE
            ENDIF
        CASE c'A'
        CASE c'B'
        CASE c'C'
        CASE c'D'
        CASE c'F'
            IF !hex
                done := TRUE
            ENDIF
        CASE c'E'
            // exponentional notation only allowed if decimal separator was there
            IF hasdec
                hasexp := TRUE
            ELSE
                IF !hex
                    done := TRUE
                ENDIF
            ENDIF
        CASE c'L'	// LONG result
        CASE c'U'	// DWORD result
            done := TRUE
        CASE c'X'
            IF cPrev == c'0' .and. !hex
                hex := TRUE
            ELSE
                done := TRUE
            ENDIF
        OTHERWISE
            done := TRUE
        END SWITCH
        IF done
            EXIT
        ENDIF
        pos += 1
        cPrev := c
    NEXT
    IF pos < cNumber:Length
        cNumber := cNumber:Substring(0, pos)
    ENDIF
    IF cNumber:IndexOf('-') == 0 .and. cNumber:Length > 2 .and. cNumber[1] == c' '
        cNumber := "-" + cNumber:Substring(1):Trim()
    END IF

    IF cNumber:IndexOfAny(<CHAR> {c'.'}) > -1

        IF cDec != c'.'
            cNumber := cNumber:Replace(cDec, c'.')
        ENDIF
        VAR style := NumberStyles.Number
        IF hasexp
            style |= NumberStyles.AllowExponent
        ENDIF
        IF System.Double.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR r8Result)
            RETURN __Float{ r8Result , cNumber:Length - cNumber:IndexOf(c'.') - 1}
        ENDIF

    ELSE

        LOCAL style AS NumberStyles
        IF hex

            style := NumberStyles.HexNumber

            LOCAL nHexTagPos AS INT
            nHexTagPos := cNumber:IndexOf("0x")
            IF nHexTagPos == -1
                nHexTagPos := cNumber:IndexOf("0X")
            ENDIF
            IF nHexTagPos != -1
                cNumber := cNumber:Substring(0, nHexTagPos) + cNumber:Substring(nHexTagPos + 2):Trim()
            ENDIF
            LOCAL lNegativeHex := FALSE AS LOGIC
            IF cNumber:IndexOf('-') == 0
            	cNumber := cNumber:Substring(1)
            	lNegativeHex := TRUE
            ELSEIF cNumber:IndexOf('+') == 0
            	cNumber := cNumber:Substring(1)
            END IF

            System.Int64.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR iResult)
            IF lNegativeHex
                iResult := - iResult
            ENDIF
            IF Math.Abs(iResult) <= Int32.MaxValue
                RETURN (INT) iResult
            ELSE
                RETURN __Float{ (REAL8) iResult , 0 }
            ENDIF

        ELSE

            style := NumberStyles.Integer

            IF cNumber:Length <= 9 // yes, no matter if there's a sign char or not
                System.Int32.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR iResult)
                RETURN iResult
            ELSE
                System.Double.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR rResult)
                RETURN __Float{rResult, 0}
            ENDIF

        ENDIF

    ENDIF
    RETURN 0

/// <summary>
/// Convert an object containing a numeric value to a FLOAT
/// </summary>
/// <param name="oValue">Object containing the numeric value to convert.</param>
/// <returns>The value in the form of a float. </returns>
/// <exception cref='System.InvalidCastException'> Thrown when the parameter oValue cannot be converted to a FLOAT.</exception>
FUNCTION Object2Float(oValue AS OBJECT) AS FLOAT
    LOCAL typ := oValue:GetType() AS System.Type
    IF typ == typeof(FLOAT)
        RETURN (FLOAT) oValue
    ENDIF
    LOCAL tc := System.Type.GetTypeCode(typ) AS TypeCode
    SWITCH tc
    CASE System.TypeCode.SByte
        RETURN (System.SByte) oValue
    CASE System.TypeCode.Byte
        RETURN (System.Byte) oValue
    CASE System.TypeCode.Double
        RETURN (System.Double) oValue
    CASE System.TypeCode.Single
        RETURN (System.Single) oValue
    CASE System.TypeCode.UInt16
        RETURN (System.UInt16) oValue
    CASE System.TypeCode.UInt32
        RETURN (System.UInt32) oValue
    CASE System.TypeCode.UInt64
        RETURN (System.UInt64) oValue
    CASE System.TypeCode.Int16
        RETURN (System.Int16) oValue
    CASE System.TypeCode.Int32
        RETURN (System.Int32) oValue
    CASE System.TypeCode.Int64
        RETURN (System.Int64) oValue
    CASE System.TypeCode.Decimal
        RETURN (System.Decimal) oValue
    OTHERWISE
        THROW InvalidCastException{"Cannot convert from type "+typ:FullName+" to FLOAT"}
    END SWITCH

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/bin2f/*" />
FUNCTION Bin2F(cFloat AS STRING) AS FLOAT
    LOCAL nDec AS WORD
    LOCAL val  AS REAL8
    IF SLen(cFloat) >= 12
        nDec := Bin2W(SubStr3(cFloat, 11,2))
        val  := Bin2Real8(SubStr3(cFloat, 1,8))
        RETURN FLOAT{val, 0, nDec}
    ENDIF
    RETURN 0.0

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/f2bin/*" />
FUNCTION F2Bin(fValue AS FLOAT) AS STRING
    RETURN Real82Bin(fValue:Value)+ e"\0\0" + W2Bin((WORD)fValue:Decimals)

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//

USING XSharp
USING System.Text
USING System.Globalization
USING System.Collections.Generic

#define MAXDIGITS               30
#define MAXDECIMALS             15

INTERNAL STATIC CLASS XSharp.ConversionHelpers
    STATIC INTERNAL usCulture AS CultureInfo
    STATIC PRIVATE formatStrings AS Dictionary<INT, STRING>
    STATIC CONSTRUCTOR
        usCulture := CultureInfo{"en-US"}
        formatStrings := Dictionary<INT, STRING>{}

    STATIC METHOD GetFormatString(nLen AS INT, nDec AS INT) AS STRING
        LOCAL nKey AS INT
        LOCAL cFormat AS STRING
        nKey := nLen * 100 + nDec
        IF formatStrings:ContainsKey(nKey)
            RETURN formatStrings[nKey]
        ENDIF
        IF nDec != 0
            cFormat := "0."
            cFormat := cFormat:PadRight(nDec+2, c'0')	// 2 extra for the 0 + Dot
        ELSE
            cFormat := "0"
        ENDIF
        cFormat := cFormat:PadLeft(nLen, c'#')
        cFormat := "{0," + nLen:ToString()+":"+cFormat+"}"
        formatStrings:Add(nKey, cFormat)
        RETURN cFormat

    PRIVATE CONST NOCHAR := c'\0' AS CHAR
    STATIC METHOD NextChar(c AS STRING, nIndex REF INT) AS CHAR
        LOCAL cChar AS CHAR
        LOCAL lStart := nIndex == -1 AS LOGIC
        DO WHILE TRUE
            nIndex ++
            IF nIndex >= c:Length
                RETURN NOCHAR
            END IF
            cChar := c[nIndex]
            IF cChar == c'E'
                RETURN NOCHAR
            END IF
            IF cChar >= c'0' .AND. cChar <= c'9'
                IF cChar == c'0' .AND. lStart
                    nIndex ++
                    LOOP
                END IF
                RETURN cChar
            END IF
        END DO

    STATIC METHOD AdjustPrecision(cNum15 AS STRING, cNum17 AS STRING) AS STRING
        LOCAL cDiff15 := "0", cDiff17 := "0" AS STRING
        LOCAL cResult AS STRING
        LOCAL c15,c17 AS CHAR
        LOCAL n15,n17 AS INT
        LOCAL nMatch AS INT
        LOCAL lDiff  AS LOGIC
        n15 := n17 := -1
        nMatch := 0
        cResult := cNum15
        lDiff := FALSE
        DO WHILE TRUE
            c15 := NextChar(cNum15 , REF n15)
            c17 := NextChar(cNum17 , REF n17)
            IF c15 == NOCHAR .OR. c17 == NOCHAR
                IF lDiff
                    EXIT
                ELSE
                    RETURN cNum15
                END IF
            ELSEIF c15 == c17
                nMatch ++
            ELSE
                IF nMatch >= 14
                    IF nMatch < 17
                        lDiff := TRUE
                        cResult := cResult:Substring(0, n15) + c17:ToString() + cResult:Substring(n15 + 1)
                        nMatch ++
                        cDiff15 += c15:ToString()
                        cDiff17 += c17:ToString()
                    ELSE
                        EXIT
                    END IF
                ELSE
                    RETURN cNum15
                END IF
            END IF
        END DO

        // if the difference of the two numbers is the minimum one, then it was probably just a rounding issue in "G17" representation
        IF Math.Abs( Int32.Parse(cDiff15) - Int32.Parse(cDiff17) ) == 1
            RETURN cNum15
        END IF
    RETURN cResult

    STATIC METHOD FormatNumber(n AS REAL8, nLen AS INT, nDec AS INT) AS STRING
        LOCAL cFormat AS STRING
        LOCAL result AS STRING
        cFormat := GetFormatString(nLen, nDec)
        // G17 returns all 17 relevant digits for a REAL8
        // See https://docs.microsoft.com/en-us/dotnet/api/system.double.tostring?view=netframework-4.7.2
        result := String.Format(usCulture, cFormat, n)
        IF nLen > 15 .AND. result:Length >= 15
            result := AdjustPrecision(result, n:ToString("G17", usCulture))
        END IF
/*      IF result:EndsWith("0") .AND. nDec > 0 .AND. nLen > 15
            VAR cTemp := n:ToString("G17", usCulture)
            VAR parts := cTemp:Split(<CHAR>{c'.'}, StringSplitOptions.RemoveEmptyEntries)
            IF parts:Length > 1
                // If the G17 format contains a decimal part, fetch it.
                VAR cDec := parts[1+ __ARRAYBASE__]
                VAR cInt := parts[0+ __ARRAYBASE__]

                // could end up being represented in exp format. For example
                // Str3(70.00 - 65.01 - 4.99,16,2)
                // causes cTemp above to have a value of "-5.3290705182007514E-15"
                LOCAL nExp AS INT
                nExp := cDec:IndexOf('E')
                IF nExp != -1
                    IF cDec[nExp + 1] == '-'
                        LOCAL cExp AS STRING
                        cExp := System.String{'0', Int32.Parse(cDec:Substring(nExp + 2))}
                        cDec := cExp + iif(cInt[0] == '-' , cInt:SubString(1) , cInt) + cDec
                        cInt := iif(cInt[0] == '-' , "-0" , "0")
                    END IF
                END IF

                parts := result:Split(<CHAR>{c'.'}, StringSplitOptions.RemoveEmptyEntries)
                VAR cOldDec := parts[1+ __ARRAYBASE__]
                IF cDec:Length > cOldDec:Length
                    cDec := cDec:SubString(0, cOldDec:Length)
                ELSEIF cDec:Length < cOldDec:Length
                    cDec := cDec:PadRight(cOldDec:Length,c'0')
                ENDIF
                result := parts[0+ __ARRAYBASE__] + "." + cDec
            ENDIF
        ENDIF*/
        IF result:Length > nLen
            LOCAL nSepIndex AS INT
            nSepIndex := result:IndexOf(usCulture:NumberFormat:NumberDecimalSeparator)
            IF nSepIndex != -1 .AND. nSepIndex <= nLen
                result := result:Substring(0, nLen)
            ELSE
                result := Replicate("*", (DWORD) nLen)
            END IF
        ENDIF
        RETURN result

    STATIC METHOD FormatNumber(n AS INT64, nLen AS INT, nDec AS INT) AS STRING
        LOCAL cFormat AS STRING
        LOCAL result AS STRING
        cFormat := GetFormatString(nLen, 0)
        result := String.Format(usCulture, cFormat, n)
        IF result:Length > nLen
            result := Replicate("*", (DWORD) nLen)
        ENDIF
        RETURN result

    STATIC METHOD AdjustDecimalSeparator(cString AS STRING) AS STRING
        IF cString:IndexOf(".") >= 0
            VAR wSep   := SetDecimalSep()
            IF wSep != 46
                cString := cString:Replace(c'.', (CHAR) wSep)
            ENDIF
        ENDIF
        RETURN cString

    STATIC METHOD GetSignificantWholeDigits(r AS REAL8) AS INT
        LOCAL nRet := IIF(r < 0.0 , 1 , 0) AS INT
        r := Math.Floor(Math.Abs(r))
        DO WHILE r > 0.0
            nRet ++
            r := r /10.0
            r := Math.Floor(r)
        END DO
    RETURN nRet

END CLASS

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/ashexstring/*" />
/// <seealso cref='C2Hex(System.String)' >C2Hex</seealso>
/// <seealso cref='_C2Hex(System.String,System.Boolean)' >_C2Hex</seealso>
FUNCTION AsHexString(uValue AS USUAL) AS STRING
    LOCAL result AS STRING
    IF uValue:IsString
        result := _C2Hex( (STRING) uValue, TRUE)
    ELSEIF uValue:IsNumeric
        IF uValue:IsInt64
            result := String.Format("{0:X16}", (INT64) uValue)
        ELSEIF uValue:IsLong
            result := String.Format("{0:X8}", (INT) uValue)
        ELSEIF uValue:IsFloat
            VAR flValue := (FLOAT) uValue
            VAR r8Value := flValue:Value
            IF r8Value >= System.Int32.MinValue .AND. r8Value <= System.Int32.MaxValue
                result := String.Format("{0:X8}", (INT) uValue)
            ELSEIF r8Value >= 0. .AND. r8Value <= System.UInt32.MaxValue
                result := String.Format("{0:X8}", (DWORD) uValue)
            ELSEIF r8Value >= System.Int64.MinValue .AND. r8Value <= System.Int64.MaxValue
                result := String.Format("{0:X16}", (INT64) uValue)
            ELSEIF r8Value >= 0. .AND. r8Value <= System.UInt64.MaxValue
                result := String.Format("{0:X16}", (UINT64) uValue)
            ELSE
                result := "********"
            ENDIF
        ELSE
            result := ""
        ENDIF
    ELSEIF uValue:IsPtr
        LOCAL i64 := (UIntPtr)uValue AS UIntPtr
        LOCAL u64 := i64:ToUInt64() AS UInt64
        result := String.Format( iif( u64 > System.UInt32.MaxValue, "{0:X16}", "{0:X8}" ), u64 )
    ELSE
        result := ""
    ENDIF
    RETURN result

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/aspadr/*" />
FUNCTION AsPadr(uValue AS USUAL,wLen AS DWORD) AS STRING
    RETURN PadR(AsString(uValue), wLen)

/// <exclude />
FUNCTION _AsString(u AS USUAL) AS STRING
    RETURN	 AsString(u)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/asstring/*" />
FUNCTION AsString(uValue AS USUAL) AS STRING
    LOCAL result AS STRING
    DO CASE
        CASE uValue:IsString
            result := (STRING) uValue
        CASE uValue:IsNumeric
            result := NTrim(uValue)
        CASE uValue:IsSymbol
            result := Symbol2String( (SYMBOL) uValue)
        CASE uValue:IsDate
            result := DToC( (DATE) uValue)
        CASE uValue:IsPtr
            LOCAL i64 := (UIntPtr)uValue AS UIntPtr
            LOCAL u64 := i64:ToUInt64() AS UInt64
            result := String.Format( iif( u64 > System.UInt32.MaxValue, "0x{0:X16}", "0x{0:X8}" ), u64 )
        CASE uValue:IsArray
            VAR aValue := (ARRAY) uValue
            //  {[0000000003]0x025400FC}
            IF aValue == NULL_ARRAY
                result := "{[0000000000]0x00000000}"
            ELSE
                VAR cHashCode := String.Format("{0:X8}", aValue:GetHashCode())
                result := "{["+String.Format("{0:D10}",aValue:Length)+"]0x"+cHashCode+"}"
            ENDIF

        CASE uValue:IsObject
            LOCAL oValue := uValue AS OBJECT
            IF oValue == NULL_OBJECT
                result := "{(0x0000)0x00000000} CLASS "
            ELSE
                VAR oType := oValue:GetType()
                VAR nSize := oType:GetFields():Length *4
                VAR cHashCode := String.Format("{0:X8}", oValue:GetHashCode())
                result := "{(0x"+String.Format("{0:X4}", nSize)+")0x"+cHashCode+"} CLASS " + oType:Name:ToUpperInvariant()
            ENDIF
        OTHERWISE
            result := uValue:ToString()
    ENDCASE
    RETURN result

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/assymbol/*" />
FUNCTION AsSymbol(uValue AS USUAL) AS SYMBOL
    RETURN SYMBOL{(STRING)uValue, TRUE}

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/descend/*" />
FUNCTION Descend(uValue AS USUAL) AS USUAL
    IF uValue:IsString
        RETURN _descendingString( (STRING) uValue)
    ELSEIF uValue:IsLogic
        RETURN ! (LOGIC) uValue
    ELSEIF uValue:IsLong
        RETURN 0 - (INT) uValue
    ELSEIF uValue:IsInt64
        RETURN 0 - (INT64) uValue
    ELSEIF uValue:IsFloat
        RETURN 0 - (__Float) uValue
    ELSEIF uValue:IsDecimal
        RETURN 0 - (Decimal) uValue
    ELSEIF uValue:IsCurrency
        RETURN 0 - (__Currency) uValue
    ELSEIF uValue:IsDate
        RETURN 5231808 - (DWORD)(DATE) uValue
    ENDIF
    RETURN uValue

INTERNAL FUNCTION _descendingString(s AS STRING) AS STRING
    VAR sb  := StringBuilder{s}
    VAR nlen := s:Length-1
    LOCAL i AS INT
    FOR i := 0 TO nlen
        IF sb[i] < 256
            sb[i] := (CHAR) (255 - sb[i])
        ENDIF
    NEXT
    RETURN sb:ToString()

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/descend/*" />
FUNCTION DescendA(uValue REF USUAL) AS USUAL
    uValue := Descend(uValue)
    RETURN uValue

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/ntrim/*" />
FUNCTION NTrim(nNum AS USUAL) AS STRING
    LOCAL ret AS STRING
    SWITCH nNum:_usualType
    CASE __UsualType.Int64
    CASE __UsualType.Long
      ret := ConversionHelpers.FormatNumber( (INT64) nNum, (INT) RuntimeState.Digits, 0):Trim()
    CASE __UsualType.Date
      ret := AsString( nNum )
    CASE __UsualType.Currency
      ret := ((__Currency) (nNum)):ToString()
    CASE __UsualType.Float
    CASE __UsualType.Decimal
      ret := ConversionHelpers.AdjustDecimalSeparator(_Str1(  (FLOAT) nNum )):Trim()
    OTHERWISE
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNum), 1, nNum)
   END SWITCH
   RETURN ret

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/pad/*" />
FUNCTION Pad( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    RETURN PadR( uValue, nLength, cFillChar )

/// <inheritdoc cref="Pad(XSharp.__Usual,System.Int32,System.String)" />
FUNCTION Pad( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadR( uValue, (INT) nLength, cFillChar )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/padc/*" />
FUNCTION PadC( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    // If they send in an empty string then change to " "
    IF cFillChar == NULL .OR. cFillChar :Length == 0
        cFillChar := " "
    ENDIF

    LOCAL ret     AS STRING
    LOCAL retlen  AS INT

    IF uValue:IsNil
        ret := ""
    ELSEIF uValue:IsNumeric
        ret := NTrim( uValue)
    ELSE
        ret := uValue:ToString()
    ENDIF
    retlen := ret:Length

    IF retlen > nLength
        ret := ret:Remove( nLength )
    ELSE
        VAR leftSpace := System.String{cFillChar[0], ( nLength - retlen ) / 2}
        ret := leftSpace+ret
        ret := ret:PadRight( nLength, cFillChar[0] )
    ENDIF

    RETURN ret

/// <inheritdoc cref="PadC(XSharp.__Usual,System.Int32,System.String)" />
FUNCTION PadC( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadC( uValue, (INT) nLength, cFillChar )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/padl/*" />
FUNCTION PadL( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    // If they send in an empty string then change to " "
    IF cFillChar == NULL .OR. cFillChar :Length == 0
        cFillChar := " "
    ENDIF
    LOCAL ret AS STRING
    IF uValue:IsNil
        ret := ""
    ELSEIF uValue:IsNumeric
        ret := NTrim( uValue)
    ELSE
        ret := uValue:ToString()
    ENDIF
    RETURN IIF( ret:Length > nLength, ret:Remove( nLength ), ret:PadLeft( nLength, cFillChar[0] ) )

/// <inheritdoc cref="PadL(XSharp.__Usual,System.Int32,System.String)" />
FUNCTION PadL( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadL( uValue, (INT) nLength, cFillChar )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/padr/*" />
FUNCTION PadR( uValue AS USUAL, nLength AS DWORD, cFillChar := " " AS STRING ) AS STRING
    RETURN PadR( uValue, (INT) nLength, cFillChar )

/// <inheritdoc cref="PadR(XSharp.__Usual,System.UInt32,System.String)" />
FUNCTION PadR( uValue AS USUAL, nLength AS INT, cFillChar := " " AS STRING ) AS STRING
    // If they send in an empty string then change to " "
    IF cFillChar == NULL .OR. cFillChar:Length == 0
        cFillChar := " "
    ENDIF
    LOCAL ret AS STRING
    IF uValue:IsNil
        ret := ""
    ELSEIF uValue:IsNumeric
        ret := NTrim( uValue)
    ELSE
        ret := uValue:ToString()
    ENDIF
    RETURN IIF( ret:Length > nLength, ret:Remove( nLength ), ret:PadRight( nLength, cFillChar[0] ) )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str/*" />
FUNCTION Str(nNumber ,nLength ,nDecimals ) AS STRING CLIPPER
    IF PCount() < 1 .OR. pCount() > 3
        RETURN ""
    ENDIF

    // Handle integer values
    IF nNumber:IsInteger .and. (PCount() <= 2 .or. (PCount() == 3 .and. nDecimals:IsNumeric .and. nDecimals == 0) )
    	LOCAL cRet AS STRING
    	LOCAL nDigits AS INT

    	cRet := ((INT64)nNumber):ToString()

    	IF nLength:IsNumeric
    		IF nLength < 0
    			nDigits := cRet:Length
    		ELSE
	    		nDigits := nLength
    		END IF
    	ELSE
    		nDigits := (INT)RuntimeState.Digits
    	END IF

    	IF cRet:Length > nDigits
    		cRet := System.String{c'*' , nDigits}
    	ELSEIF cRet:Length < RuntimeState.Digits
    		cRet := cRet:PadLeft(nDigits)
    	END IF

    	RETURN cRet
    ELSEIF nNumber:IsFloat
        LOCAL oFloat AS FLOAT
        oFloat := nNumber
        IF oFloat:Digits != -1 .and. IsNil(nLength)
        	nLength := oFloat:Digits
        END IF
    END IF

    LOCAL result AS STRING
    LOCAL nLen AS DWORD
    LOCAL nDec AS DWORD
    LOCAL lTrimSpaces := FALSE AS LOGIC
    IF nLength:IsNumeric
        IF nLength < 0
            nLen := System.UInt32.MaxValue - 1
            lTrimSpaces := TRUE
        ELSE
            nLen := (DWORD) nLength
        ENDIF
    ELSE
        nLen := System.UInt32.MaxValue
    ENDIF
    IF ! nDecimals:IsNumeric
        nDec := UInt32.MaxValue
    ELSE
        IF nDecimals < 0
            nDec := System.UInt32.MaxValue
        ELSE
            nDec := (DWORD) nDecimals
        ENDIF
    ENDIF
    result := _Str3(nNumber, nLen, nDec)
    IF lTrimSpaces
        result := result:TrimStart()
    END IF
    RETURN ConversionHelpers.AdjustDecimalSeparator(result)

/// <inheritdoc cref="Str" />
/// <returns>The returned string with always have a DOT as decimal separator.</returns>
FUNCTION _Str(nValue ,uLen ,uDec ) AS STRING CLIPPER
    LOCAL nLen,  nDec AS LONG
    LOCAL dwLen, dwDec AS DWORD
    IF PCount() > 0 .AND. ! nValue:IsNumeric
       THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nValue),1, nValue, uLen, uDec)
    ENDIF
    SWITCH PCount()
    CASE 1
        RETURN _Str1( nValue)
    CASE 2
        IF ! uLen:IsNumeric
            THROW Error.DataTypeError( __FUNCTION__, NAMEOF(uLen),2,nValue, uLen, uDec)
        ENDIF
        nLen := uLen
        IF nLen < 0
            dwLen := System.UInt32.MaxValue
        ELSE
            dwLen := (DWORD) nLen
        ENDIF
        IF nValue:IsFractional
            RETURN _Str2(nValue, dwLen)
        ELSE
            RETURN ConversionHelpers.FormatNumber((INT64) nValue, nLen,0)
        ENDIF
    CASE 3
        IF ! uLen:IsNumeric
            THROW Error.DataTypeError( __FUNCTION__, NAMEOF(uLen),2,nValue, uLen, uDec)
        ENDIF
        IF ! uDec:IsNumeric
            THROW Error.DataTypeError( __FUNCTION__, NAMEOF(uDec),3,nValue, uLen, uDec)
        ENDIF
        nLen := uLen
        nDec := uDec
        IF nValue:IsFractional
            nLen := uLen
            IF nLen < 0
                dwLen := System.UInt32.MaxValue
            ELSE
                dwLen := (DWORD) nLen
            ENDIF

            IF nDec < 0 .OR. RuntimeState.Fixed
                dwDec := XSharp.RuntimeState.Decimals
            ELSE
                dwDec := (DWORD) nDec
            ENDIF
            IF nLen < 0 .OR. RuntimeState.DigitsFixed
                dwLen := XSharp.RuntimeState.Digits
                IF nDec != 0
                    dwLen := dwLen + dwDec + 1
                ENDIF
            ENDIF
            VAR res := _Str3(nValue, dwLen, dwDec)
            IF nLen < 0
                res := res:TrimStart()
            ENDIF
            RETURN res
        ELSE
            IF nLen < 0
                nLen := 30
            ENDIF
            IF nDec < 0
                nDec := (INT) SetDecimal()
            ENDIF
            RETURN ConversionHelpers.FormatNumber((INT64) nValue, nLen,nDec)
        ENDIF
    OTHERWISE
        RETURN ""
    END SWITCH

// The following three functions are undocumented in Vulcan but sometimes used in user code
// They are the equivalent of the STR() functions but always return with digit decimal separator
// We route all three to the _Str() function that takes care of this already
/// <exclude/>
FUNCTION __Str(n AS USUAL) AS STRING
    RETURN _Str( n)

/// <exclude/>
FUNCTION __Str(n AS USUAL,nLen AS USUAL) AS STRING
    RETURN _Str( n, nLen)

/// <exclude/>
FUNCTION __Str(n AS USUAL,nLen AS USUAL, nDec AS USUAL) AS STRING
    RETURN _Str( n, nLen, nDec)

INTERNAL FUNCTION _PadZero(cValue AS STRING) AS STRING
    LOCAL iLen := 	cValue:Length AS INT
    cValue := cValue:TrimStart()
    IF cValue:Length > 1 .and. cValue[0] == c'-'
    	cValue := cValue:Substring(1)
        RETURN "-" + cValue:PadLeft((INT) iLen - 1, c'0')
    END IF
    RETURN cValue:PadLeft((INT) iLen, c'0')

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strzero/*" />
FUNCTION StrZero(nNumber AS USUAL,nLength AS INT,nDecimals AS INT) AS STRING
    IF ! ( nNumber:IsNumeric )
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNumber),1,nNumber)
    ENDIF
    LOCAL cValue := Str3(nNumber, (DWORD) nLength, (DWORD) nDecimals) AS STRING
    RETURN _PadZero(cValue)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strzero/*" />
FUNCTION StrZero(nNumber AS USUAL,nLength AS INT) AS STRING
    IF ! ( nNumber:IsNumeric )
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNumber),1,nNumber)
    ENDIF
    LOCAL cValue := Str3(nNumber, (DWORD) nLength, 0) AS STRING // Str3() and Str2() in VO have totally different behavior regarding decimal digits, rounding etc
    RETURN _PadZero(cValue)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strzero/*" />
FUNCTION StrZero(nNumber AS USUAL) AS STRING
    IF ! ( nNumber:IsNumeric )
      THROW Error.DataTypeError( __FUNCTION__, NAMEOF(nNumber),1,nNumber)
    ENDIF
    LOCAL cValue := Str1(nNumber) AS STRING
    RETURN _PadZero(cValue)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/toword/*" />
FUNCTION ToWord(n AS USUAL) AS DWORD
    RETURN (DWORD) n

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strint/*" />
FUNCTION StrInt(liNumber AS LONG,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN Str3( liNumber, dwLength, dwDecimals)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strlong/*" />
FUNCTION StrLong(liNumber AS LONG,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN StrInt(liNumber, dwLength, dwDecimals)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/strfloat/*" />
FUNCTION StrFloat(flSource AS FLOAT,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN Str3( flSource, dwLength, dwDecimals )

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str1/*" />
FUNCTION Str1(fNumber AS USUAL) AS STRING
    IF fNumber:IsFloat
        RETURN ConversionHelpers.AdjustDecimalSeparator(_Str1( (FLOAT) fNumber))
    ELSE
        RETURN ConversionHelpers.FormatNumber( (LONG) fNumber, (INT) RuntimeState.Digits, 0):Trim()
    ENDIF

INTERNAL FUNCTION _Str1(f AS FLOAT) AS STRING
    VAR nDecimals := f:Decimals
    VAR nDigits   := f:Digits

    SWITCH (Double)f
    CASE Double.NaN
        RETURN Double.NaN:ToString()
    CASE Double.PositiveInfinity
        RETURN Double.PositiveInfinity:ToString()
    CASE Double.NegativeInfinity
        RETURN Double.NegativeInfinity:ToString()
    END SWITCH

    IF nDecimals < 0 .OR. RuntimeState.Fixed
        nDecimals := (SHORT) RuntimeState.Decimals
    ENDIF
    IF nDigits <= 0 .OR. RuntimeState.DigitsFixed
        nDigits := (SHORT) RuntimeState.Digits
        IF ConversionHelpers.GetSignificantWholeDigits(f) > nDigits
            RETURN STRING{ c'*', (INT) nDigits}
        END IF
        IF nDecimals != 0
            nDigits += nDecimals +1
        ENDIF
    ENDIF
    VAR result := ConversionHelpers.FormatNumber(f, nDigits, nDecimals )
    RETURN result

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str2/*" />
/// <returns>A string representation of the value.</returns>
/// <seealso cref="Str" />
/// <seealso cref="Str1" />
/// <seealso cref="Str3" />
FUNCTION Str2(fNumber AS FLOAT,dwLength AS DWORD) AS STRING
    RETURN ConversionHelpers.AdjustDecimalSeparator(_Str2(fNumber, dwLength))

INTERNAL FUNCTION _Str2(f AS FLOAT,dwLen AS DWORD) AS STRING
  IF dwLen == 0 .OR. RuntimeState.DigitsFixed
      dwLen := (DWORD) RuntimeState.Digits
   ELSEIF dwLen  != UInt32.MaxValue
      dwLen := Math.Min( dwLen, MAXDIGITS )
   ENDIF
   VAR nDecimals := f:Decimals
    IF nDecimals < 0 .OR. RuntimeState.DigitsFixed
        nDecimals := (SHORT) RuntimeState.Decimals
    ENDIF
   RETURN ConversionHelpers.FormatNumber(f, (INT) dwLen, nDecimals)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/str3/*" />
/// <seealso cref="Str" />
/// <seealso cref="Str1" />
/// <seealso cref="Str2" />
FUNCTION Str3(fNumber AS FLOAT,dwLength AS DWORD,dwDecimals AS DWORD) AS STRING
    RETURN ConversionHelpers.AdjustDecimalSeparator(_Str3(fNumber, dwLength, dwDecimals))

/// <inheritdoc cref="Str3" />
/// <returns>A string with DOT as decimal separator.</returns>
FUNCTION _Str3(f AS FLOAT,dwLen AS DWORD,dwDec AS DWORD) AS STRING

   LOCAL lUnspecifiedDecimals := dwDec == UInt32.MaxValue AS LOGIC
   IF dwDec == UInt32.MaxValue
        IF RuntimeState.Fixed
            dwDec := (DWORD) RuntimeState.Decimals
        ELSE
            dwDec := (DWORD) f:Decimals
        ENDIF
   ELSE
      dwDec := Math.Min( dwDec, MAXDECIMALS )
   ENDIF

   // dwLen == UInt32.MaxValue     : Nil passed for 2nd param of Str()
   // dwLen == UInt32.MaxValue - 1 : Negative value for 2nd param of Str()

   IF dwLen == 0 .OR. dwLen == UInt32.MaxValue .or. dwLen == UInt32.MaxValue - 1
        IF dwDec > 0
        	LOCAL nSignificant AS INT
        	nSignificant := ConversionHelpers.GetSignificantWholeDigits(f)
            IF .not. dwLen == UInt32.MaxValue - 1
	            IF nSignificant > RuntimeState.Digits
	                RETURN STRING{ c'*', (INT) (RuntimeState.Digits + dwDec +1) } // VO's behavior...
	            END IF
            END IF
            IF dwLen == UInt32.MaxValue - 1
	            dwLen := (DWORD) nSignificant + dwDec + 2
            ELSE
	            dwLen := (DWORD) RuntimeState.Digits + dwDec +1
            END IF
        ELSE
            dwLen := (DWORD) RuntimeState.Digits
        ENDIF
   ELSE
      dwLen := Math.Min( dwLen, MAXDIGITS )
   ENDIF

 IF dwDec > 0 .AND. dwLen != UInt32.MaxValue .and. !lUnspecifiedDecimals .AND. ( dwLen < ( dwDec + 2 ) )
      RETURN STRING{ c'*', (INT) dwLen }
   ENDIF
   RETURN ConversionHelpers.FormatNumber(f, (INT) dwLen, (INT) dwDec)

/// <inheritdoc cref="Val" />
/// <returns>The numeric value as a FLOAT.</returns>
FUNCTION StrToFloat(c AS STRING) AS FLOAT
    RETURN (FLOAT) Val(c)

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/val/*" />
FUNCTION Val(cNumber AS STRING) AS USUAL
    LOCAL isCurrency AS LOGIC
    IF String.IsNullOrEmpty(cNumber)
      RETURN 0
    ENDIF
    cNumber := AllTrim(cNumber)
    isCurrency := cNumber:StartsWith("$")
    IF isCurrency
        cNumber := cNumber:Substring(1)
    ENDIF
    VAR result := _VOVal(cNumber)
    IF isCurrency
        RETURN __Currency{ (REAL8) result }
    ENDIF
    RETURN result

//implements the quirks of VO's version of Val()
INTERNAL FUNCTION _VOVal(cNumber AS STRING) AS USUAL
    IF String.IsNullOrEmpty(cNumber)
      RETURN 0
    ENDIF
    cNumber := cNumber:Trim():ToUpper()
    // find non numeric characters in cNumber and trim the field to that length
    VAR pos := 0
    VAR done := FALSE
    VAR hex  := FALSE
    VAR hasdec := FALSE
    VAR hasexp := FALSE
    VAR lNoNumYet := TRUE
    VAR cPrev  := c' '
    VAR cDec   := (CHAR) RuntimeState.DecimalSep

    IF cDec != c'.'
        cNumber := cNumber:Replace(c'.', cDec) // VO behavior...
        cNumber := cNumber:Replace(cDec, c'.')
    ENDIF
    FOREACH VAR c IN cNumber
        SWITCH c
        CASE c'0'
        CASE c'1'
        CASE c'2'
        CASE c'3'
        CASE c'4'
        CASE c'5'
        CASE c'6'
        CASE c'7'
        CASE c'8'
        CASE c'9'
            lNoNumYet := FALSE
        CASE c'-'
        CASE c'+'
            IF lNoNumYet
                lNoNumYet := TRUE
            ELSE
            	done := TRUE
            ENDIF
        CASE c' '
            IF .not. lNoNumYet
            	done := TRUE
            ENDIF
        CASE c'.'
        CASE c','
            IF c == c',' .AND. cDec != c',' // Don't ask, VO...
                done := TRUE
            ELSEIF hasdec
                done := TRUE
            ELSE
                hasdec := TRUE
            ENDIF
        CASE c'A'
        CASE c'B'
        CASE c'C'
        CASE c'D'
        CASE c'F'
            IF !hex
                done := TRUE
            ENDIF
        CASE c'E'
            // exponentional notation only allowed if decimal separator was there
            IF hasdec
                hasexp := TRUE
            ELSE
                IF !hex
                    done := TRUE
                ENDIF
            ENDIF
        CASE c'L'	// LONG result
        CASE c'U'	// DWORD result
            done := TRUE
        CASE c'X'
            IF cPrev == c'0' .and. !hex
                hex := TRUE
            ELSE
                done := TRUE
            ENDIF
        OTHERWISE
            done := TRUE
        END SWITCH
        IF done
            EXIT
        ENDIF
        pos += 1
        cPrev := c
    NEXT
    IF pos < cNumber:Length
        cNumber := cNumber:Substring(0, pos)
    ENDIF
    IF cNumber:IndexOf('-') == 0 .and. cNumber:Length > 2 .and. cNumber[1] == c' '
        cNumber := "-" + cNumber:Substring(1):Trim()
    END IF

    IF cNumber:IndexOfAny(<CHAR> {c'.'}) > -1

        IF cDec != c'.'
            cNumber := cNumber:Replace(cDec, c'.')
        ENDIF
        VAR style := NumberStyles.Number
        IF hasexp
            style |= NumberStyles.AllowExponent
        ENDIF
        IF System.Double.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR r8Result)
            RETURN __Float{ r8Result , cNumber:Length - cNumber:IndexOf(c'.') - 1}
        ENDIF

    ELSE

        LOCAL style AS NumberStyles
        IF hex

            style := NumberStyles.HexNumber

            LOCAL nHexTagPos AS INT
            nHexTagPos := cNumber:IndexOf("0x")
            IF nHexTagPos == -1
                nHexTagPos := cNumber:IndexOf("0X")
            ENDIF
            IF nHexTagPos != -1
                cNumber := cNumber:Substring(0, nHexTagPos) + cNumber:Substring(nHexTagPos + 2):Trim()
            ENDIF
            LOCAL lNegativeHex := FALSE AS LOGIC
            IF cNumber:IndexOf('-') == 0
            	cNumber := cNumber:Substring(1)
            	lNegativeHex := TRUE
            ELSEIF cNumber:IndexOf('+') == 0
            	cNumber := cNumber:Substring(1)
            END IF

            System.Int64.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR iResult)
            IF lNegativeHex
                iResult := - iResult
            ENDIF
            IF Math.Abs(iResult) <= Int32.MaxValue
                RETURN (INT) iResult
            ELSE
                RETURN __Float{ (REAL8) iResult , 0 }
            ENDIF

        ELSE

            style := NumberStyles.Integer

            IF cNumber:Length <= 9 // yes, no matter if there's a sign char or not
                System.Int32.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR iResult)
                RETURN iResult
            ELSE
                System.Double.TryParse(cNumber, style, ConversionHelpers.usCulture, OUT VAR rResult)
                RETURN __Float{rResult, 0}
            ENDIF

        ENDIF

    ENDIF
    RETURN 0

/// <summary>
/// Convert an object containing a numeric value to a FLOAT
/// </summary>
/// <param name="oValue">Object containing the numeric value to convert.</param>
/// <returns>The value in the form of a float. </returns>
/// <exception cref='System.InvalidCastException'> Thrown when the parameter oValue cannot be converted to a FLOAT.</exception>
FUNCTION Object2Float(oValue AS OBJECT) AS FLOAT
    LOCAL typ := oValue:GetType() AS System.Type
    IF typ == typeof(FLOAT)
        RETURN (FLOAT) oValue
    ENDIF
    LOCAL tc := System.Type.GetTypeCode(typ) AS TypeCode
    SWITCH tc
    CASE System.TypeCode.SByte
        RETURN (System.SByte) oValue
    CASE System.TypeCode.Byte
        RETURN (System.Byte) oValue
    CASE System.TypeCode.Double
        RETURN (System.Double) oValue
    CASE System.TypeCode.Single
        RETURN (System.Single) oValue
    CASE System.TypeCode.UInt16
        RETURN (System.UInt16) oValue
    CASE System.TypeCode.UInt32
        RETURN (System.UInt32) oValue
    CASE System.TypeCode.UInt64
        RETURN (System.UInt64) oValue
    CASE System.TypeCode.Int16
        RETURN (System.Int16) oValue
    CASE System.TypeCode.Int32
        RETURN (System.Int32) oValue
    CASE System.TypeCode.Int64
        RETURN (System.Int64) oValue
    CASE System.TypeCode.Decimal
        RETURN (System.Decimal) oValue
    OTHERWISE
        THROW InvalidCastException{"Cannot convert from type "+typ:FullName+" to FLOAT"}
    END SWITCH

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/bin2f/*" />
FUNCTION Bin2F(cFloat AS STRING) AS FLOAT
    LOCAL nDec AS WORD
    LOCAL val  AS REAL8
    IF SLen(cFloat) >= 12
        nDec := Bin2W(SubStr3(cFloat, 11,2))
        val  := Bin2Real8(SubStr3(cFloat, 1,8))
        RETURN FLOAT{val, 0, nDec}
    ENDIF
    RETURN 0.0

/// <include file="VoFunctionDocs.xml" path="Runtimefunctions/f2bin/*" />
FUNCTION F2Bin(fValue AS FLOAT) AS STRING
    RETURN Real82Bin(fValue:Value)+ e"\0\0" + W2Bin((WORD)fValue:Decimals)