I have a feeling that there is an easy way to do it. I can do it the hard way, but I don't want to.
What's your hard way? I'm curious. Maybe it's not all that hard.
I'll post my method here soon (I've done this before), as soon as I can find it ...
If you just have a list of characters, its a parsing issue. Not exactly sure of what you are after.
PS : How is your new notebook going ?
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.
For GUI
GetNumberFormat function (https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getnumberformata)
For comparison - 64 bytes, pure Masm32 SDK, handles fractions and negative numbers:
1 --> 1
12 --> 12
123 --> 123
1234 --> 1,234
-1234567890 --> -1,234,567,890
+1234567890 --> +1,234,567,890
1234567890 --> 1,234,567,890
234567890 --> 234,567,890
34567890 --> 34,567,890
1234567890.123 --> 1,234,567,890.123
234567890.1234 --> 234,567,890.1234
34567890.12345 --> 34,567,890.12345
Pretty kewl. Without the error-checking (which I notice you don't have either), mine's not too bad at 94 bytes. But you win. Mine doesn't handle sign characters.
What kind of errors would you be checking for? You are passing a number...
Since the input was from an edit control I was checking for a blank entry (the edit control was set up to only accept numeric input).
Can't be too careful, dontcha know.
If you start with a string, you should do error checking in the function that converts the string to a number, not in the function that adds the commas. The latter gets a number, in eax or as a REAL variable. There are (almost) no "wrong" numbers...
You might argue that you can pass a string directly, without converting it to a number and back to a string. However, once the commas are added, most string to number converters can't handle it any more, so the string has become pretty useless, except for printing.
Yes, agree completely.
Quote from: hutch-- on February 01, 2022, 04:35:05 PM
If you just have a list of characters, its a parsing issue. Not exactly sure of what you are after.
PS : How is your new notebook going ?
New HP 17 notebook is great. All I have to do is find how to get the function keys to work.
Got a great procedure from this group to comma-format a number greater than 3 digits.
I saw the procedure written by NoCforMe and tried it out. Works great! So I wrote this little program to try and test it out.
It folows:
;****************************************************************
;* *
;* FLOWER-BOX MICROSOFT MACRO ASSEMBLER *
;* *
;* *
;* *
;* Filename: putcommas01.asm *
;* Author: Arty *
;* Date: 2022-02-05 23:51 *
;* Modified: 2022-02-07 02:34 *
;* Modified: 2022-02-09 08:56 *
;* Modified: 2022-02-12 17:43 *
;* *
;* *
;* *
;* *
;* Purpose: To put commas into a string of digits > 3 *
;* *
;* Files: None *
;* *
;* *
;****************************************************************
;
; Copyright (c) 2020 by Arty
; Certain portions of this program are
; copyrighted (c) 1991 by Ray Duncan
TITLE "PUTCOMMAS01.ASM"
PAGE 32,100
true equ 1
false equ 0
chkptrs equ true ; if true, HREALLOC and
; HFREE check all pointers
CR equ 00dh ; ASCII carriage return
LF equ 00ah ; ASCII line feed
blank equ 020h ; ASCII space character
tab_char equ 009h ; ASCII TAB character
; ####################################################
.NOLIST
include \masm32\include\masm32rt.inc
.LIST
; ####################################################
.data
input_buffer db 128 DUP (0)
output_buffer db 128 DUP (0)
temp_buffer db 128 DUP (0)
BadNumMsg db "Invalid number", CR, LF, 0
ALIGN 4
string_size dd 0 ; size of a measured string
valid_sum dd 0 ; sum of valid digits
; ####################################################
.code
start:
main PROC ; entry point from Windows
call initialization ; do inits
call main_processing ; do work
call wrapup ; do termination
INVOKE ExitProcess,0 ; terminate program
main ENDP
;-----------------------------------------------
initialization PROC ; process inits
; NOTHIN' DOIN' HERE
ret ; my work here is done
initialization ENDP
;-----------------------------------------------
main_processing proc ; do main work
cld ; clear direction flag
printc "\nEnter the number: "
INVOKE StdIn, addr input_buffer, 128 ; get input number
INVOKE StripLF, addr input_buffer ; zero out carriage-return (not LF)
INVOKE szLen, addr input_buffer ; get number of characters entered
MOV string_size, EAX ; save it
call CommaNum ; go place the commas
JC exitMain ; if error, get out w/carry set
print chr$(CR,LF) ; newline
INVOKE StdOut, addr output_buffer ; display comma-formatted number
print chr$(CR,LF) ; another newline
exitMain:
ret ; my work here is done
main_processing endp
;-----------------------------------------------
;====================================================================
; CommaNum
;
; Number comma-formatting routine. (1234 input = 1,234 output)
;
; On entry,
; Digits to be formatted are in the 'input_buffer', zero-terminated
;
; On exit,
; 'output_buffer' contains the comma-formatted number (also zero-terminated)
; Carry Flag is false if all digits are valid, otherwise it's true
;
; A "triad" is a group of three characters
;
; Great thanks go to member 'NoCforMe' for this comma inserting procedure.
;
;====================================================================
CommaNum PROC
LOCAL digitMoveCount:DWORD,
quotient:DWORD,
remainder:DWORD
PUSH ESI ; save registers
PUSH EDI
INVOKE szTrim, addr input_buffer ; trim off leading & trailing spaces and tabs
CALL strip_leading_zeros ; get rid of those pesky zeros, if any
INVOKE szLen, addr input_buffer ; get number of characters in buffer
CMP EAX, 0 ; zero length input?
JE badnumber ; yes, empty buffer is no good
MOV string_size, EAX ; save it
LEA EDI, output_buffer ; get pointer to the output_buffer
LEA ESI, input_buffer ; get pointer to the input_buffer
MOV digitMoveCount, 3 ; Default # of digits to skip - "triad"
CALL testDigits ; chk characters entered for validity
MOV EAX, valid_sum ; carry will be set if one or more bad digits
CMP EAX, string_size ; 'valid_sum" should equal 'string_size'
JC badnumber ; if less, bad digit(s) - get outta here
XOR EDX, EDX ; clear EDX for dividend
MOV ECX, digitMoveCount ; get divisor
DIV ECX ; calc number of "triads"?
MOV quotient , EAX ; save quotient
MOV remainder, EDX ; & remainder
CMP quotient, 0 ; # of "triads" = zero?
JNE @F ; no, so go format them
MOV EAX, remainder ; yes, we have 1 or 2 digits
MOV digitMoveCount, EAX ; get count of digits to move
MOV quotient, 1 ; 1 time through move loop
JMP triads ; go print odd front digits
; Error!
badnumber:
print chr$(CR,LF) ; newline
INVOKE StdOut, ADDR BadNumMsg ; display bad number
stc ; set carry (error) flag
JMP exit99 ; and get out of here
; First see if there's a short "triad" at beginning:
@@:
MOV ECX, remainder ; short "triad" here?
JECXZ triads ; not if there's no remainder
REP MOVSB ; yes, so move 1 or 2 digits
MOV AL, ',' ; and tack on a comma
STOSB
; Now we're just moving "triads" and putting in commas after them:
triads:
MOV ECX, quotient
nloop:
PUSH ECX ; save the quotient
MOV ECX, digitMoveCount ; get digit count
REP MOVSB ; and print them
POP ECX ; restore the quotient
; Do we put a comma in, or are we on the last "triad"?
CMP ECX, 1
JE nocomma ; yes, last "triad"
MOV AL, "," ; nope, put a comma in
STOSB
nocomma:
LOOP nloop
; Cap off formatted number buffer:
done: ; this label isn't a target
MOV BYTE PTR [EDI], 0 ; terminate the valid digit string
clc ; clear carry (error) flag
exit99:
POP EDI ; restore registers
POP ESI
RET ; my work here is done
CommaNum ENDP
;-----------------------------------------------
;====================================================================
;testDigits
;
; Tests characters in input_buffer for valid digits
;
; On entry,
; Digits must be in the string variable, 'input_buffer'
;
; On exit,
; 'valid_sum' contains the number of valid digits
;====================================================================
testDigits proc uses eax ecx edx esi
LEA ESI, input_buffer ; point to input characters
MOV valid_sum, 0 ; start with zero valid digits
@@:
LODSB ; get a character
CMP AL, 0 ; at end of digit string?
JE done ; yes, get outta here
INVOKE isnumber, AL ; no, see if character is numeric
ADD valid_sum, EAX ; add return value (0 or 1) to valid count
JMP @B ; check next digit
done:
ret ; my work here is done
testDigits endp
;-----------------------------------------------
;====================================================================
;strip_leading_zeros
;
; Strips leading zeros from number in 'input_buffer'
;
; On entry,
; Digits must be in the string variable, 'input_buffer'
;
; On exit,
; 'input_buffer' contains the number w/out leading zeros
;====================================================================
strip_leading_zeros proc uses eax ecx edx esi edi
CMP string_size, 1 ; is it just one character?
JE exit99 ; yes, get out - preserve single character
LEA esi, input_buffer ; get the input address
LEA edi, input_buffer ; get the output address - same as input
L1:
LODSB ; get a character
CMP al, 0 ; string terminating zero?
JE atend ; yes, get out
CMP al, "0" ; no, is it an ASCII zero?
JE L1 ; yes, discard it and process next character
L2:
STOSB ; otherwise, store it
LODSB ; and get next character
CMP al, 0 ; end of string zero byte?
JNE L2 ; no, continue processing string
atend:
STOSB ; yes, terminate output w/zero byte
print chr$(CR,LF) ; newline
INVOKE StdOut, addr input_buffer ; display number without the zeros
print chr$(CR,LF) ; another newline
exit99:
ret ; my work here is done
strip_leading_zeros endp
;-----------------------------------------------
wrapup proc ; terminate program
; NOTHIN' DOIN' HERE
ret ; my work here is done
wrapup endp
;-----------------------------------------------
END start
That's it. I tested it at least 2 dozen times and it worked every time. Thank you NoCforMe!
Hey, you're welcome. I see you reformatted it to suit your own style and purposes. Good work. I do that all the time.
And thanks for the testing, which I never really did much of.