Artifact
Cent-Exact by Construction: An Empirical Study of an Integer Ledger for Multi-Payer Expense Splitting
- README.md
- bench/bin/e1_bias_by_id.dart
- bench/bin/e1_float_drift.dart
- bench/bin/e1_summarise.dart
- bench/bin/e2_simplifier.dart
- bench/bin/e3_routed.dart
- bench/bin/e4_determinism.dart
- bench/bin/e4_digest.dart
- bench/bin/e5_runtime.dart
- bench/bin/e6_parse_receipts.dart
- bench/figures.py
- bench/lib/common.dart
- bench/pubspec.yaml
- results/e1_bias_by_id.json
- results/e1_float_drift.csv
- results/e1_summary.json
- results/e2_simplifier.csv
- results/e2_summary.json
- results/e3_routed.csv
- results/e3_summary.json
- results/e4_summary.json
- results/e5_runtime.json
- results/e6_summary.json
- SNAPSHOT_SHA256SUMS
# Artifact: Cent-Exact by Construction
This folder holds the benchmarks, raw results and figure script behind the technical report
"Cent-Exact by Construction: An Empirical Study of an Integer Ledger for Multi-Payer Expense Splitting"
(Yousof Selim, November 2026).
> **Source availability.** The package under test, `bupples_ledger` 0.1.0, is part of the private Bupples
> codebase and is **not** included here. What is public: the benchmark code in `bench/`, every raw result
> in `results/`, the figure script, and `SNAPSHOT_SHA256SUMS`, which records the SHA-256 of each library
> and test file that was measured. Every number and figure in the report can be recomputed from `results/`
> alone. Rerunning the experiments needs the package source, so a reviewer with access can rerun them and
> check the hashes; anyone else can audit the benchmark code and the results but cannot rerun it.
## Contents
| Path | What it is |
| --- | --- |
| `SNAPSHOT_SHA256SUMS` | SHA-256 of every file in `lib/` and `test/` of the measured `bupples_ledger` 0.1.0 snapshot. The source itself is private and not included. |
| `bench/` | The experiment package (`cent_exact_bench`). It expects the private package at `../bupples_ledger_snapshot` (a path dependency) and has no other dependencies. |
| `bench/lib/common.dart` | Seeded generators, the naive IEEE-754 double reference implementation, the exact rational reference, two bench-only remainder rules, and the statistics helpers (Student t and Wilson intervals). |
| `bench/bin/e1_*.dart` | E1: floating-point drift, conservation and allocation bias. |
| `bench/bin/e2_simplifier.dart` | E2: transfers from the greedy simplifier and the direct plan, compared with the exact minimum (dynamic programming over subsets). |
| `bench/bin/e3_routed.dart` | E3: paying a simplified plan under five recording strategies, plus a tamper campaign against the covered-edge validator. |
| `bench/bin/e4_*.dart` | E4: order independence under shuffling, and a VM versus JavaScript output digest. |
| `bench/bin/e5_runtime.dart` | E5: microbenchmarks (CPU time through `getrusage`, so macOS only as written). |
| `bench/bin/e6_parse_receipts.dart` | E6: exhaustive parsing sweep and receipt co-claim conservation. |
| `bench/figures.py` | Draws the five SVG figures from `results/`. |
| `results/` | The raw results used in the report: one CSV row per ledger or instance, plus JSON summaries. |
## Environment used for the reported run
- Date: 1–2 November 2026 (the runs finished on 2 November 2026, local time UTC+8).
- Dart SDK 3.12.1 (stable) (Tue May 26 2026) on "macos_arm64", from Flutter's bundled SDK (`/Users/yousofselim/flutter/bin/dart`).
- macOS 27.0.1 (26A434) on an Apple M1 Pro with 16 GB RAM. `Platform.numberOfProcessors` reported 8.
- The machine was shared with other heavy jobs during the run. The load average was about 150 to 180, as recorded in `results/e5_runtime.json`. This affects the E5 timings only; every other experiment produces the same output regardless of load.
- Python 3.14.2 with matplotlib 3.10.7 (for figures only).
## Reproducing
Recomputing the report's numbers needs only `results/` (and Python with matplotlib for the figures). Rerunning the
experiments needs the private package source placed at `bupples_ledger_snapshot/` next to `bench/`; check it
with `cd bupples_ledger_snapshot && shasum -a 256 -c ../SNAPSHOT_SHA256SUMS` first.
```sh
cd bupples_ledger_snapshot # private; see above
dart pub get
dart test # 153 tests, all passing on 2026-11-01
cd ../bench
dart pub get
export RESULTS_DIR=$PWD/../results-repro
# E1 (about 11 minutes per 10 seeds on the machine above; seed ranges can run in parallel)
dart compile exe bin/e1_float_drift.dart -o build/e1
build/e1 0 10 & build/e1 10 20 & build/e1 20 30 & wait
dart run bin/e1_summarise.dart # merges into e1_float_drift.csv + e1_summary.json
dart run bin/e1_bias_by_id.dart
# E2, E3, E4, E6 (each a few minutes when AOT-compiled)
dart compile exe bin/e2_simplifier.dart -o build/e2 && build/e2
dart compile exe bin/e3_routed.dart -o build/e3 && build/e3
dart compile exe bin/e4_determinism.dart -o build/e4 && build/e4
dart compile exe bin/e6_parse_receipts.dart -o build/e6 && build/e6
# E4 cross-platform digest: the two lines must match
dart run bin/e4_determinism.dart digest
dart compile js -O2 bin/e4_digest.dart -o build/e4_digest.js && node build/e4_digest.js
# reported: "digest 132897213 over 1375426 chars" on both (Node.js 26.0.0)
# E5 (timings vary with the machine and its load)
dart compile exe bin/e5_runtime.dart -o build/e5 && build/e5
# Figures
python3 -m venv .venv && .venv/bin/pip install matplotlib==3.10.7
.venv/bin/python figures.py $RESULTS_DIR figures/
```
## Seeds
Every random input comes from `dart:math` `Random(seed)`. With the same Dart SDK, the output is identical bit for bit. Each JSON summary records its seed formula:
| Experiment | Seed |
| --- | --- |
| E1 | `Random(s*1000003 + n*1009 + m*7 + e)` for s in 0..29, n in {2,5,10,20}, m in {10,100,1000,10000}, e in {0,2,3} |
| E1 bias by id | `Random(1)` |
| E2 | `Random(7000000 + genIndex*100000 + n*5000 + t)`, t in 0..1999 |
| E3 | `Random(8000000 + genIndex*100000 + n*5000 + t)`, t in 0..1999; tamper `Random(9100000 + t)`, t in 0..19999 |
| E4 | `Random(6000000 + i)`, i in 0..4999; shuffles `Random(seed ^ 0x5bd1e995)` |
| E5 | `Random(424242 + n*100 + m)` |
| E6 | receipts `Random(5000000 + t)`; parsing is exhaustive |
The E6 summary and the E4 digest were regenerated from a clean copy of this folder (with the package snapshot in place) on 2026-11-02. Both matched the committed results exactly.
## What the bench adds that is NOT in the package
- `NaiveMode` in `common.dart`: the naive double reference (raw, Kahan-compensated, and round-each-share).
- `RemainderRule` in `common.dart`: largest remainder with ties by id, and largest remainder with ties rotated by a per-expense hash. They are used only to measure allocation bias.
- The "guarded" strategy and the "strict" covered-edge check in `e3_routed.dart`. These are proposals that were evaluated here and are not part of `bupples_ledger` 0.1.0.