News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

How to put commas in numbers larger than 999?

Started by deeR44, February 01, 2022, 03:29:58 PM

Previous topic - Next topic

deeR44

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.

NoCforMe

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 ...
Assembly language programming should be fun. That's why I do it.

hutch--

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 ?

NoCforMe

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.
Assembly language programming should be fun. That's why I do it.


jj2007

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



NoCforMe

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.
Assembly language programming should be fun. That's why I do it.

jj2007

What kind of errors would you be checking for? You are passing a number...

NoCforMe

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.
Assembly language programming should be fun. That's why I do it.

jj2007

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.

NoCforMe

Assembly language programming should be fun. That's why I do it.

deeR44

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.

deeR44

#12
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!

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.
Assembly language programming should be fun. That's why I do it.