I recently ran across an article about calculating the mathematical constant e (the base of natural logarithms) to 116,000 places in the June 1981 issue of Byte magazine, written by some character named Wozniak. (It seems unlikely that he is notable for anything else 6502 related.)
https://archive.org/stream/byte-magazin ... 3/mode/2up(The Wikipedia page for e also references that article.)
https://en.wikipedia.org/wiki/E_(mathematical_constant)
In the Byte article are program listings for the Apple II; two 6502 assembly programs, and an "Integer" BASIC program. It calculates e by fixed-point arithmetic using the well-known (and more importantly, quickly converging) series:
Code:
1 1 1 1
e = 1 + -- + -- + -- + -- + ...
1! 2! 3! 4!
(Actually, only fractional part is calculated, i.e. excluding the terms to the left of the 1/2! term.) It is an interesting article. A naive implementation would use (roughly) half of available RAM to hold the current (1/n!) term, and the other half of available RAM to hold the current sum, but the article points out that by working from right to left, (roughly) all of available RAM can be used for the sum, and thus twice as many digits can be calculated.
In fact, working from right to left is exactly how the spigot algorithm for e works. (Although they look quite different from one another, the formula in the article that illustrates how the series is calculated from right to left is the same "refactoring" (to borrow a software term) of the above series for e in Rabinowitz and Wagon's classic spigot paper.)
After trying the program in the article, I wrote a short implementation of a spigot algorithm for comparison. Of course, I did not have the patience to wait out a 34500 (decimal) digit calculation (which is the number of digits the programs in article will output). Instead I made NUMPAG 1 (and adjusted elsewhere accordingly) which gives 616 correct digits (after the decimal point). (Note that a 256 byte number is a 616 (decimal) digit number, i.e. 256*log10(256) =~ 616.5)
The spigot program -- which also only calculates (actually it only converts one base to another) the fractional part -- uses a comparable amount of space (254 bytes) for the mixed-radix number, and the largest divisor used is 255 (and divisors are therefore 8 bits wide -- the main reason for cutting off the calculation here). Rabinowitz and Wagon state that n+2 mixed-radix digits suffices for n decimal digits. This is true, but it greatly understates the number of correct digits you will actually get, especially as n gets larger. A divisor of 255 corresponds to the 1/255! term in the series above, so we should expect that there should be about 504 correct digits, since log10(255!) =~ 504.5, and this is exactly how many correct digits there happen to be. It is also double the number of digits (254-2) you would expect according to Rabinowitz and Wagon's criteria.
As you may have noticed, this initial comparison of the two programs is not going to be totally fair (616 digits vs. 504 digits). The idea at this stage was to have them close enough to do an initial rough A-B comparison, rather than attempt to draw any final conclusions.
Woz's program has two passes: the first pass is the fixed point calculation of e, and the second pass converts that result to base 10. In the second pass there is an assembly routine to multiply by 100 (to obtain the next 2 digits); the BASIC program (using division and MOD) splits apart the two digits, and outputs them, formatting them into neat columns.
Because the spigot program is entirely assembly, step one is to convert the BASIC program to assembly, so that we are comparing fixed-point to spigot rather than the BASIC interpreter (and its division, output, etc. routines) to their more streamlined equivalents in assembly.
In addition, there is a routine which will patch the (original) assembly code to use only 1, 2, or 4 (instead of 56 = $38) pages for the fixed-point numbers, and to use only as many terms in the series as needed.
From the Apple II monitor, to patch the program to use 4 pages for the fixed-point number (and 1000 terms) as recommended in the article, and calculate and output the result, type: (the monitor places the (low byte) of hex number before the < in zero page location $42)
8<4403G4400G
1 page (i.e. a page of paper, not a page of memory) of digits is output; and 42 lines of the last (and in this case, only) page are output. There are 60 digits per line (in 12 groups of 5), so that is 42*60 = 2520 digits (after the decimal point).
Likewise, to use 1 (memory) page for the fixed-point number, type:
10<4403G4400G
Note that we want to output all of the correct digits, since we are only outputting entire lines; so we actually output more than the expected number of correct digits, and indeed the last few digits output will be incorrect.
Likewise, to use 2 (memory) pages for the fixed-point number, type:
18<4403G4400G
To patch the assembly back to its original values in the program listings, type:
0<4403G
Here is the assembly listing:
Code:
{ } ; Listing 4: INTBASIC driver program converted to assembly
{ FD8E } :OUTCRLF = $FD8E
{ } ; 12*5 digits/line * (60 lines/page * (10-1) pages + 35 lines) = 34500 digits
{ } ;
{ } ; $38 * 256 * log10(256) =~ 34524.5 digits
{ } ; log10(9720!) =~ 34541.17
{ } ;
{ 000A } :PAGES = 10
{ 0023 } :LINES = 35
{ 0001 } :RESULT = 1
{ 4000 } :INIT = $4000
{ 401B } :MULT = $401B
{ 4300 } :DIV10 = $4300
{ 4370 } :MOD10 = $4370
{ 4400 } ORG $4400
{ 4400 4C 06 44 } JMP =PRINT
{ 4403 4C 04 45 } JMP =PATCH
{ 4406 } :PRINT
{ 4406 20 EA 44 } JSR =GENTBLDIV
{ 4409 20 00 40 } JSR =INIT
{ 440C A9 80 } LDA # $80
{ 440E 85 01 } STA =RESULT ; tracks which of the two digits to output
{ 4410 A9 01 } LDA # 1
{ 4412 8D E4 44 } STA =PAGE
{ 4415 20 44 44 } .1 JSR =OUTHEADER ; loop to output a page
{ 4418 A9 01 } LDA # 1
{ 441A 8D E3 44 } STA =LINE
{ 441D 20 6D 44 } .2 JSR =OUTLINE ; loop to output a line
{ 4420 AD E3 44 } LDA =LINE
{ 4423 AC E4 44 } LDY =PAGE
{ 4426 C0 0A } .A CPY # =PAGES ; this gets patched
{ 4428 D0 04 } BNE =.3
{ 442A C9 23 } .B CMP # =LINES ; this gets patched
{ 442C F0 15 } BEQ =.4
{ 442E C9 3C } .3 CMP # 60
{ 4430 EE E3 44 } INC =LINE
{ 4433 90 E8 } BCC =.2
{ 4435 20 8E FD } JSR =OUTCRLF
{ 4438 20 8E FD } JSR =OUTCRLF
{ 443B 20 8E FD } JSR =OUTCRLF
{ 443E EE E4 44 } INC =PAGE
{ 4441 D0 D2 } BNE =.1
{ 4443 60 } .4 RTS
{ 4444 } :OUTHEADER ; output a page header
{ 4444 20 8E FD } JSR =OUTCRLF
{ 4447 A0 06 } LDY # 6
{ 4449 20 C1 44 } JSR =OUTSP
{ 444C A9 45 } LDA # $45 ; E
{ 444E 20 E5 44 } JSR =OUTPUT
{ 4451 A0 3F } LDY # 63
{ 4453 20 C1 44 } JSR =OUTSP
{ 4456 A0 00 } LDY # =STRPAGE =STRINGS -
{ 4458 20 CA 44 } JSR =OUTSTR
{ 445B AC E4 44 } LDY =PAGE
{ 445E 20 B9 44 } JSR =OUTTENS
{ 4461 B9 70 43 } LDA =MOD10 ,Y
{ 4464 20 BC 44 } JSR =OUTDIGIT
{ 4467 20 8E FD } JSR =OUTCRLF
{ 446A 4C 8E FD } JMP =OUTCRLF
{ 446D } :OUTLINE ; output 1 line (60 digits)
{ 446D 20 8D 44 } JSR =OUTMARGIN
{ 4470 A9 0C } LDA # 12 ; 12 groups of digits
{ 4472 48 } .1 PHA
{ 4473 A9 05 } LDA # 5 ; 5 digits per group
{ 4475 48 } .2 PHA
{ 4476 20 A1 44 } JSR =OUTNEXT
{ 4479 68 } PLA
{ 447A 38 } SEC
{ 447B E9 01 } SBC # 1
{ 447D D0 F6 } BNE =.2
{ 447F A0 01 } LDY # 1
{ 4481 20 C1 44 } JSR =OUTSP
{ 4484 68 } PLA
{ 4485 38 } SEC
{ 4486 E9 01 } SBC # 1
{ 4488 D0 E8 } BNE =.1
{ 448A 4C 8E FD } JMP =OUTCRLF
{ 448D } :OUTMARGIN ; output the left margin
{ 448D AD E4 44 } LDA =PAGE
{ 4490 0D E3 44 } ORA =LINE
{ 4493 C9 02 } CMP # 2
{ 4495 B0 05 } BCS =.1
{ 4497 A0 06 } LDY # =STREE2P =STRINGS - ; output E=2. if page 1, line 1
{ 4499 4C CA 44 } JMP =OUTSTR
{ 449C A0 06 } .1 LDY # 6
{ 449E 4C C1 44 } JMP =OUTSP ; otherwise output spaces
{ 44A1 } :OUTNEXT ; output the next digit
{ 44A1 A5 01 } LDA =RESULT
{ 44A3 10 08 } BPL =.1
{ 44A5 20 1B 40 } JSR =MULT ; multiply by 100
{ 44A8 A4 01 } LDY =RESULT ; PEEK(1)
{ 44AA 4C B9 44 } JMP =OUTTENS ; output the tens digit
{ 44AD A4 01 } .1 LDY =RESULT ; PEEK(1)
{ 44AF A9 80 } LDA # $80
{ 44B1 85 01 } STA =RESULT
{ 44B3 B9 70 43 } LDA =MOD10 ,Y ; output the ones digit
{ 44B6 4C BC 44 } JMP =OUTDIGIT
{ 44B9 } :OUTTENS
{ 44B9 B9 00 43 } LDA =DIV10 ,Y ; fall thru
{ 44BC } :OUTDIGIT
{ 44BC 49 30 } EOR # $30
{ 44BE 4C E5 44 } JMP =OUTPUT
{ 44C1 } :OUTSP ; output Y spaces
{ 44C1 A9 20 } .1 LDA # $20
{ 44C3 20 E5 44 } JSR =OUTPUT
{ 44C6 88 } DEY
{ 44C7 D0 F8 } BNE =.1
{ 44C9 60 } RTS
{ 44CA } :OUTSTR ; output the string indexed by Y
{ 44CA B9 D6 44 } .1 LDA =STRINGS ,Y
{ 44CD F0 06 } BEQ =.2
{ 44CF 20 E5 44 } JSR =OUTPUT
{ 44D2 C8 } INY
{ 44D3 D0 F5 } BNE =.1
{ 44D5 60 } .2 RTS
{ 44D6 } :STRINGS
{ } :STRPAGE STR "PAGE "00
{ 44D6 50 41 47 45 20 00 }
{ } :STREE2P STR " E=2."00
{ 44DC 20 20 45 3D 32 2E 00 }
{ 44E3 } :LINE DS 1
{ 44E4 } :PAGE DS 1
{ 44E5 } :OUTPUT
{ 44E5 49 80 } EOR # $80
{ 44E7 4C ED FD } JMP $FDED
{ 44EA } :GENTBLDIV ; generate divide-by-10 quotient and remainder tables
{ 44EA A2 63 } LDX # 99
{ 44EC A9 09 } LDA # 9
{ 44EE 38 } SEC
{ 44EF A0 09 } .1 LDY # 9
{ 44F1 9D 00 43 } .2 STA =DIV10 ,X
{ 44F4 98 } TYA
{ 44F5 9D 70 43 } STA =MOD10 ,X
{ 44F8 BD 00 43 } LDA =DIV10 ,X
{ 44FB CA } DEX
{ 44FC 88 } DEY
{ 44FD 10 F2 } BPL =.2
{ 44FF E9 01 } SBC # 1
{ 4501 B0 EC } BCS =.1
{ 4503 60 } RTS
{ 4504 } :PATCH ; patch the assembly for different numbers of digits and terms
{ 4504 A6 42 } LDX $42
{ 4506 BD 34 45 } LDA =.1 ,X
{ 4509 8D 41 02 } STA =NXTDVSR 1 +
{ 450C 8D 1C 40 } STA =MULT 1 +
{ 450F 18 } CLC
{ 4510 69 07 } ADC # =E 256 - 256 /
{ 4512 8D 20 40 } STA =MULT 5 +
{ 4515 BD 36 45 } LDA =.1 2 + ,X
{ 4518 8D 69 02 } STA =NLREF1 1 +
{ 451B 8D 70 02 } STA =NLREF2 1 +
{ 451E BD 37 45 } LDA =.1 3 + ,X
{ 4521 8D 61 02 } STA =NHREF1 1 +
{ 4524 8D 75 02 } STA =NHREF2 1 +
{ 4527 BD 38 45 } LDA =.1 4 + ,X
{ 452A 8D 27 44 } STA =PRINT.A 1 +
{ 452D BD 3A 45 } LDA =.1 6 + ,X
{ 4530 8D 2B 44 } STA =PRINT.B 1 +
{ 4533 60 } RTS
{ } ; 12*5 digits/line * (60 lines/page * (P-1) pages + L lines) = N digits
{ } .1 DW ( $38 9720 10 35 )
{ 4534 38 00 F8 25 0A 00 23 00 }
{ } DW ( 4 1000 1 42 ) ; 4 * 256*log10(256) =~ 2466.038 digits
{ 453C 04 00 E8 03 01 00 2A 00 }
{ } ; log10(1000!) =~ 2567.6
{ } ; 12*5*(60*(1-1)+42) = 2520 digits
{ } DW ( 1 301 1 11 ) ; 1 * 256*log10(256) =~ 616.5 digits
{ 4544 01 00 2D 01 01 00 0B 00 }
{ } ; log10(301!) =~ 616.96
{ } ; 12*5*(60*(1-1)+11) = 660 digits
{ } DW ( 2 537 1 21 ) ; 2 * 256*log10(256) =~ 1233.019 digits
{ 454C 02 00 19 02 01 00 15 00 }
{ } ; log10(537!) =~ 1234.5
{ } ; 12*5*(60*(1-1)+21) = 1260 digits
The spigot program is mostly the same as the algorithm described by Rabinowitz and Wagon. It uses base 100 (thus calculating 2 digits as at time, same as Woz's program), i.e. it multiplies by 100 instead of 10. It uses exactly the same divide-by-10 tables as the program above (GENTBLDIV is identical in both programs) to split two base 10 digits. The multiply-by-100 tables are also identical. GENTBLMUL is nearly the same as Woz's INIT (Listing 2), the only difference being that the former uses LDA MUL100L,X (4 cycles, 3 bytes) and the latter uses PHA and PLA (7 cycles, 2 bytes).
The spigot program also formats the output into neat columns of 5 groups of 5 digits per line, with a blank line after every 5 lines of digits. This makes it very easy to see how many digits you have. I chose 5 groups instead of 10, since the former will fit on the Apple II 40 column text screen, and that's much easier to read than the 80 column screen. (Paging BDD to the white courtesy phone for remarks about aging and vision
)
Note that the addresses of the tables, and in particular of N (the mixed-radix) number, were chosen to avoid page boundary crossings.
The LDA #DIGITS&1 line looks superfluous, but it means the program will still work if you reduce the value of DIGITS. You could use conditional assembly here instead. DIGITS, by the way, is the total number of digits (before and after the decimal point).
None of these routines (including Woz's assembly routines) are Apple specific, so it's easy to run them on another machine. As you'd expect, OUTPUT ($FDED on the Apple II) outputs the character in the accumulator (on the Apple II (normal video) characters have their high bit set, hence the EOR #$80). OUTCRLF ($FD8E on the Apple II) outputs the appropriate CR,LF sequence for your system (on the Apple II, this is a CR only).
What would we expect in a fair comparison?
We'd expect the advantage of the fixed-point calculation to be that it's looping over a smaller amount of memory (a 504 (decimal) digit can be stored in 210 bytes, whereas the mixed-radix number is stored in 254 bytes).
We'd expect the advantage of the spigot routine to be that it only has one pass, since it doesn't actually calculate e, it just converts to base 10. (In the mixed-radix base, e is exactly 1.111111... (i.e. repeating forever) in the same way that 1/3 is exactly .333333... in base 10). We can also make a spigot calculation speed up as it goes along (Rabinowitz and Wagon's paper doesn't mention this); the spigot program currently doesn't do this.
In the unfair comparison we would expect the spigot routine to be faster for a couple of reasons (thus if it weren't, there would be no need to make the test more fair). First, it's calculating fewer digits (504 vs. 616). Second, it uses 8-bit divisors (the fixed-point calculation uses 16-bit divisors, since it goes beyond the 1/255! term).
Indeed, the spigot is faster in the unfair comparison. I measured 16 seconds for the spigot, and 27 seconds for fixed-point, both at ~1.02 MHz.
Thus the next step (which I have not yet taken) would be to do a fair comparison. First, calculate the same number of digits for both. Second, use 8 bit divisors for both for the terms before 1/255!, and 16 bit divisors for both for the terms beyond 1/255!. Third, add the speed-up-as-you-go-along optimization to the spigot calculation (this is fair, since we want to compare an optimal spigot algorithm to an optimal fixed-point algorithm -- rather than comparing an 8-bit division routine to a 16-bit division routine). (By the way, do you see why the fixed-point calculation can't do the spigot speed-up optimization?)
Here is the spigot program:
Code:
{ 01F8 } :DIGITS = 504
{ 7C00 } :MUL100L = $7C00
{ 7D00 } :MUL100H = $7D00
{ 7E00 } :DIV10 = $7E00
{ 7E70 } :MOD10 = $7E70
{ 7F02 } :N = $7F02
{ 8000 } ORG $8000
{ 8000 } :E
{ 8000 D8 } CLD
{ 8001 20 34 81 } JSR =OUTINIT
{ 8004 20 8F 80 } JSR =GENTBLDIV
{ 8007 20 A9 80 } JSR =GENTBLMUL
{ 800A 20 C1 80 } JSR =INIT
{ 800D A9 32 } LDA # $32 ; output 2.
{ 800F 20 49 81 } JSR =OUTPUT
{ 8012 A9 2E } LDA # $2E
{ 8014 20 49 81 } JSR =OUTPUT
{ 8017 A2 FB } LDX # =DIGITS 1 - 2 /
{ 8019 8A } .1 TXA ; loop to output digits
{ 801A 48 } PHA
{ 801B A9 00 } LDA # 0
{ 801D A2 FF } LDX # $FF
{ 801F 18 } .2 CLC ; loop to multiply mixed-radix number by 100
{ 8020 BC 00 7F } LDY =N 2 - ,X
{ 8023 79 00 7C } ADC =MUL100L ,Y
{ 8026 8D E4 80 } STA =Q
{ 8029 A9 00 } LDA # 0
{ 802B 79 00 7D } ADC =MUL100H ,Y
{ 802E 8E E5 80 } STX =R
{ 8031 20 CC 80 } JSR =DIV
{ 8034 9D 00 7F } STA =N 2 - ,X
{ 8037 AD E4 80 } LDA =Q
{ 803A CA } DEX
{ 803B E0 02 } CPX # 2
{ 803D B0 E0 } BCS =.2
{ 803F A8 } TAY
{ 8040 B9 00 7E } LDA =DIV10 ,Y ; output first digit
{ 8043 49 30 } EOR # $30
{ 8045 20 49 81 } JSR =OUTPUT
{ 8048 B9 70 7E } LDA =MOD10 ,Y ; output second digit
{ 804B 49 30 } EOR # $30
{ 804D 20 49 81 } JSR =OUTPUT
{ 8050 68 } PLA
{ 8051 AA } TAX
{ 8052 CA } DEX
{ 8053 D0 C4 } BNE =.1
{ 8055 A9 00 } LDA # =DIGITS 1 &
{ 8057 F0 01 } BEQ =.A ; branch if there is an even number of digits
{ 8059 60 } RTS
{ 805A AC 03 7F } .A LDY =N 1 + ; multiply by 100...
{ 805D B9 00 7C } LDA =MUL100L ,Y ; ...starting with second to last mixed-radix digit
{ 8060 8D E4 80 } STA =Q
{ 8063 B9 00 7D } LDA =MUL100H ,Y
{ 8066 EE E5 80 } INC =R
{ 8069 20 CC 80 } JSR =DIV
{ 806C 18 } CLC
{ 806D AC 02 7F } LDY =N ; last mixed radix digit
{ 8070 B9 00 7C } LDA =MUL100L ,Y
{ 8073 6D E4 80 } ADC =Q
{ 8076 8D E4 80 } STA =Q
{ 8079 B9 00 7D } LDA =MUL100H ,Y
{ 807C 69 00 } ADC # 0
{ 807E CE E5 80 } DEC =R
{ 8081 20 CC 80 } JSR =DIV
{ 8084 AC E4 80 } LDY =Q
{ 8087 B9 00 7E } LDA =DIV10 ,Y ; output digit
{ 808A 49 30 } EOR # $30
{ 808C 4C 49 81 } JMP =OUTPUT
{ 808F } :GENTBLDIV ; generate divide-by-10 quotient and remainder tables
{ 808F A2 63 } LDX # 99
{ 8091 A9 09 } LDA # 9
{ 8093 38 } SEC
{ 8094 A0 09 } .1 LDY # 9
{ 8096 9D 00 7E } .2 STA =DIV10 ,X
{ 8099 98 } TYA
{ 809A 9D 70 7E } STA =MOD10 ,X
{ 809D BD 00 7E } LDA =DIV10 ,X
{ 80A0 CA } DEX
{ 80A1 88 } DEY
{ 80A2 10 F2 } BPL =.2
{ 80A4 E9 01 } SBC # 1
{ 80A6 B0 EC } BCS =.1
{ 80A8 60 } RTS
{ 80A9 } :GENTBLMUL ; generate multiply-by-100 hi and lo byte tables
{ 80A9 A9 00 } LDA # 0
{ 80AB AA } TAX
{ 80AC A8 } TAY
{ 80AD 9D 00 7C } .1 STA =MUL100L ,X
{ 80B0 98 } TYA
{ 80B1 9D 00 7D } STA =MUL100H ,X
{ 80B4 BD 00 7C } LDA =MUL100L ,X
{ 80B7 18 } CLC
{ 80B8 69 64 } ADC # 100
{ 80BA 90 01 } BCC =.2
{ 80BC C8 } INY
{ 80BD E8 } .2 INX
{ 80BE D0 ED } BNE =.1
{ 80C0 60 } RTS
{ 80C1 } :INIT ; initial mixed-base digits to 1
{ 80C1 A2 FE } LDX # $FE
{ 80C3 A9 01 } LDA # 1
{ 80C5 9D 01 7F } .1 STA =N 1 - ,X
{ 80C8 CA } DEX
{ 80C9 D0 FA } BNE =.1
{ 80CB 60 } RTS
{ 80CC } :DIV ; 256*a+q / r -> t = b * q + a
{ 80CC A0 08 } LDY # 8
{ 80CE 0E E4 80 } ASL =Q
{ 80D1 2A } .1 ROL A
{ 80D2 B0 05 } BCS =.2
{ 80D4 CD E5 80 } CMP =R
{ 80D7 90 04 } BCC =.3
{ 80D9 ED E5 80 } .2 SBC =R
{ 80DC 38 } SEC
{ 80DD 2E E4 80 } .3 ROL =Q
{ 80E0 88 } DEY
{ 80E1 D0 EE } BNE =.1
{ 80E3 60 } RTS
{ 80E4 } :Q DS 1
{ 80E5 } :R DS 1
{ 80E6 } :OUTSPS ; output Y spaces
{ 80E6 48 } .1 PHA
{ 80E7 A9 A0 } LDA # $A0
{ 80E9 20 ED FD } JSR $FDED
{ 80EC 68 } PLA
{ 80ED 38 } SEC
{ 80EE E9 01 } SBC # 1
{ 80F0 D0 F4 } BNE =.1
{ 80F2 60 } RTS
{ 80F3 } :FORMAT ; format digits into columns
{ 80F3 48 } PHA
{ 80F4 2C 31 81 } BIT =FORMATD
{ 80F7 30 2C } BMI =.5
{ 80F9 A9 01 } LDA # 1
{ 80FB CE 31 81 } DEC =FORMATD
{ 80FE D0 23 } BNE =.4
{ 8100 CE 30 81 } DEC =FORMATC
{ 8103 D0 16 } BNE =.2
{ 8105 A9 05 } LDA # 5
{ 8107 8D 30 81 } STA =FORMATC
{ 810A CE 33 81 } DEC =FORMATR
{ 810D D0 06 } BNE =.1
{ } ; LDA # 5
{ 810F 8D 33 81 } STA =FORMATR
{ 8112 2C 8E FD } BIT $FD8E
{ 8115 20 8E FD } .1 JSR $FD8E
{ 8118 AD 32 81 } LDA =FORMATI
{ 811B 20 E6 80 } .2 JSR =OUTSPS
{ 811E A9 05 } LDA # 5
{ 8120 8D 31 81 } .3 STA =FORMATD
{ 8123 68 } .4 PLA
{ 8124 60 } RTS
{ 8125 EE 32 81 } .5 INC =FORMATI
{ 8128 C9 2E } CMP # $2E
{ 812A D0 F7 } BNE =.4
{ 812C A9 06 } LDA # 6
{ 812E 10 F0 } BPL =.3
{ 8130 05 } :FORMATC DB 5 ; digit counter
{ 8131 FF } :FORMATD DB $FF ; digit group counter
{ 8132 00 } :FORMATI DB 0 ; indentation
{ 8133 05 } :FORMATR DB 5 ; row (line) counter
{ 8134 } :OUTINIT ; initialize output formatting variables
{ 8134 A9 05 } LDA # 5
{ 8136 8D 30 81 } STA =FORMATC
{ 8139 A9 FF } LDA # $FF
{ 813B 8D 31 81 } STA =FORMATD
{ 813E A9 00 } LDA # 0
{ 8140 8D 32 81 } STA =FORMATI
{ 8143 A9 05 } LDA # 5
{ 8145 8D 33 81 } STA =FORMATR
{ 8148 60 } RTS
{ 8149 } :OUTPUT ; output a character
{ 8149 20 F3 80 } JSR =FORMAT
{ 814C 49 80 } EOR # $80
{ 814E 4C ED FD } JMP $FDED