sprintf / sprintf_s in 64-bit mode

Started by Roland Stowasser, June 24, 2024, 09:33:41 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Roland Stowasser

Hi Charles,

I tried this little program:
$filename "test_sprintf.exe"

'uses rtl32
uses rtl64

uses corewin
uses console

SetConsoleTitle "sprintf / sprintf_s Examples"
double num
char buff[64]

num = 333 * 1.5
sprintf (buff, "%.2f", num)
printl "333*1.5 = " buff

num = 100 / 13
sprintf_s (buff,64, "%.4f" , num)
printl "100/13 = "buff

printl
printl

sprintf (buff, "Value of pi = %.2f", pi)
printl buff

int arg1 = 27, arg2 = 9, result
result = arg1 * arg2
sprintf(buff, "When %d is multiplied with %d the result is: %d", arg1, arg2, result)
printl buff

int age = 30
double height = 1.75   'float / single does not work
string name = "John"
sprintf(buff, "%s is %d years old and %.2f meters tall.", name, age, height)
'Result: "John is 30 years old and 1.75 meters tall."
printl buff

printl
printl "Enter ..."
waitkey

If I use msvcrt.inc as is, then the app only works correctly in 32-bit..
Therefore, I changed in msvcrt.inc:

...
//! sprintf
int  sprintf(char *buffer, const char *format, ...)
//! sprintf_s
int  sprintf_s(char *buffer, int size, const char *format, ...)
...

now the app does also work in 64-bit mode.

I know that many functions of msvcrt.dll work if applied unprototyped with Oxygen, but it seems to me that functions with variable arguments need prototyping. Is there another solution?

Charles Pegge

#1
Hi Roland,

Interesting anomaly!

In 64bit mode, The variadic sprintf looks for the number in register rdx even though it is a double, normally passed in the simd register xmmm2.

The solution (hack) is to cast the number as a quad integer so it goes into the rdi register, where it will be picked up by sprintf.

sprintf (buff, "%.2f", (quad) num)

Roland Stowasser

Hi Charles,

this is really curious. If I use (quad) num, I can use sprintf unprototyped. But (quad)pi does not work. I have to define: double my_pi = pi and  (quad)my_pi to get the correct result.

I tried these statements:

int diameter = 10
sprintf (buff, "Circumferece: %d * %.3f = %.2f", diameter, pi, diameter * pi)
printl buff

the output is:
Circumference: 10 * 0.000 = 31.42

But if all else fails, I can use prototyped functions in 64-bit mode for these cases


Roland Stowasser

Maybe I can use this strategy, which works in 32-bit and 64-bit mode and it does not interfere with the original Msvcrt.inc file:

$filename "test_sprintf.exe"

'uses rtl32
uses rtl64

uses corewin
uses console

#ifdef mode64bit
! sprintf   lib "msvcrt.dll" (char *buffer, const char *format, ...) as sys
! sprintf_s lib "msvcrt.dll" (char *buffer, int size, const char *format, ...) as sys
#endif

SetConsoleTitle "sprintf / sprintf_s Examples"
double num
char buff[64]

num = 333 * 1.5
sprintf (buff, "%.2f", num)
printl "333*1.5 = " buff

num = 100 / 13
sprintf_s (buff,64, "%.4f" , num)
printl "100/13 = "buff

printl
printl
double my_pi = pi
sprintf (buff, "Value of pi = %.2f", pi)
printl buff
sprintf (buff, "Value of my_pi = %.2f", my_pi)
printl buff

int arg1 = 27, arg2 = 9, result
result = arg1 * arg2
sprintf(buff, "When %d is multiplied with %d the result is: %d", arg1, arg2, result)
printl buff

int age = 30
double height = 1.75
string name = "John"
sprintf(buff, "%s is %d years old and %.2f meters tall.", name, age, height)
'Result: "John is 30 years old and 1.75 meters tall."
printl buff

int diameter = 10
sprintf (buff, "Circumference: %d * %.3f = %.2f", diameter, pi, diameter * pi)
printl buff

printl
printl "Enter ..."

waitkey


Charles Pegge

To match the %f sprintf specification, your diameter variable must be a double, and it must be cast (quad) as previously.

This is certainly an edge-case for unprototyped calls :)

Roland Stowasser

#5
Hi Charles,

It took me some time to create a small demo that should show the advantages of sprintf over str(). Line 233 - 238 use sprintf. If try to use str() I will not achieve the desired result,. Therefore sprintf is a very useful formating feature and I think it is used in several programming languages, among other functions of msvcrt.dll. But perhaps you can adapt str()?

I am still hesitating to apply the quad approach. In 32-bit I do not need this, it even works without prototyping. Therefore I apply prototyping only in 64-bit. Is there a side effect that speaks against it? The best solution would actually be to carry out prototyping directly from time to time with the variadic functions in msvcrt.dll, as this is obviously very possible in Oxygenbasic. But I would of course like to use the original .inc files, so I will not change them.

Amort-Demo.o2bas:

' ************************************************
'  Adapted from: Amort.bas by Kevin Diggins(MrBCX)
'  ported to Oxygenbasic (simplified)     
' ************************************************
'  AMORT is a small loan amortization program 
' ************************************************

$ filename "Amort-Demo.exe"

'uses rtl32
'uses rtl64

uses winutil

#ifdef mode64bit
! sprintf lib "msvcrt.dll" (char *buffer, const char *format, ...) as sys
#endif

typedef sys control

% DS_MODALFRAME=128
% SS_NOTIFY = 256
% SS_LEFT = 0

% LVS_EX_GRIDLINES 1 
% LVS_EX_FULLROWSELECT  0x0020
% LVM_SETEXTENDEDLISTVIEWSTYLE 0x1036
% LVCF_TEXT=4
% LVM_SETCOLUMN = 4122
% LVM_SETCOLUMNWIDTH=4126
% LVSCW_AUTOSIZE  -1
% LVSCW_AUTOSIZE_USEHEADER = -2
% LVM_DELETEALLITEMS = 4105
% LVIF_TEXT=1
% LVM_INSERTITEM=4103
% LVCF_FMT 1
% LVCF_WIDTH 2
% LVCF_SUBITEM 8
% LVCFMT_LEFT = 0
% LVM_INSERTCOLUMN = 4123
% LVS_REPORT = 1
% LVS_SHAREIMAGELISTS = 64
% LVS_EDITLABELS = 512
% LVM_SETITEMTEXT = 4142


% HDI_FORMAT = 4
% HDF_STRING = 16384
% HDM_SETITEM = 4612
% HDF_LEFT = 0
% HDF_RIGHT = 1


type LVCOLUMN
  uint  mask
  int   fmt
  int   cx
  char* pszText
  int   cchTextMax
  int   iSubItem
  int   iImage
  int   iOrder
  int   cxMin
  int   cxDefault
  int   cxIdeal 
end type
typedef LVCOLUMN LV_COLUMN

type LVITEM 
  uint   mask
  int    iItem
  int    iSubItem
  uint   state
  uint   stateMask
  char*  pszText
  int    cchTextMax
  int    iImage       // index of the list view item's icon
  sys    lParam       // 32-bit value to associate with item
  int    iIndent
  int    iGroupId
  uint   cColumns
  uint   *puColumns
  int    *piColFmt
  int    iGroup
end type
typedef LVITEM LV_ITEM

type HDITEM 
  uint     mask 
  int      cxy
  char*    pszText
  sys      hbm
  int      cchTextMax
  int      fmt
  sys      lParam
  int      iImage
  int      iOrder
'#if (_WIN32_IE >= 0x0500)
  uint     type_    'type
  sys      pvFilter 'LPVOID
'#endif /* _WIN32_IE >= 0x0500 */
'#if (_WIN32_WINNT >= 0x0600)
  uint     state
'#endif /* _WIN32_WINNT >= 0x0600 */ 
end type
typedef HDITEM HD_ITEM


macro ListView_DeleteAllItems(hwnd) (SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0))
macro ListView_InsertItem(hwnd,pitem) (SendMessage(hwnd, LVM_INSERTITEM,0, pitem))
macro ListView_InsertColumn(hwnd,iCol,pcol) (SendMessage(hwnd, LVM_INSERTCOLUMN, iCol, pcol))
macro ListView_SetItemText(hwnd,i,iSubItem_,pszText_) 
  LV_ITEM _ms_lvi
  _ms_lvi.iSubItem = iSubItem_
  _ms_lvi.pszText = pszText_
  SendMessage((hwnd),LVM_SETITEMTEXT, i, &_ms_lvi)
end macro 

    ' create a structure of INITCOMMONCONTROLSEX
    INITCOMMONCONTROLSEXt iccex
    iccex.dwSize=sizeof(iccex)
    'Register Common Controls
    iccex.dwICC= 0xffff
    InitCommonControlsEx(&iccex)

GLOBAL Input1  AS CONTROL
GLOBAL Input2  AS CONTROL
GLOBAL Input3  AS CONTROL
GLOBAL Label1  AS CONTROL
GLOBAL Label2  AS CONTROL
GLOBAL Label3  AS CONTROL
GLOBAL Label4  AS CONTROL
GLOBAL Button1 AS CONTROL
GLOBAL LView1  AS CONTROL

SUB Set_ColumnText(ByVal hWnd AS sys, ByVal Column as int, ByVal Text$ as string)
   LOCAL lvc AS LV_COLUMN
   lvc.mask = LVCF_TEXT
   lvc.pszText = Text$
   SendMessage(hWnd, LVM_SETCOLUMN, Column, &lvc)
   SendMessage(LView1, LVM_SETCOLUMNWIDTH, Column, LVSCW_AUTOSIZE_USEHEADER)
END SUB

SUB LV_Reset(ByVal LView AS CONTROL, ByVal Columns as int, ByVal Rows as int)
   LOCAL lvItem AS LV_ITEM
   LOCAL i as int
   ListView_DeleteAllItems(LView)
   for i = 1 to Rows
     lvItem.mask = LVIF_TEXT
     lvItem.pszText = " "
     lvItem.iSubItem = 0
     ListView_InsertItem(LView1, &lvItem)
   next   
END SUB

SUB LV_Justify(ByVal LV AS sys, ByVal Column as int , ByVal JustifyType as int)
   LOCAL hHeader AS LONG
   LOCAL hdi AS HD_ITEM
   '*******************************************
   % HDF_LEFT = 0    'JustifyType
   % HDF_RIGHT = 1    'JustifyType
   % HDF_CENTER = 2    'JustifyType
   '*******************************************
   % LVM_FIRST = 4096
   % LVM_GETHEADER = LVM_FIRST + 31
   '*******************************************
   hHeader = SendMessage(LV, LVM_GETHEADER, 0, 0)
   hdi.mask = HDI_FORMAT
   hdi.pszText = " "
   hdi.fmt = HDF_STRING OR JustifyType
   SendMessage(hHeader, HDM_SETITEM, Column, &hdi)
END SUB

FUNCTION INTEREST_PAYMENT(ByVal i AS DOUBLE, ByVal Balance AS DOUBLE) AS DOUBLE
   FUNCTION =(i/12/100) * Balance
END FUNCTION
   
FUNCTION Payment(ByVal i AS DOUBLE, ByVal np AS DOUBLE, ByVal pv AS DOUBLE, ByVal fv AS DOUBLE) AS DOUBLE
   DIM q1 AS DOUBLE
   DIM ir AS DOUBLE
   ir = i / 12 / 100
   q1 = POW(1 + ir, np)
   FUNCTION =((ir *(fv + q1 * pv))/(-1 + q1))
END FUNCTION

function Set_Text(sys hWnd, string Text) as int
  return SetWindowText(hWnd,Text)
end function

function Get_Text(sys hWnd) as string
  int tmpint
  tmpint = 1 + GetWindowTextLength(hWnd)
  static string strtmp = nuls * 1024
  GetWindowText(hWnd, strtmp, tmpint) 
  return strtmp
end function


SUB FillListView() 
   '==========================
   DIM Amount AS DOUBLE
   DIM Interest AS DOUBLE
   DIM Years AS DOUBLE
   DIM Pmt AS DOUBLE
   DIM Int_Payment AS DOUBLE
   DIM Prin_Payment AS DOUBLE
   DIM lvItem AS LV_ITEM
   DIM SumPrinc AS DOUBLE
   DIM SumInt AS DOUBLE
   DIM z AS int
   char buff[32]
   '==========================
 
   Amount =   VAL(GET_TEXT(Input1))
   Interest = VAL(GET_TEXT(Input2))
   Years =    VAL(GET_TEXT(Input3))
   Pmt = Payment(Interest, Years * 12, Amount, 0)
 
   LV_Reset(LView1, 4, Years * 12)
 
   Set_ColumnText(LView1, 0, "Payment")
   Set_ColumnText(LView1, 1, "Interest")
   Set_ColumnText(LView1, 2, "Principal")
   Set_ColumnText(LView1, 3, "Balance")
 
   FOR z = 1 TO Years * 12
     Int_Payment = INTEREST_PAYMENT(Interest, Amount)
     Prin_Payment = Pmt - INTEREST_PAYMENT(Interest, Amount)
     SumPrinc = SumPrinc + Prin_Payment
     SumInt = SumInt + Int_Payment
     string z1=str(z)
     ListView_SetItemText(LView1, z - 1, 0, z1)
     sprintf(buff, "%0.2lf", Int_Payment)
     ListView_SetItemText(LView1, z - 1, 1, buff)     
     sprintf(buff, "%.2f", Prin_Payment)
     ListView_SetItemText(LView1, z - 1, 2, buff)
     Amount = Amount - Prin_Payment
     sprintf(buff, "%.2f", Amount)
     ListView_SetItemText(LView1, z - 1, 3, buff)
   NEXT
 
   ListView_SetItemText(LView1, z, 3, " ")  ' add a line at the bottom
 
   FOR z = 0 TO 3
     SendMessage(LView1, LVM_SETCOLUMNWIDTH, z, LVSCW_AUTOSIZE_USEHEADER)
   NEXT
 
   LV_Justify(LView1, 0, HDF_LEFT)
   LV_Justify(LView1, 1, HDF_RIGHT)
   LV_Justify(LView1, 2, HDF_RIGHT)
   LV_Justify(LView1, 3, HDF_RIGHT)
 
   SET_TEXT(Label3, str(SumPrinc,2))
   SET_TEXT(Label4, str(SumInt,2))
END SUB

******************************************************************************

sys hInstance=inst
MainWindow 400,460, DS_MODALFRAME OR WS_POPUP OR WS_CAPTION OR WS_SYSMENU

function WndProc(sys hwnd, uMsg, wParam, lParam) as sys callback
 
    select uMsg
    case WM_CREATE
        SetWindowText(hwnd, "Amortization Schedule in Oxygenbasic")

        Input1=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","8000",WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 272,  6, 92, 28, hwnd,0,hinstance,0)
        Input2=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","9.25",WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 272, 36, 92, 28, hwnd,0,hinstance,0)
        Input3=CreateWindowEx(WS_EX_CLIENTEDGE,"edit","3",WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 272, 66, 92, 28, hwnd,0,hinstance,0)
        Label1=CreateWindowEx(0,"static","Principal",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 104, 80, 28, hwnd,0,hinstance,0)
        Label2=CreateWindowEx(0,"static","Interest",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE,  10, 130, 80, 28, hwnd,0,hinstance,0)
        Label3=CreateWindowEx(0,"static","",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 100, 104, 80, 28, hwnd,0,hinstance,0)
        Label4=CreateWindowEx(0,"static","",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 100,130, 80, 28, hwnd,0,hinstance,0)
        Button1=CreateWindowEx(WS_EX_STATICEDGE,"button","Calc",WS_CHILD | WS_VISIBLE | BS_MULTILINE | BS_PUSHBUTTON | WS_TABSTOP, 280,104, 80, 28, hwnd, 101, hinstance, 0)

        LV_COLUMN lvCol =
            { LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM, LVCFMT_LEFT, 65, "", 0, 0 }
        LView1=CreateWindowEx(WS_EX_CLIENTEDGE,"SysListView32",0,WS_CHILD | WS_TABSTOP |
                                         WS_VISIBLE | LVS_REPORT |
                                         LVS_SHAREIMAGELISTS | LVS_EDITLABELS | WS_BORDER, 10, 160, 300, 246, hwnd, 0, hinstance, 0)
        int Style=LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT
        SendMessage(LView1, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, Style)
        lvCol.iSubItem=0
        while lvCol.iSubItem<15
           ListView_InsertColumn(LView1, 0, lvCol)
           lvCol.iSubItem+=1     
        wend

        CreateWindowEx(0,"static","Loan Amount",  WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 14, 126, 22, hwnd,0,hinstance,0)
        CreateWindowEx(0,"static","Interest Rate",WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 44, 126, 22, hwnd,0,hinstance,0)
        CreateWindowEx(0,"static","No. of Years", WS_CHILD | SS_NOTIFY | SS_LEFT | WS_VISIBLE, 10, 72, 192, 22, hwnd,0,hinstance,0)

        Set_ColumnText(LView1, 0, "Payment")
        Set_ColumnText(LView1, 1, "Interest")
        Set_ColumnText(LView1, 2, "Principal")
        Set_ColumnText(LView1, 3, "Balance")
               
        SetFocus(Input1)
        RedrawWindow(hwnd)
        ShowWindow(hwnd)

    case WM_COMMAND
        if LOWORD(wParam) = 101 then 'Calc
           FillListView()
        end if   

    case WM_CLOSE
        DestroyWindow(hwnd)
           
    case WM_DESTROY
        PostQuitMessage(0)
           
    case else
        return DefWindowProc(hwnd, uMsg, wParam, lParam)

    end select

    return 0
end function


Roland Stowasser

Is it not possible any more to edit a reply? There are two lines in the code which I forgot to delete:

it should read:

...
function WndProc(sys hwnd, uMsg, wParam, lParam) as sys callback
 
    select uMsg
...

Charles Pegge

#7
Many thanks Roland, I will include it in demos\WinGui

It should be possible to edit using 'Quick Edit' or 'More'--> Modify' if these are visible to you. I have made the change for you.

PS:
a format function is available in parseutil.inc

uses parseutil
...
print format(num,"###.00") cr