80-byte Pi calculatorAssembler/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

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