Number Format
 Volume Number: 3 Issue Number: 11 Column Tag: Assembly Language Lab

# Formatted Output for Numbers

By Mike™ Scanlin, San Diego, CA

On most systems, programmers of high level languages have an advantage over assembly language programmers in that a lot of the nitty gritty detail work has been done for them by what ever compiler or interpretter they’re using. An example of this is in the outputting of numbers. High level language programmers usually take it for granted that they can output a number in just about any format they want to. Pascal’s writeln and C’s printf make it a trivial task to change the field width of both integers and reals. While it’s true that assembly programmers can make use of _NumToString or the SANE formatter, we still need to play with the resulting strings to get nice formatted numbers. Until now.

## CONVERTING INTEGERS

The basic idea when converting an integer into its string equivalent is to first break it down into its digits and then convert each digit into its ASCII equivalent. The only tricky part is that a number is represented as binary internally and we need its decimal equivalent.

There are at least 2 different ways to approach the problem of extracting base 10 digits from a binary number. The _NumToString routine that Apple provides in the system file (see listing 1) uses binary coded decimal (BCD) arithmetic to calculate the digits and then calls a separate subroutine to build the string two digits at a time (the least significant bytes of registers D1-D5 are used to store 2 BCD digits each). The main problem with this algorithm is that it runs in more or less constant time (the number 1 takes as long to convert as 2147483648 = 2^31) and isn’t very efficient except for very large numbers (about 7 or more digits).

Another way to get digits one by one is to subtract by successively smaller powers of 10, starting with 10^9 (since it is the largest power of 10 that can be represented by a 32 bit integer). Count how many times you can subtract each power of 10 from the number and then convert that number (which will be in the range 0..9) to its ASCII equivalent. If you use multi-word compares and subtracts, this method can be used to convert any size integers (like 64 or 128 bits) into strings.

My own NumToString routine (Shown in the program listing) uses the subtract method. How does the subtract method compare to the real _NumToString? As for size, _NumToString is 118 bytes (after all of the trap related instructions are removed) and my routine is 120 bytes. Pretty close. As for speed, time trials on 10,000 random 32 bit signed integers showed my routine to be faster by about 2% -- no big deal. But for time trials on 10,000 16 bit signed integers my routine was faster by 32% and for 10,000 8 bit signed integers my routine was faster by 45% (to be fair, _NumToString was timed after it had been isolated so there was no overhead for trap calling). The reason the smaller numbers showed so much improvement is because my routine doesn’t spend much time on little digits (0,1,2...) and virtually no time on leading zeros. If you have a time critical application that makes a lot of calls to _NumToString, it may be worthwhile to use my routine instead. Of course, you could just patch my routine over the existing _NumToString in the system file to speed up all applications that use _NumToString -- no, wait, I didn’t just say that (and I’m not responsible for the consequences. Is there any reason that wouldn’t work?).

Fig. 1 Demo Program formats numerical output

## CONVERTING FIXED POINT REALS

When you use DIVS or DIVU, the 32 bit result is made up of 16 bits of quotient and 16 bits of remainder. The FixPtToString routine uses these two pieces to convert a fixed point number to a string. Since the quotient is just an integer, we can use NumToString to convert it. Then add a decimal point and convert the remainder. But in order to convert the remainder into a fractional number, we need to know the original divisor. We also need to specify how many digits we want after the decimal point.

For each digit you want after the decimal point, the routine multiplies the remainder by 10 and then divides the it by the divisor, getting a new remainder in the process. It adds the digit to the string and then repeats the process for the next digit. However, there is a limited number of times that this can be done accurately (since we only have 16 bits of remainder to begin with). The number of digits that can be accurately calculated depends on the magnitude of the divisor, but 5 or 6 digits should be safe.

Note that the number you pass to FixPtToString doesn’t have to be the result of a divide instruction. You can output any arbitrary fixed point number you want by using the same basic idea. If you wanted an integer part bigger than 16 bits, you could output the number in a two part process. FixPtToString could be modified to be FractionToString by eliminating instructions 3 (EXT.L D0) thru 17 (BEQ.S @6) inclusive. Then it will tack on whatever fraction you pass it to what ever string you pass it. For instance, to output the number 1864723135.24226 (.24226 = 47/194):

``` MOVE.L stringPtr(A5),A0
MOVE.L #1864723135,D0  ;quotient
JSR    NumToString
MOVE   #47,D0   ;remainder
SWAP   D0;no need to set quot
MOVE   #194,D1  ;divisor
MOVE   #5,D2    ;5 digits accuracy
JSR    FractionToString
MOVE.L A0,-(SP)
_DrawString
```

## FORMATTING

Now that we can get integers and reals into strings we need to be able to set field widths. Also, an option to add commas would be nice. The FormNumString routine does both of these. You pass it a pointer to a format string of the form: [‘,’][q[‘.’[r]]] where [ ] denotes something optional and ‘.’ and ‘,’ denote a constant character. Q and r are string variables in the range [‘0’..’99'] and represent how many spaces should be allocated for the quotient (including sign and commas) and remainder (not including decimal point). Notice that everything is optional, so the empty string is a legal one, but one that won’t do much (i.e. any) formatting. Also, the word “string” here does not mean a pascal string; i.e. there is no length byte. The nested brackets mean that you can’t have a ‘.’ if a q was not provided and you can’t have an r if a ‘.’ wasn’t provided. If q is too small to contain the number, space is used as needed. If it is too big, the number will be padded with spaces. If r is bigger than any existing remainder the number might have, zeros are appended after the decimal point (which doesn’t do much for the accuracy of the extra digits). If r is smaller than any existing remainder, then digits are just dropped. No attempt at rounding is made.

Another use for this routine is to reformat the output string from the SANE formatter. The SANE formatter can format any SANE data type into a fixed sytle number (see the Apple Numerics Manual for everything you ever wanted to know about SANE). So, you could use SANE to do all of your calculations in floating point and have your result formatted into a fixed style string and then use FormNumString to add commas, pad with spaces and delete decimal places (or add zeros) to make all your numbers uniform.

Fig. 2 Program supports easy updating with a picture

## PUTTING IT ALL TOGETHER

The 3 routines NumToString, FixPtToString and FormNumString have been pieced together to form DrawNumsInAString which can be used to output complete sentences containing any number of formatted numbers. For instance, you could pass it the string “result = \f,8.4.” along with the fixed point number 123456.789 and it will output “result = 12,3456.7890.” Each number you want formatted in the input string will begin with a ‘\’ character and be followed by an ‘i’ (for longints) or an ‘f’ (for fixed points). After that comes the FormNumString style format codes for the number. The only peculiar part of using the routine is that the numbers you want formatted have to be pushed on the stack in reverse of their occuring order in the string. For instance, if your input string looks like “x = \i4 y = \i4 z = \i4” then you would do this:

``` MOVE.L z,-(SP)  ;3rd parameter
MOVE.L y,-(SP)  ;2nd
MOVE.L x,-(SP)  ;1st
PEA    inputString
JSR    DrawNumsInAString
```

The reason for passing the parameters that way is (1) because we don’t know how many will be present in advance, and (2) so that they can be taken off the stack in the order needed.

## DEMO PROGRAM

The FormatDemo program shows how all of this comes together. It is a bare bones application that demonstrates how the routines presented here might be used. A click in the window will generate another set of random numbers to format. Desk accessories are supported and you can see a simple technique for providing window updating without an update event. Whenever a content click is detected, a set of addition and division problems are displayed in formatted output using the formatting routines discussed. After displaying the numbers, a quickdraw picture is taken of the window output using copybits on the portRect of the window. The appropriate field in the window record is set with the pointer to this picture so that when the window needs updating, it is updated automatically from the picture. Figures 1 and 2 show the demo program in operation with a desk accessory showing this easy update method. A good reference book for this and all assembly language techniques is Dan Weston’s classic The Complete Book of Macintosh Assembly Language Programming, vol. 1 and 2, from Scott, Foresman and Company.

```{1}
Listing 1. The Apple Way
; NOTE: This code is Apple Computer’s. Only

;==========
NumToString
;==========
; convert a 32 bit longint into a pascal string
;  input: D0 longint
;  A0 points to a space of at least 12 bytes
; output: A0 points to pascal string

MOVEM.LD0-D6/A1,-(SP)
MOVEQ  #0,D1    ;init digits
MOVEQ  #0,D2
MOVEQ  #0,D3
MOVEQ  #0,D4
MOVEQ  #0,D5
MOVEQ  #31,D6   ;loop counter
LEA    1(A0),A1 ;skip length byte
TST.L  D0;is num zero?
BGT.S  @2
BMI.S  @1
MOVE.B #’0',(A1)+ ;special case for zero
BRA.S  @3
@1 MOVE.B #’-’,(A1)+ ;give string a minus sign
NEG.L  D0;make number positive
@2 ADD.LD0,D0    ;shift a bit into extend flag
ABCD   D5,D5    ;tens’ & ones’ digits
ABCD   D4,D4    ;thous’ & hunds’ digits
ABCD   D3,D3    ;hund thous’ & ten thous’ digits
ABCD   D2,D2    ;ten mils’ & mils’ digits
ABCD   D1,D1    ;bils’ & hund mils’ digits
DBRA   D6,@2    ;do next bit
; NOTE: at this point D6 = -1. It is used as a flag to
BSR.S  Do2Digits
MOVE.B D2,D1
BSR.S  Do2Digits
MOVE.B D3,D1
BSR.S  Do2Digits
MOVE.B D4,D1
BSR.S  Do2Digits
MOVE.B D5,D1
BSR.S  Do2Digits
;calculate length of string that was created
@3 MOVE A1,D0    ;end of string + 1
SUB    A0,D0    ;beginning of string
SUBQ.B #1,D0    ;minus 1 for length byte
MOVE.B D0,(A0)
MOVEM.L(SP)+,A1/D0-D6
RTS

Do2Digits
;convert BCD byte in D1 into 2 ASCII digits.
;do most significant digit
ROR    #4,D1
;do least significant digit
ROL    #4,D1
TST    D6;have we had a non-zero digit yet?
BPL.S  @6
TST.B  D1;is this a leading zero?
BEQ.S  @7
MOVEQ  #0,D6    ;print all zeros from now on
@6 ORI.B#\$30,D1  ;covert BCD digit to ASCII
MOVE.B D1,(A1)+ ;add it to the string
SUB.B  D1,D1
@7 RTS

; DrawNumsInAString.asm
;----------------------
; by Mike™ Scanlin

Include Traps.D

Xref  DrawNumsInAString,NumToString,FixPtToString,FormNumString
Xref  GetANumber

;================
DrawNumsInAString
;================
```

Draw a string that may contain implicit formatted numbers. input: stack contains numbers that will be needed and a pointer a string. The numbers should be pushed on the stack in order from last to first (so they can be poped off in order from first to last). The last thing to be pushed on the stack is the string pointer. Each formatted number within the input string begins with a ‘\’ and then either an ‘i’ (for longints) or a ‘f’ (for a fixed point number). For fixed points, first push the fixed point number, then the divisor to be used to calculate the remainder. The formatting after the ‘i’ or ‘f’ is the same as for the FormNumString routine. output: a string is drawn A0,D0 are trashed.

```{2}
MOVEM.LA0-A3/D1-D2,-(SP)
LEA    28(SP),A3;point to first parameter
MOVE.L A3,-(SP) ;save initial position
MOVE.L (A3)+,A2 ;string pointer
@1 MOVEQ#0,D0
MOVE.B (A2)+,D0
BEQ.S  @10 ;end of string found
CMPI.B #’\’,D0  ;is it a number?
BEQ.S  @2
MOVE   D0,-(SP)
_DrawChar
BRA.S  @1
;we got a number to format
@2 LEA  scratch,A0
MOVE.B (A2)+,D0
CMPI.B #’i’,D0  ;is it a longint?
BNE.S  @4
;handle integers
MOVE.L (A3)+,D0 ;get longint
JSR    NumToString
BRA.S  @5;go format it
@4 CMPI.B #’f’,D0;is it a fixed point?
BNE.S  @1;if not, ignore it
;handle fixed point
MOVEA.LA2,A1
;find out how many decimal places should be passed to
; FixPtToString
MOVE.B (A1)+,D0 ;skip comma, if present
CMPI.B #’,’,D0
BEQ.S  @4.1
SUBA   #1,A1
@4.1  JSR GetANumber
BMI.S  @1;no quotient present
MOVE.B (A1)+,D0
CMPI.B #’.’,D0
BNE.S  @4.2
JSR    GetANumber
MOVE   D0,D2
BPL.S  @4.3
@4.2  MOVEQ #0,D2;no remainder
@4.3  MOVE(A3)+,D1 ;get divisor
MOVE.L (A3)+,D0 ;get fixed point num
JSR    FixPtToString
;do the formatting
@5 MOVEA.LA2,A1  ;addr of format string
JSR    FormNumString
MOVEA.LA1,A2    ;point past format string
MOVE.L A0,-(SP)
_DrawString
BRA.S  @1
@10SUBA.L (SP)+,A3 ;calc len of params
MOVE   A3,D0
MOVEM.L(SP)+,D1-D2/A0-A3
JMP    (A0)

scratch DCB.B  40,0

; FixPtToString.asm
;------------------
; by Mike™ Scanlin

Xref  FixPtToString,NumToString

;============
FixPtToString
;============
; convert a 32 bit fixed point number into a pascal
; string.
;  input: D0 fixed point number
;  D1 16 bit divisor used when D0 was calculated
;  D2 # of digits after decimal point (D2=0 for no
;          dec point)
;  A0 points to a space of at least (8 + D2) bytes
; output: A0 points to pascal string

MOVEM.LD0-D3/A1,-(SP)
MOVE.L D0,D3    ;save quotient & remainder
EXT.L  D0;sign extend quotient
JSR    NumToString
;if q = 0 and the result should be < 0, we’ll have to
;(NumToString won’t know about it, since all it sees is
; a zero quotient)
TST    D3;q = 0?
BNE.S  @0
TST.L  D3;check remainder
BEQ.S  @0;q & r both zero
;if r & divisor have the same sign, then result will
; be > 0
EXT.L  D1
MOVE.L D1,D0
EOR.L  D3,D0
BPL.S  @0
MOVE.B #’-’,1(A0)
MOVE.B #’0',2(A0)
MOVE.B #2,(A0)  ;new length
@0 TST  D2;do we want a decimal point?
BEQ.S  @6
MOVEQ  #0,D0
MOVE.B (A0),D0  ;length of quotient
LEA    1(A0,D0),A1;end of string + 1
MOVE.B #’.’,(A1)+
TST.L  D1;make divisor positive
BPL.S  @1
NEG.L  D1
@1 TST.LD3;make remainder positive
BPL.S  @2
NEG.L  D3
@2 SWAP D3
ANDI.L #\$FFFF,D3;isolate remainder
SUBQ   #1,D2    ;loop control
@3 ADD.LD3,D3    ;mult r by 10
MOVE.L D3,D0
ADD.L  D0,D3    ;10x = 8x + 2x
MOVEQ  #’0',D0  ;init digit
@4 CMP.LD1,D3    ;is 10r > divisor?
BLT.S  @5
SUB.L  D1,D3    ;subtract divisor
BNE.S  @4
@5 MOVE.B D0,(A1)+ ;add to string
DBRA   D2,@3
MOVE   A1,D0    ;calc length of new string
SUB    A0,D0
SUBQ.B #1,D0    ;minus 1 for length byte
MOVE.B D0,(A0)
@6 MOVEM.L(SP)+,A1/D0-D3
RTS

; FormNumString.asm
;------------------
; by Mike™ Scanlin

Xref  FormNumString,GetANumber

;============
FormNumString
;============
; format the string representation of a number (integer
; or real) according to a format string.
; input: A0 points to string of a number (which should
; be in a space big enough for formatting result)
;  A1 points to format string
;  syntax of format string is [‘,’]d[d][‘.’[d[d]]]
;     where ‘’ denote a constant char and d denotes
;     a digit ‘0’..’9'
;  there is no length byte for these strings.
;   valid strings  invalid strings
;3 230. (230 too big [99 max])
;    4.0 or 4.   .0(need a d before ‘.’)
;,3.4   ,.4 (need a d between ‘,.’)
;,12.20 0.123  (123 too big)
; output: A0 points to formatted string of a number
;  A1 points to first byte after format string

MOVEM.LD0-D5/A2,-(SP)
CMPI.B #’,’,(A1)
BNE.S  @6
;do commas
;first find out if a decimal point in the string
MOVEQ  #0,D0
MOVE.B (A0),D0  ;length of string
MOVE   D0,D2    ;save in case it’s and int
SUBQ   #1,D0    ;loop control
; D0 counts how many digits from the end of the string
; to the decimal point (including the decimal
; point -- which is why it starts out as 1 and not 0).
; If the number is an int, then D0=0
MOVEQ  #1,D1
@1 CMPI.B #’.’,1(A0,D0) ;dec point, it’s a real
BEQ.S  @2
DBRA   D0,@1
;it’s an integer
MOVE   D2,D0
MOVEQ  #0,D1
@2 MOVE D1,D5    ;save length of fraction
MOVEQ  #3,D2    ;# of digits until next comma
SUBQ   #1,D0
@3 ADDQ #1,D1    ;total len, from end to cur pos
SUBQ   #1,D2
BNE.S  @5
MOVE   D0,D3
MOVE.B (A0,D0),D0
BNE.S  @6
MOVE   D3,D0
;move some chars, add a comma, increase string length.
MOVE   D1,D4    ;loop control
SUBQ   #1,D4
LEA    1(A0,D0),A2
@4 MOVE.B (A2,D4),1(A2,D4)
DBRA   D4,@4
MOVE.B #’,’,(A2)
MOVEQ  #3,D2    ;reset counter
@5 DBRA D0,@3
;get next byte of format string
@6 BSR.SGetANumber ;D0 = q
BMI.S  @16 ;no q provided -- leave
MOVEQ  #0,D2
MOVE.B (A0),D2  ;length of string
MOVE   D2,D1
SUB    D5,D1    ;D1=len of quotient now
SUB    D1,D0
BMI.S  @10
BEQ.S  @10
ADD.B  D0,(A0)  ;increase length by D0 spaces
;add D0 # of preceeding spaces
@7 LEA  1(A0,D0),A2
SUBQ   #1,D2
@8 MOVE.B 1(A0,D2),(A2,D2)
DBRA   D2,@8
SUBQ   #1,D0
@9 MOVE.B #’ ‘,1(A0,D0)
DBRA   D0,@9
;do remainder
@10CMPI.B #’.’,(A1)
BNE.S  @16
;make sure there is a decimal point already
LEA    1(A0),A2
MOVEQ  #0,D0
MOVE.B (A0),D0
SUBQ   #1,D0
@11CMP.B#’.’,(A2)+
BEQ.S  @12
DBRA   D0,@11
MOVE.B #’.’,(A2)+
@12BSR.SGetANumber
BMI.S  @16 ;no r provided -- leave
; D0 is how many digits they want. D5 is
@13SUBQ #1,D5
@14CMP  D5,D0
BEQ.S  @16
BPL.S  @15
SUB.B  #1,(A0)
BRA.S  @13
BRA.S  @14
@16MOVEM.L(SP)+,A2/D0-D5
RTS

;=========
GetANumber
;=========
; convert a one or two digit ASCII integer into
; its numerical form
;  input: A1 points to digit(s)
; output: D0 is the decimal equivalent (-1 if A1
; didn’t point to a digit)
;  A1 points to byte after digit(s)
;  Z and N flags reflect the value of D0
MOVEM.LD1-D2,-(SP)
MOVEQ  #0,D0
MOVE.B (A1),D0
BEQ.S  @2
MOVEQ  #-1,D0
BRA.S  @4
MOVE   D0,D1    ;save first digit
MOVE.B (A1),D0
BEQ.S  @3
MOVE   D1,D0
BRA.S  @4
ADD    D1,D1    ;multiply first digit by 10
MOVE   D1,D2
@4 MOVEM.L(SP)+,D1-D2
TST    D0
RTS

;=========
;=========
; If the ASCII byte in D0 is in ‘0’..’9' it’s
; value [0..9]
; is returned in D0. If D0 is a digit, all
; flags are set.
CMPI.B #’0',D0
BLT.S  @1
CMPI.B #’9',D0
BGT.S  @1
SUBI.B #’0',D0
MOVE   #-1,CCR  ;set all flags
RTS
@1 MOVE #0,CCR   ;clear all flags
RTS

; NumToString.asm
;----------------
; by Mike™ Scanlin
; an alternative to _NumToString

Xref  NumToString

;==========
NumToString
;==========
; convert a 32 bit integer into a pascal string
;  input: D0 longint
;  A0 points to a space of at least 12 bytes
; output: A0 points to pascal string

MOVEM.LD0-D4/A1-A2,-(SP)
LEA    1(A0),A1 ;skip length byte
TST.L  D0;is number zero?
BGT.S  @2
BMI.S  @1
MOVE.B #’0',(A1)+ ;special case for zero
BRA.S  @8
@1 MOVE.B #’-’,(A1)+ ;give string a minus sign
NEG.L  D0;make number positive
@2 LEA  PowersTable,A2
MOVEQ  #1,D3    ;set leading zeros flag
MOVEQ  #9,D4    ;loop counter
@3 MOVE.L (A2)+,D2 ;get a power of 10
MOVEQ  #’0',D1  ;init digit
@4 CMP.LD2,D0    ;is # > power of 10?
BLT.S  @5
SUB.L  D2,D0    ;subtract power of 10
BNE.S  @4
@5 TST  D3;have we had a non-zero digit yet?
BEQ.S  @6
CMP.B  #’0',D1  ;is this a leading zero?
BEQ.S  @7
MOVEQ  #0,D3    ;print all zeros from now on
@6 MOVE.B D1,(A1)+
@7 DBRA D4,@3
@8 MOVE A1,D0    ;calc length of new string
SUB    A0,D0
SUBQ.B #1,D0    ;minus 1 for length byte
MOVE.B D0,(A0)
MOVEM.L(SP)+,A1-A2/D0-D4
RTS

PowersTable:
DC.L 1000000000
DC.L 100000000
DC.L 10000000
DC.L 1000000
DC.L 100000
DC.L 10000
DC.L 1000
DC.L 100
DC.L 10
DC.L 1

; Resource File
RESOURCE ‘FRED’ 0 ‘IDENTIFICATION’
DC.B 14, ‘Format Program’

.ALIGN 2
RESOURCE ‘BNDL’ 128 ‘BUNDLE’

DC.L ‘FRED’;name
DC.W 0,1‘;data
DC.L ‘ICN#’;icon map
DC.W 0 ;mapping-1
DC.W 0,128 ;map 0 to 128

DC.L ‘FREF’;file ref
DC.W 0 ;maps-1
DC.W 0,128 ;map 0 to 128

RESOURCE ‘FREF’ 128 ‘FREF 1’

DC.B ‘APPL’,0,0,0

.ALIGN 2
RESOURCE ‘ICN#’ 128 ‘MY ICON’

DC.L \$00000000, \$00000000, \$07FFFFC0, \$18000030
DC.L \$202038E8, \$40604514, \$80204412, \$80204422
DC.L \$80204441, \$80204481, \$802139F1, \$80000001
DC.L \$80000001, \$80F81021, \$80103061, \$802010A1
DC.L \$80701121, \$800811F1, \$80881021, \$80711021
DC.L \$80000001, \$80201801, \$80602001, \$80204001
DC.L \$9E207802, \$80204402, \$40204404, \$20213808
DC.L \$18000030, \$07FFFFC0, \$00000000, \$00000000
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF
DC.L \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF, \$FFFFFFFF

.ALIGN 2
DC.W 0 ;width holder
DC.W 0 ;height holder
DC.L 0 ;resource id holder
DC.L \$1FB;flags enable all except 2
DC.B 1 ;title length
DC.B 20;title apple symbol

DC.B 0 ;no icon
DC.B 0 ;no keyboard equiv
DC.B 0 ;marking char
DC.B 0 ;style of item text

DC.B ‘--’
DC.B 0 ;no icon
DC.B 0 ;no keyboard equiv
DC.B 0 ;marking char
DC.B 0 ;style of item text

DC.B 0 ;end of menu items

.ALIGN 2
DC.W 0 ;width holder
DC.W 0 ;height holder
DC.L 0 ;resource id
DC.L \$1FF;flags
DC.B 4 ;title length
DC.B ‘File’;title

DC.B ‘Quit/Q’
DC.B 0 ;no icon
DC.B 0 ;no keyboard
DC.B 0 ;no marking
DC.B 0 ;style

DC.B 0 ;end of menu items

.ALIGN 2
DC.W 0 ;width holder
DC.W 0 ;height holder
DC.L 0 ;resource id
DC.L \$1F8;flags - DISABLE
DC.B 16;title length
DC.B ‘Click in Window ‘  ;title

DC.B ‘’
DC.B 0 ;no icon
DC.B 0 ;no keyboard
DC.B 0 ;no marking
DC.B 0 ;style

DC.B 0 ;end of menu items
```

Community Search:
MacTech Search:

Pinegrow 6.23 - Mockup and design web pa...
Pinegrow (was Pinegrow Web Designer) is desktop app that lets you mockup and design webpages faster with multi-page editing, CSS and LESS styling, and smart components for Bootstrap, Foundation,... Read more
WhatsApp 2.2149.4 - Desktop client for W...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
Microsoft Remote Desktop 10.7.4 - Connec...
Microsoft Remote Desktop for Mac is an application that allows connecting to virtual apps or another PC remotely. Discover the power of Windows with Remote Desktop designed to help you manage your... Read more
ffWorks 2.6.7 - Convert multimedia files...
ffWorks, focused on simplicity, brings a fresh approach to the use of FFmpeg, allowing you to create ultra-high-quality movies without the need to write a single line of code on the command-line.... Read more
Opera 82.0.4227.58 - High-performance We...
Opera is a fast and secure browser trusted by millions of users. With the intuitive interface, Speed Dial and visual bookmarks for organizing favorite sites, news feature with fresh, relevant content... Read more
Day One 6.15 - Maintain a daily journal.
Day One is an easy, great-looking way to use a journal / diary / text-logging application. Day One is well designed and extremely focused to encourage you to write more through quick Menu Bar entry,... Read more
Default Folder X 5.6.3 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click on... Read more
OmniOutliner Pro 5.9.2 - Pro version of...
OmniOutliner Pro is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually designed to help you think. It's... Read more
OmniOutliner Essentials 5.9.2 - Organize...
OmniOutliner Essentials (was OmniOutliner) is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually... Read more
QuickBooks 19.0.11.984 - Financial manag...
QuickBooks helps you manage your business easily and efficiently. Organize your finances all in one place, track money going in and out of your business, and spot areas where you can save. Built for... Read more

## Latest Forum Discussions

The Best Wordle Clone in Town – The Touc...
In this week’s episode of The TouchArcade Show we dig into the drama of the moment which is the cloning and subsequent gloating about the cloning of the lovely little free word game Wordle. This leads into some additional drama about how PUGB Mobile... | Read more »
TouchArcade Game of the Week: ‘Cards Inf...
There’s nothing I love more than a perfect mobile game. What do I mean by that? Well, no game is actually perfect, but there’s something special about a game you know you can just whip out at a moment’s notice and dive into, and you know it will... | Read more »
‘Micro RPG’ Bringing Streamlined RPG Goo...
Originally announced on our forums more than 3 years ago, Micro RPG is an upcoming mobile game from a two-person studio that goes by the name JoliYeti Games and, as the title implies, it looks to offer all the fun of an RPG but in a more condensed... | Read more »
Hello gentle readers, and welcome to the SwitchArcade Round-Up for January 14th, 2022. Yesterday was a big day, but today shows that we’re still warming up the engines for this year. There are a handful of new releases, but nothing nearly as... | Read more »
Mobile MMORPG Shooter ‘Avatar: Reckoning...
Archosaur Games, Tencent, Lightstorm Entertainment, and Disney have just revealed a mobile MMORPG shooter Avatar: Reckoning. Avatar: Reckoning will be published by Level Infinite when it hits iOS and Android. It is an official Avatar game developed... | Read more »
‘Crashlands+’ Is Out Now on Apple Arcade...
The brilliant Crashlands from Butterscotch Shenanigans was confirmed to arrive on Apple Arcade as an App Store Great in the form of Crashlands+ () a little while ago and it has just released worldwide. If it isn’t live yet, it should roll out in... | Read more »
Hello gentle readers, and welcome to the SwitchArcade Round-Up for January 13th, 2022. It’s a Thursday, and we’ve got a pretty hefty bag of new releases to dig into. There are always some fun surprises, and this week that came in the form of SNK Vs... | Read more »
‘Crush the Castle Legacy Collection’ Lau...
Ever since Angry Birds broke into the mainstream and became a household name more than a decade ago, there’s always been a small niche of people on the sidelines who would pipe up to remind everybody that “Crush the Castle did it first!" Indeed, the... | Read more »
Non-Violent Stealth Game ‘El Hijo – A Wi...
Over a year ago, Handy Games brought the non-violent stealth game El Hijo – A Wild West Tale to Switch, PS4, Xbox, PC, and Stadia. El Hijo – A Wild West Tale has been developed by Honig Studios and Quantumfrog. You play as El Hijo, a six year old,... | Read more »
‘ZED BLADE’ from SNK and Hamster Is Out...
After a bit of a break likely due to the holiday season, we’ve gotten a new title in the ACA NeoGeo series on iOS and Android. SNK and Hamster originally brought the series to mobile with Samurai Shodown IV, Alpha Mission II, and Metal Slug 5.... | Read more »

## Price Scanner via MacPrices.net

Get an Apple Watch Series 7 for \$50 off MSRP,...
Amazon has Apple Watch Series 7 models on sale for \$50 off MSRP including free shipping. Their prices are the lowest available for Apple Watch Series 7 models today: – 41mm Apple Watch Series 7 GPS... Read more
Here are the details of Apple’s 2022 Educatio...
Need a new Apple Mac or iPad for school? Whether you’re a student, teacher, or staff member, you can use your .edu email address when ordering at Apple Education to take up to \$400 off the price of a... Read more
Amazon is blowing out 2020 21″ iMacs for only...
Amazon has clearance 2020 21″ iMacs (2.3GHz Dual-Core i5, 8GB RAM, 256GB SSD) on sale right now for \$599.99 including free shipping. Original MSRP for this model was \$1099. Amazon expects delivery in... Read more
Find the best deal on an Apple MacBook using...
In the market for a new 13″ MacBook Air, 13″ MacBook Pro, 14″ MacBook Pro, or 16″ MacBook Pro with M1, M1 Pro, or M1 Max Apple Silicon? Use our Apple award-winning and exclusive price trackers to... Read more
Red Pocket Mobile is offering the Apple iPhon...
Switch to Red Pocket Mobile and get an Apple iPhone 13 Pro for \$50 off MSRP, plus get free 6 months of Unlimited nationwide 5G service with the purchase of any iPhone 13. Red Pocket Mobile is a... Read more
24″ M1 iMacs on sale for \$1249, \$50 off Apple...
Amazon has base 24″ M1 iMacs (8-Core CPU/7-Core GPU/8GB RAM/256GB SSD) on sale today for \$1249 shipped. Their price is \$50 off Apple’s MSRP, and it’s the lowest price available for a new 24″ M1 iMac... Read more
Open-Box 16″ M1 Pro MacBook Pros available fo...
QuickShip Electronics has open-box return 16″ M1 Pro MacBook Pros in stock and on sale for \$200-\$300 off MSRP on their eBay store right now with free express delivery. According to QuickShip, “The... Read more
Stock Alert! Order a new 16″ M1 Pro MacBook P...
New 16″ MacBook Pros with Apple’s M1 Pro and M1 Max CPUs have been very hard to find, largely due to current global supply constraints. However, B&H Photo is reporting stock of Space Gray... Read more
Apple has maxed-out 13″ M1 MacBook Airs (16GB...
Save \$250 on maxed-out 13″ M1 MacBook Airs today at Apple (16GB RAM/1TB SSD) with Certified Refurbished models available for \$1399 in Space Gray and Gold colors. Regular price for this configuration... Read more
New promo at Xfinity Mobile: \$400 off any App...
Xfinity Mobile is offering any new Apple iPhone for \$400 off MSRP for new customers. This includes the iPhone 13. Price for the phone, including the discount, is spread monthly over a 24 month term... Read more

## Jobs Board

Registered Nurse (RN) Employee Health PSJH -...
…is calling for a Registered Nurse (RN) Employee Health PSJH to our location in Apple Valley, CA.** We are seeking a Registered Nurse (RN) Employee Health PSJH to be Read more
Systems Administrator - Pearson (United State...
…and troubleshoot Windows operating systems (workstation and server), laptop computers, Apple iPads, Chromebooks and printers** + **Administer and troubleshoot all Read more
IT Assistant Level 1- IT Desktop Support Anal...
…providing tier-1 or better IT help desk support in a large Windows and Apple environment * Experience using IT Service Desk Management Software * Knowledge of IT Read more
Human Resources Business Partner PSJH - Provi...
…**is calling a** **Human Resources Business Partner, PSJH** **to our location in Apple Valley, CA.** **Applicants that meet qualifications will receive a text with Read more
Manager Community Health Investment Programs...
…is calling a Manager Community Health Investment Programs PSJH to our location in Apple Valley, CA.** **Qualified candidates will be invited to do a self-paced video Read more