OK, here's what I came up with. Works well, so far as my limited testing goes. I was originally going to loop through the buffer with numeric digits backwards from the end, but that turned out to be a gigantic pain in the ass.
What I came up with is dividing the # of digits by 3, which gives me the number of "triads" and whether there's a short "triad" (1 or 2 digits) at the front. The quotient sets the number of times through the "triad" loop, while the remainder sets the # of digits to move before the first comma (or none if there are fewer than 3 digits). Have to handle a couple of special cases, like fewer than 4 digits, which means no comma. Anyhow, check it out. Enter the number in the edit field at top and hit the "Go!" button.
;============================================
; -- CommaNum --
;
;============================================
include \masm32\include\masm32rt.inc
;============================================
; Defines, macros, prototypes, etc.
;============================================
WinMain PROTO :DWORD
$mainWinWidth EQU 390
$mainWinHeight EQU 200
;===== Window styles: =====
$mainWinStyles EQU WS_OVERLAPPED OR WS_CLIPCHILDREN OR WS_VISIBLE or WS_SYSMENU
$editStyles EQU WS_CHILD or WS_BORDER or WS_VISIBLE or ES_NUMBER
$staticStyles EQU WS_CHILD or WS_VISIBLE
$buttonStyles EQU WS_CHILD or WS_VISIBLE or BS_PUSHBUTTON
; Control locations, dimensions:
$numInFldX EQU 40
$numInFldY EQU 40
$numInFldWidth EQU 300
$numInFldHeight EQU 24
$numOutSTX EQU 40
$numOutSTY EQU 80
$numOutSTWidth EQU 300
$numOutSTHeight EQU 24
$btnX EQU 150
$btnY EQU 130
$btnWidth EQU 60
$btnHeight EQU 24
; Control IDs:
$numInFldID EQU 1200
$numOutSTID EQU 1201
$btnID EQU 1202
;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199
$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)
;============================================
; HERE BE DATA
;============================================
.data
WC WNDCLASSEX < SIZEOF WNDCLASSEX, \
CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW, \
NULL, \ ;lpfnWndProc
NULL, \ ;cbClsExtra
NULL, \ ;cbWndExtra
NULL, \ ;hInstance
NULL, \ ;hIcon
NULL, \ ;hCursor
NULL, \ ;hbrBackground
NULL, \ ;lpszMenuName
NULL, \ ;lpszClassName
NULL > ;hIconSm
StatusParts DD 50, 150, 250, -1
NullString DB 0
MainClassName DB "CommaNum", 0
EditClassName DB "edit", 0
StaticClassName DB "static", 0
ButtonClassName DB "button", 0
MainTitleText DB "CommaNum - Number Formatter", 0
BtnText DB "Go!", 0
BadNumMsg DB "Bad, bad number given! Try again.", 0
;============================================
; UNINITIALIZED DATA
;============================================
.data?
MainWinHandle HWND ?
NumInFldHandle HWND ?
NumOutSTHandle HWND ?
FormattedNumBuffer DB 64 DUP(?)
;============================================
; CODE LIVES HERE
;============================================
.code
start: INVOKE GetModuleHandle, NULL
MOV WC.hInstance, EAX
INVOKE WinMain, EAX
INVOKE ExitProcess, EAX
;====================================================================
; Mainline proc
;====================================================================
WinMain PROC hInst:DWORD
LOCAL msg:MSG, brush:HBRUSH, wX:DWORD, wY:DWORD, gpRect:RECT
; Create brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV brush, EAX
; Register class for parent window:
MOV WC.lpfnWndProc, OFFSET MainWindowProc
MOV EAX, brush
MOV WC.hbrBackground, EAX
MOV WC.lpszClassName, OFFSET MainClassName
MOV WC.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV WC.hCursor, EAX
INVOKE RegisterClassEx, OFFSET WC
INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWinWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWinHeight
CALL CenterDim
MOV wY, EAX
; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, OFFSET MainClassName,
OFFSET MainTitleText, $mainWinStyles, wX, wY, $mainWinWidth,
$mainWinHeight, NULL, NULL, hInst, NULL
MOV MainWinHandle, EAX
; Create edit control to input #s:
INVOKE CreateWindowEx, WS_EX_LEFT, OFFSET EditClassName, NULL, $editStyles, $numInFldX, $numInFldY,
$numInFldWidth, $numInFldHeight, MainWinHandle, $numInFldID, hInst, NULL
MOV NumInFldHandle, EAX
; Create static text to output formatted #:
INVOKE CreateWindowEx, WS_EX_LEFT, OFFSET StaticClassName, NULL, $staticStyles,
$numOutSTX, $numOutSTY, $numOutSTWidth, $numOutSTHeight, MainWinHandle, $numOutSTID, hInst, NULL
MOV NumOutSTHandle, EAX
; Create "Go" button:
INVOKE CreateWindowEx, WS_EX_LEFT, OFFSET ButtonClassName, OFFSET BtnText, $buttonStyles,
$btnX, $btnY, $btnWidth, $btnHeight, MainWinHandle, $btnID, hInst, NULL
;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit99
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop
exit99: MOV EAX, msg.wParam
RET
WinMain ENDP
;====================================================================
; Main Window Proc
;====================================================================
MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL numBuffer[64]:BYTE
MOV EAX, uMsg
CMP EAX, WM_COMMAND
JE do_command
CMP EAX, WM_CLOSE
JE do_close
dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET
do_command:
MOV AX, WORD PTR wParam
CMP AX, $btnID ;User clicked "Go!"?
JNE dodefault
JMP SHORT do_number
do_number:
INVOKE GetWindowText, NumInFldHandle, ADDR numBuffer, SIZEOF numBuffer
LEA EAX, numBuffer
CALL CommaNum
INVOKE SetWindowText, NumOutSTHandle, OFFSET FormattedNumBuffer
XOR EAX, EAX
RET
do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET
MainWindowProc ENDP
;====================================================================
; CommaNum()
;
; Number comma-formatting routine.
; On entry,
; EAX--> buffer with ASCII number digits (zero-terminated)
;
; On exit,
; FormattedNumBuffer contains the comma-formatted number string.
;====================================================================
CommaNum PROC
LOCAL digitMoveCount:DWORD, quotient:DWORD, remainder:DWORD
PUSH ESI
PUSH EDI
LEA EDI, FormattedNumBuffer ;Where to store our finished product.
MOV digitMoveCount, 3 ;Default # of digits to move/"triad".
MOV ESI, EAX ;Point to input buffer.
CALL strlen ;Get # digits.
MOV ECX, EAX
JECXZ badnum ;Zero-length number given.
XOR EDX, EDX
MOV ECX, 3
DIV ECX ;How many "triads"?
MOV quotient, EAX
MOV remainder, EDX
; If quotient = 0, use remainder as digit count for last "triad":
CMP quotient, 0
JNE @F
MOV EAX, remainder
MOV digitMoveCount, EAX
MOV quotient, 1 ;1 time through move loop.
JMP SHORT triads
; Error! Error! Error!
badnum: INVOKE MessageBox, NULL, OFFSET BadNumMsg, NULL, MB_OK
JMP SHORT exit99
; First see if there's a short "triad" at beginning:
@@: MOV ECX, remainder
JECXZ triads ;No remainder, so no short "triad".
REP MOVSB ;Yes, so move that many digits.
MOV AL, ','
STOSB ;Put in comma.
; Now we're just moving "triads" and putting in commas after them:
triads: MOV ECX, quotient
nloop: PUSH ECX
MOV ECX, digitMoveCount
REP MOVSB
POP ECX
; Do we put a comma in, or are we on the last "triad"?
CMP ECX, 1
JE nocom
MOV AL, ','
STOSB
nocom: LOOP nloop
; Cap off formatted # buffer:
done: MOV BYTE PTR [EDI], 0
exit99: POP EDI
POP ESI
RET
CommaNum ENDP
;====================================================================
; strlen()
;
; On entry,
; EAX--> string to be measured
; On exit,
; EAX = length of string
;====================================================================
strlen PROC
XOR EDX, EDX ;Len. counter
sl10: CMP BYTE PTR [EAX + EDX], NULL
JE sl88
INC EDX
JMP sl10
sl88: MOV EAX, EDX
RET
strlen ENDP
;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================
CenterDim PROC
SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX
CenterDim ENDP
END start
Without the error checking, the actual formatting code, CommaNum(), is not too hairy. If someone comes up with a better solution, I'm interested.