# Library Math32

> math32 IEEE single float library: multi-CPU layout (asm/z80, asm/8085, asm/8080, asm/gbz80), products math32*.lib including ez80_z80 / gbz80, rounding policy, div=restoring / inv=NR, force rebuild. Use when editing libsrc/math/float/math32 or A/B float divide/mul.

- Skill: `z88dk/library-math32` (Agent Skill)
- Install (CLI): `npx skillmds@latest add z88dk/library-math32`
- Raw SKILL.md: https://api.skillmd.com/api/skills/z88dk/library-math32/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: z88dk (https://skillmd.com/u/z88dk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/z88dk/library-math32

---


# Library — math32

Home: `libsrc/math/float/math32/`. Docs of record: `libsrc/math/float/math32/readme.md`.
Link via **`--math32`** (`-lmath32@{ZCC_LIBCPU}`).

## 0b. Math32 multi-CPU float library (layout + policy)

Home: `libsrc/math/float/math32/`. Products: `math32.lib` (plain z80) plus
`math32_{z80n,z180,ez80_z80,r2ka,kc160,8085,8080,gbz80,…}.lib`. Link via
**`--math32`** (`-lmath32@{ZCC_LIBCPU}` — e.g. 8085 → `math32_8085`, 8080 →
`math32_8080`, gbz80 → `math32_gbz80`, ez80_z80 → `math32_ez80_z80`; no
separate `--math32_8085` flag).

### Layout

| Tree | Role |
|------|------|
| `asm/z80/` | Z80-family cores; shared by z80n/z180/ez80_z80/r2ka/… when the lst points here |
| `asm/8085/` | Stack-only 8085 cores (no EXX / IX / IY); extended opcodes + synthetics. After `rl de`, test exp with `inc d`/`dec d` — RDEL does not write Z |
| `asm/8080/` | Stack-only 8080 cores (original ISA; no 8085 extras). `ld hl,sp+n`; park HL |
| `asm/gbz80/` | Stack-only Game Boy cores (`ld hl,sp+*`, `bit 7` leading-one; no cheap `ex`) |
| `c/z80/`, `c/8085/`, `c/8080/`, `c/gbz80/` | Higher functions (C → precompiled asm); 8080/8085/gbz80 higher via **sccz80 only** |
| `newlibfiles_*.lst` | Which modules land in each product (`newlibfiles_ez80_z80.lst`, `newlibfiles_gbz80.lst`, …) |

**CPU-specific** = same *operation* name, different ISA file (same one-op-per-file
map as §0). Do not invent a second taxonomy for 8085 / gbz80.

### eZ80 Z80-mode product (mlt)

eZ80 Z80-mode has the same `mlt` encodings as Z180 (`ED 4C/5C/6C/7C`).
`math32_ez80_z80.lib` is built from **`newlibfiles_ez80_z80.lst`**, which
selects the Z180 mantissa helpers (`f32_z180_mulu_*` / `f32_z180_sqr_*`).

Those helpers are gated:

```asm
IF __CPU_Z180__ | __CPU_EZ80__ | __CPU_EZ80_Z80__
```

| Assemble as | Define set |
|-------------|------------|
| `-mz180` | `__CPU_Z180__` |
| `-mez80` (ADL) | `__CPU_EZ80__` |
| `-mez80_z80` | **`__CPU_EZ80_Z80__`** |

Do **not** build eZ80 from `@newlibfiles_z80.lst` alone: the plain-Z80 helpers
are `IF __CPU_Z80__` only and assemble **empty** on `-mez80_z80`. Suite gate:
`test/suites/math` → `test_math32_ez80_z80.bin`.

### Rounding policy (do not mix casually)

| Class | Policy (current math32) |
|-------|-------------------------|
| **mul / sqr / div / poly / sqrt pack** | **IEEE RNE** on residual below the kept mantissa |
| **add / sub** | **Digi jam-sticky**: lost align/overflow bits → OR **1** into mant LSB; pack has no RNE residual |

Long add chains (e.g. n-body energy) are sensitive to add rounding: jam keeps
second-energy error ~1e−6 class; full RNE-on-add has been measured to worsen E1
(~5e−5 class) at higher cost. Prefer matching z80 and 8085 **policy** even when
engines are not bit-identical.

### Divide / inverse (current policy)

| Op | Algorithm | Notes |
|----|-----------|--------|
| **`div` / `m32_fsdiv`** | **Restoring** 24-bit mantissa | z80 + **8085** + **8080** + **gbz80** cores; z80n/z180/ez80_z80 share z80 `asm/z80/f32_fsdiv.asm` |
| **`inv` / `m32_fsinv`** | Newton–Raphson | Slower than `div` for a reciprocal. HW mul helps inv only |
| **`invsqrt` / `m32_fsinvsqrt`** | Quake seed + 3× NR | **Still the fastest** `1/sqrt`. Do not replace with `1.0/sqrt` |
| math16 | Same split: restoring `asm_f16_div`, NR `asm_f16_inv` | |

Do **not** reintroduce NR trampoline `fsdiv` = `fsinv`+`fsmul` without A/B proof.
Docs: `math32/readme.md` § div/inv; measurement: **z88dk-tooling** § A/B.

In **C higher functions** (`c/m32_*.c`):

| Need | Write | Do not write |
|------|--------|----------------|
| Reciprocal `1/n` | `1.0/x` (restoring `div`) | `m32_invf(x)` |
| Inverse square root | `m32_invsqrtf(x)` | `1.0/m32_sqrtf(x)` |

`pow(x, -1)` / `sinh` / `cosh` / `tanh` / `atan` recip / `asinh` / `acosh` use divide. `pow(x, -0.5)` keeps `m32_invsqrtf`.

IEEE bit punning: use **`union float_long`** from `c/m32_math.h` (`float f; int32_t l`). Do not invent a local `uint32_t` union. Assign `NAN_*` / `INFINITY_*` as `(int32_t)NAN_NEG_F32` so zsdcc does not warn on unsigned→signed.

### Micro-opt patterns that port

| Pattern | Idea | Notes |
|---------|------|--------|
| Implicit-1 CF | `ld a,255` / `add a,h` → CF=(exp≠0) instead of `or a` / `jr Z` / `scf` | ~8–9 T per unpack; works on Z80 and 8085 |
| Z80 non-callee stack load | 3×`pop` + 3×`push` vs `hl=sp+2` walk | ~8 T; 8085 stack-slot paths already different |
| Hot tiny helpers | Inline 2–4 insn jam sticky at call sites | Saves call/ret; size often net-neutral or smaller |
| Unused stack pad | Drop frame slots only after proving no SP offsets still use them | Remeasure; fix every `sp+N` comment |

Callee linkage: float helpers that pop a return address + stack args must be
**`call`’d**, not bare **`jp`** (floor/ceil class bugs). Keep that rule when
editing pack/add glue.

### sccz80 + newlib: plain names vs `*_fastcall` (issue #3061)

`math32.lib` products are assembled with **`-D__CLASSIC`**. Under that flag,
`lm32/c/sccz80/{sin,sqrt,log,…}.asm` expose **stack-arg bridges** for plain
names; the **DEHL** entry is `*_fastcall` (`defc sin_fastcall = _m32_sinf`).

| Consumer | What must happen |
|----------|------------------|
| **Classic** `include/math/math_math32.h` | `#define sin(x) sin_fastcall(x)` (and peers) — already correct |
| **Newlib** sccz80 + `--math32` | Same remaps under `__MATH_MATH32` in **`include/_DEVELOPMENT/proto/math.h`**, then `make -C include/_DEVELOPMENT common/math.h` |
| **SDCC** | Single-arg `__DPROTO` already emits `#define sin(a) sin_fastcall(a)` — OK without the sccz80 block |
| **math48** default newlib | Plain `sin` is true DEHL (`cm48_sccz80_sin`) — do **not** force math32 remaps off math48 |

Without the newlib remaps, sccz80 marks plain `sin` as `__z88dk_fastcall` and
emits `call sin` with DEHL, but the linked object is the **stack bridge** (ignores
DEHL). Symptom: hotspots show **zero** `m32_fsinv` / `m32_fsinvsqrt` /
`m32_fsmul32x32` entry hits, TIMER “too fast” (e.g. Whetstone ~15 KWIPS vs ~11),
wrong numerics. Map proof of a healthy build: `sin_fastcall` / `sqrt_fastcall`
present; app `.asm` shows `callsin_fastcall` not `callsin`.

**Do not** “fair up” classic vs newlib by adding bench-only `invsqrt()` optims
to one side only (n-body). Align source (`1.0/sqrt` vs half `invsqrtf16`) and
remeasure both products after header fixes.

### Higher-function C regen (`c/Makefile`)

Z80 higher funcs: `make -C libsrc/math/float/math32/c` → `c/z80/*.asm` (SDCC).
8085: `make -C …/c 8085` → `c/8085/*.asm` (sccz80 only).
8080: `make -C …/c 8080` → `c/8080/*.asm` (sccz80 only).
gbz80: `make -C …/c gbz80` → `c/gbz80/*.asm` (sccz80 only). **`make clean`** must
only remove C-derived objects — never wipe hand-written peers in the same dir
(math16: keep `cm16_sccz80_*.asm` under `c/8085/`).

**Measure / rebuild / wiki:** **`methodology-measure`**
(§ math32 rebuild, benches, classic/newlib A/B, wiki bold rules). Docs of record:
`libsrc/math/float/math32/readme.md`, `support/benchmarks/*/readme.txt`.

---

## Force rebuild (multi-CPU)

### Math32 / multi-CPU float libs (force rebuild)

Sources: `libsrc/math/float/math32/` (per-CPU under `asm/z80/`, `asm/8085/`, …).
Shared Z80-family add lives in `asm/z80/d32_fsadd.asm` and is assembled into
**each** of `math32.lib`, `math32_z80n.lib`, `math32_z180.lib`,
`math32_ez80_z80.lib`, `math32_r2ka.lib`, `math32_kc160.lib`, … Changing that
file requires **rebuilding every product that lists it**, not only
`math32_8085.lib`.

```bash
cd libsrc/math/float/math32
# force one object + relink (example: 8085 add)
rm -f obj/8085/math/float/math32/asm/8085/f32_fsadd.o ../../../math32_8085.lib
z88dk-z80asm -d -I"$ZCCCFG/.." -O=obj/8085/x/x/x -I.. -m8085 -D__CLASSIC \
  @newlibfiles_8085.lst
TYPE=8085 z88dk-z80asm -d -I"$ZCCCFG/.." -I.. -m8085 \
  -x../../../math32_8085 @math32.lst
cp -f ../../../math32_8085.lib ../../../lib/clibs/   # or: make -C libsrc install

# Z80-family products that share asm/z80/d32_fsadd.asm (repeat per CPU)
for cpu in z80 z80n z180 ez80_z80 r2ka kc160; do
  case $cpu in
    z80) lst=newlibfiles_z80.lst; lib=math32; masm=z80 ;;
    ez80_z80) lst=newlibfiles_ez80_z80.lst; lib=math32_ez80_z80; masm=ez80_z80 ;;
    *) lst=newlibfiles_${cpu}.lst; lib=math32_$cpu; masm=$cpu ;;
  esac
  rm -f obj/$cpu/math/float/math32/asm/z80/d32_fsadd.o ../../../$lib.lib
  z88dk-z80asm -d -I"$ZCCCFG/.." -O=obj/$cpu/x/x/x -I.. -m$masm -D__CLASSIC @$lst
  TYPE=$cpu z88dk-z80asm -d -I"$ZCCCFG/.." -I.. -m$masm -x../../../$lib @math32.lst
  cp -f ../../../$lib.lib ../../../lib/clibs/
done
```

Or: `make -C libsrc/math/float/math32` then install all `math32*.lib` into
`lib/clibs/`. After install, **delete** suite/bench `.bin`/`.map` before remeasure.

Prove the object is current: `z88dk-z80nm lib/clibs/math32_8085.lib | rg 'f32_fsadd|ay16_njam'`.
For eZ80: `z88dk-z80nm lib/clibs/math32_ez80_z80.lib | rg 'm32_mulu_32h|f32_z180'`.

---

## Related

- Half float: `library-math16`
- Measure / A/B: `methodology-measure`
- Newlib headers (math remaps): `library-newlib` · edit **proto** then regenerate
- 8085 cores: `cpu-8085` · 8080: `cpu-8080` · gbz80: `cpu-gbz80`
- Z180 / eZ80 Z80-mode `mlt`: `cpu-z180`
- Issue class: z88dk **#3061** (classic vs newlib Whetstone)
- Suite: `test/suites/math` (`test_math32*.bin`, including `test_math32_ez80_z80.bin`)

