The MASM Forum

General => The Campus => Topic started by: deeR44 on February 01, 2022, 03:29:58 PM

Title: How to put commas in numbers larger than 999?
Post 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.
Title: Re: How to put commas in numbers larger than 999?
Post by: NoCforMe on February 01, 2022, 04:33:42 PM
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 ...
Title: Re: How to put commas in numbers larger than 999?
Post by: 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 ?
Title: Re: How to put commas in numbers larger than 999?
Post by: NoCforMe on February 01, 2022, 06:20:45 PM
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.
Title: Re: How to put commas in numbers larger than 999?
Post by: TimoVJL on February 01, 2022, 08:21:08 PM
For GUI
GetNumberFormat function (https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getnumberformata)
Title: Re: How to put commas in numbers larger than 999?
Post by: jj2007 on February 01, 2022, 10:01:32 PM
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


Title: Re: How to put commas in numbers larger than 999?
Post by: NoCforMe on February 02, 2022, 11:17:32 AM
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.
Title: Re: How to put commas in numbers larger than 999?
Post by: jj2007 on February 02, 2022, 12:14:03 PM
What kind of errors would you be checking for? You are passing a number...
Title: Re: How to put commas in numbers larger than 999?
Post by: NoCforMe on February 02, 2022, 12:19:37 PM
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.
Title: Re: How to put commas in numbers larger than 999?
Post by: jj2007 on February 02, 2022, 12:39:19 PM
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.
Title: Re: How to put commas in numbers larger than 999?
Post by: NoCforMe on February 02, 2022, 12:49:41 PM
Yes, agree completely.
Title: Re: How to put commas in numbers larger than 999?
Post by: deeR44 on February 14, 2022, 03:38:26 PM
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.
Title: Re: How to put commas in numbers larger than 999?
Post by: deeR44 on March 18, 2022, 04:46:53 PM
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!
Title: Re: How to put commas in numbers larger than 999?
Post by: NoCforMe on July 21, 2022, 08:33:52 AM
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.