https://github.com/ncw/gbpi
This calculates the digits of π on a 1989 Nintendo Game Boy, using nothing but its 8-bit Sharp LR35902 CPU and a hand-written spigot algorithm in Z80-ish assembly.
https://github.com/ncw/gbpi
gameboy pi
Last synced: 6 days ago
JSON representation
This calculates the digits of π on a 1989 Nintendo Game Boy, using nothing but its 8-bit Sharp LR35902 CPU and a hand-written spigot algorithm in Z80-ish assembly.
- Host: GitHub
- URL: https://github.com/ncw/gbpi
- Owner: ncw
- License: mit
- Created: 2026-06-07T14:04:25.000Z (about 2 months ago)
- Default Branch: master
- Last Pushed: 2026-06-07T14:04:46.000Z (about 2 months ago)
- Last Synced: 2026-07-11T21:00:07.515Z (19 days ago)
- Topics: gameboy, pi
- Language: Assembly
- Homepage:
- Size: 39.1 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# gbpi - Pi on the Game Boy
This calculates the digits of π on a 1989 Nintendo Game Boy, using nothing but
its 8-bit Sharp LR35902 CPU and a hand-written spigot algorithm in Z80-ish
assembly.

## What it does
`gbpi` computes the decimal digits of π one screenful at a time and prints them
to the Game Boy's LCD as they are produced. It uses the **Rabinowitz and Wagon
spigot algorithm**, which emits digits of π from the start onwards using only
integer arithmetic on a large array. No floating point is needed, and there is
no need for high precision arithmetic.
The CPU has no multiply or divide instructions, so the project includes
hand-written 16×16→32 bit multiply (`mmul`), 32÷16 bit divide (`mdiv`, `mdiv32`)
and "multiply by 10000" (`mul10k`) routines, each tuned cycle by cycle for
speed.
A ~100 Hz timer interrupt counts hundredths of a second so the ROM can report
how long the calculation took once it finishes.
## The two versions
There are two pre-built ROMs in the repository, charting the optimisation work:
| ROM | Algorithm | Speed (1000 digits) |
| --- | --- | --- |
| [gbpi-v1-digit-at-a-time.gb](./gbpi-v1-digit-at-a-time.gb) | base 10 - one decimal digit per array element | ~1400 s |
| [gbpi-v2-4-digits-at-a-time.gb](./gbpi-v2-4-digits-at-a-time.gb) | base 10000 - four decimal digits per array element | **350.43 s** |
Switching from base 10 to base 10000 packs four decimal digits into every array
element, giving roughly a 4× speed-up by processing four digits per pass of the
inner multiply/divide loop. The current source builds the base-10000 version.
The number of digits and the working array length are set at the top of
[`src/main/pi.asm`](src/main/pi.asm):
```asm
DEF DIGITS = 1000
DEF LENGTH = 3330
```
## Running it
The quickest way to try it is to load one of the pre-built `.gb` files into a
Game Boy emulator, for example [Emulicious](https://emulicious.net/),
[SameBoy](https://sameboy.github.io/) or [BGB](https://bgb.bircd.org/):
```sh
emulicious gbpi-v2-4-digits-at-a-time.gb
```
The ROMs should also run on real hardware via a flash cart.
## Building from source
The project is built with [RGBDS](https://rgbds.gbdev.io/), the Game Boy
assembler toolchain (`rgbasm`, `rgblink`, `rgbfix`, `rgbgfx`). Install RGBDS,
then run:
```sh
make # build dist/gbpi.gb
make run # build and launch in Emulicious
make clean # remove generated files
```
The build assembles every `.asm` file under `src/main/`, converts the font PNG
under `src/resources/backgrounds/` into Game Boy tile data with `rgbgfx`, links
the objects and runs `rgbfix` to patch the ROM header.
## Source layout
| File | Purpose |
| --- | --- |
| `src/main/main.asm` | Entry point, interrupt vectors, intro text and the ~100 Hz timer |
| `src/main/pi.asm` | The π spigot algorithm plus the multiply/divide maths routines |
| `src/main/display.asm` | LCD control and background clearing |
| `src/main/text.asm` | Text output, scrolling and the font tile loader |
| `src/main/memory.asm` | A small memory-copy helper |
| `src/main/charmap.inc` | Maps ASCII characters to font tile indices |
| `src/main/hardware.inc` | Game Boy hardware register definitions |
| `src/resources/backgrounds/bbc-mode1.png` | The font - a BBC Micro MODE 1 typeface, for old times' sake |
## How the algorithm works
The algorithm comes from the paper:
> Rabinowitz, Stanley; Wagon, Stan (1995). "A Spigot Algorithm for the Digits
> of Pi". *American Mathematical Monthly*. **102** (3): 195–203.
> [doi:10.2307/2975006](https://doi.org/10.2307/2975006).
> [JSTOR 2975006](https://www.jstor.org/stable/2975006).
### π as a number in a strange base
The starting point is this series which can easily be derived from the well known
[Leibniz formula for π](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80):
```
π = 2 + 1/3·(2 + 2/5·(2 + 3/7·(2 + 4/9·(… ))))
```
Read this as a positional number whose "digits" are all `2`, but where each
position has a *different* base. In ordinary base 10 the place values are
1, 1/10, 1/100, … and every position uses the radix 10. Here the place values
are the nested products of the fractions `i/(2i+1)`, so position `i` effectively
uses the **mixed radix** `(2i+1)/i`. In that mixed-radix system π is simply
```
π = (2 ; 2, 2, 2, 2, …)
```
The whole algorithm is therefore a **base conversion**: it takes π written in
this `(2i+1)/i` mixed radix and converts it into ordinary base 10 (or, in the
fast version, base 10000), one block of output digits at a time.
### Converting one block of digits
Converting a number to base *B* is the familiar "multiply by *B* and take the
overflow" trick. To get the next base-10 digit of a fraction you multiply by 10;
the integer part that pops out above the point is the next digit, and you keep
the fractional remainder and repeat. This program does exactly that, but to the
*entire mixed-radix number at once*:
1. **Multiply the whole array by the base** (`B = 10`, or `10000` for four
digits at a time). Each element of the `digit` array holds the value for one
mixed-radix position; all of them are multiplied by `B`.
2. **Normalise from the low end up**, position by position. At position `i` the
value can no longer be ≥ its radix, so it is divided by the denominator
`2·i + 1`: the remainder stays in place (it becomes the new digit `2` for the
next round, scaled), and the quotient is multiplied by the numerator `i` and
**carried** down into position `i-1`. This is just "carrying" during base
conversion, except each position carries with its own weird radix.
3. **The carry out of the front** of the array is the integer part produced by
multiplying by `B` - i.e. the next base-10 (or base-10000) block of π. It is
printed to the screen.
In the source this is the `.outer` loop in [`src/main/pi.asm`](src/main/pi.asm):
the `digit` array is initialised to `2`, `mmul`/`mul10k` do the "multiply by the
base" step, and `mdiv`/`mdiv32` do the divide-and-carry normalisation that walks
the array. The leading carry is assembled in `prev`/`carry` and emitted with
`printdigit` / `print4digits`.
A subtlety from the paper: the last digit of a block can occasionally need to be
revised by a carry from the *next* block (the classic "`…999999`" or "`…000000`"
rollover). The code holds the previous block back in `prev` and only commits it
once the following block confirms there was no carry, which is the `.prevloop` /
`.prevok` logic.
Finally, later digits of π need progressively less precision, so the working
array length is trimmed a little on every pass (about 13 bits ≈ 4 decimal digits
per pass), shrinking the work as the calculation proceeds - a small extra
speed-up beyond the base-10000 change.
## License
[MIT](LICENSE) © Nick Craig-Wood