[80-byte Pi calculator]                                [Assembler][/][8086]

The following program is a tiny program displaying digits of Pi. The
decimal version displays PI's 3, no decimal, then is followed by 9303
digits of PI. It is a rewrite of the 125-byte tinypi.com found on Jason
Papadopoulos's webpage (see below). I re-wrote it to 113 bytes, and sent
him a copy. The enclosed 110-byte program (see archive) is basically the
same program, except saves 3 bytes by using INT 29h for screen display.

The enclosed 80-byte hex program is based on a rough version of a C program
Jason sent me after I was having trouble converting the decimal version to
output PI in hexadecimal. This program outputs 0003, no decimal, followed
by another 8184 hexadecimal digits of PI. Notice that the very last digit
is wrong. The last 4 digits should be 209C, but are displayed as 209F.
There appears to be some sort of rounding error in the algorithm, so in
actual usage, you should probably attempt to calculate to calculate two
more words than you need to display or use.

Warning: While the decimal program displays digits as it runs, the
hexadecimal version will have a long pause, followed by the digits being
displayed all at once.

Here is what have been optimized since the 92-byte program, by Mark Andreas

     I've rewritten the program which displays hexadecimal digits of PI,
     reducing its size to 80 bytes. The main reason for the reduction in
     size is due to accidentally discovering that the program doesn't need
     to begin the program by initializing array[] to 0000's.

     Also, this version is slightly faster, taking 20 seconds on my P166 -
     compared to 22 seconds for the previous version. I believe the main
     reasons for the speed difference is that the array is now aligned on
     even-word boundaries, and that I am now getting a WORD from memory and
     multiplying it by a register, instead of multiplying by a memory
     variable. Other changes were:
        o swapped function of BX and DI registers, allowing STOSW instead
          of MOV [BX],AX following by a couple of INC BX's.
        o made a slight change in the way numbers were printed.
     The source code describes a couple changes which can be made to the
     program. It's possible to make a change to the number-printing section
     which reduces the program by 2 bytes, but uses code that's valid only
     on a 286 or better. Another change to the number printing increases
     the size of the program if you need to be able to redirect the data to
     a diskfile and/or have the hex numbers be in lower-case.

     Unfortunately, the changes made here-in would have no effect on the
     decimal program, except for the no-size-change where you change:

             mov     ax,bp           ; ax = 10,000
             add     bx,bx           ; align for 2-byte array elements
             mul     [word ptr bx+offset Buffer-2]

     into:

             mov     ax,[bx+offset Buffer-2]
             add     bx,bx           ; align for 2-byte array elements
             mul     bp

     This was one of the things which sped up the new hexadecimal version,
     so it's probably worth changing the existing decimal-version to use
     this code change.

An archive containg both the decimal, the 92-byte hex and 80-byte hex
versions are available for download (6965 bytes, sources, object files and
compiled files are included).
                 See http://www.glue.umd.edu/~jasonp/ for more information.

.radix 10
syze=2047               ; size is a reserved word in ASM
                        ; 2047 the is largest value which allows
                        ; denom to be less than 65536
        ; 2047 words is 8188 digits, of which the 1st 4 digits
        ; are the 0003 to the left of the decimal, and the last
        ; digit is probably wrong
        ; Note:  This program waits until after calculating all
        ; values.  This means that there may be a long pause before
        ; any display.  The delay can be over a minute on 486's
        ; or slower Pentiums.
        ; Note:  It appears that a rounding error creeps into
        ; the last 1 or 2 words of display.  In actual usage, it's
        ; probably best to calculate 2 more words than displaying.
        ; In this case, the last displayed WORD should be 209C.

terms=syze*16
denom=2*terms+1

.model tiny
.code
org 100h
start:
array=terms             ; note, am defining array=terms, which
                        ; permits copying values from other registers
                        ; "terms" was used instead of "syze", because
                        ; terms is always even, allowing faster
                        ; access of memory variables

        mov     bx,terms        ; terms=syze*16
        mov     bp,(terms*2)+1  ; denom=2*terms+1
        mov     si,bx           ; si=base of array[], start of printing
                                ; should be "mov si,offset array", but
                                ; am reducing size to allow "mov si,bx"
        mov     di,si           ; di=array[0], or same as si register

;       sub     ax,ax           ; assume program begins with AX=0000
;       push    di              ; it's not required to clear the array[]
;       rep     stosw           ; only effect is that the last digits
;       pop     di              ; error might be slightly different.

L1:
;       mov     di,offset array ; di=array[0] from above & below
;       sub     cx,cx           ; remainder=0, zero from below,
                                ; from above doesn't matter
L2:
                ; dx:ax = dividend      ; cx = remainder
                ; bp = denom            ; bx = terms
        mov     dx,cx   ;dividend+= (remainder <<16)
        mov     ax,[di]         ; ax = array[i]
        div     bp              ; dividend = dividend/denom
        stosw                   ; array[i] = dividend/denom
                                ; and goes to next array[i] element
        mov     cx,dx           ; remainder = dividend mod denom
        cmp     di,offset array+(syze*2) ; bx increases by 2 per array[i]
        jb      L2

L3:             ; dx:ax = dividend      ; cx = remainder
                ; bp = denom            ; di = terms
        dec     di
        dec     di              ; point to beginning of array[i]
        mov     ax,[di]         ; array[i] -> ax
        mul     bx              ; dx:ax dividend =  terms*array[i]
        add     ax,cx           ;       dividend += remainder
        adc     dx,+00
        mov     [di],ax         ; array[i] = dividend / 65536
                                ;  same as = dividend & 0x0000ffff;
        mov     cx,dx           ; remainder = dividend mod 65536
                                ;   same as = dividend >> 16;
        cmp     di,si
        ja      L3

;------------------------------------
; this section displays a progress meter
; since program can get slow with high syze value.
; Is safe to alter the AX value when branching up to L1.
;       test    bl,0ffh ; prints a "*" every 256 loops
;       jnz     past
;       mov     al,'*'
;       int     29h     ; this section adds 9 bytes to the size.
past:
;------------------------------------
        add     word ptr[si],+02        ;array[0]+=2
        dec     bp
        dec     bp      ;denom -=2
        dec     bx      ;terms--
        jnz     L1
        mov     bp,syze ; use "syze-1" to avoid printing error
                        ; in last word.
        mov     cl,4    ; display in base 16 hexadecimal
Num1:
        lodsw
        mov     bl,4
Num2:
        ; Note: you can save 2 byte by changing this routine to
        ; generate the digit with code valid only for 286's or
        ; better.  replace the "rol ax,cl" with "rol ax,4",
        ; which allows using CX as the loop instead of
        ; decrementing through BX.
        rol     ax,cl
        push    ax
        and     al,0fh
        cmp     al,0ah
        sbb     al,69h
        das
;       or      al,20h  ; add this "or al,20h" for lowercase output.
        int     29h     ; if you need to be able to redirect
                        ; the data to disk, rem out this "int 29h"
                        ; and un-REM the following 3 lines.
;       xchg    dx,ax
;       mov     ah,02
;       int     21h
        pop     ax
        dec     bx
        jnz     Num2

        dec     bp
        jnz     Num1
        ret

; Array:        ; saving space by setting Array[] to same value as syze
                ; see top of program.
end start

                                                  Gem writers: Mark Andreas
                                                         Jason Papadopoulos
                                                   last updated: 1998-06-07
