The MASM Forum
General => The Campus => Topic started by: deeR44 on February 01, 2022, 03:29:58 PM
-
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.
-
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.