arm64 macOS has no binary128 type, so f128 could not be built there.
With SOFTFLOAT=1 (the default on macOS only) the f128 scalar is
SoftFloat 3e's float128_t:
- leaf_f128_soft.h implements the *_generic API and snap() on SoftFloat
calls; native builds keep their code unchanged.
- The ~30 raw scalar operations outside the leaf primitives go through
LEAF_S_* macros, plain operators on native builds.
- prob_t is double in this mode (measurement, --norm-csv).
- to_str prints "%.30g" exactly as glibc's strfromf128 does (integer
conversion with round-half-even; equal on 2M random values).
- make init-softfloat fetches and builds SoftFloat at a pinned commit;
the link fails early with that hint if it is missing.
On x86_64 Linux, SoftFloat f128 against native __float128 over 613
benchmark runs: every classic run bit-identical; the 39 differing
--symbolic runs differ only through (1/sqrt 2)^k, which SoftFloat builds
exactly while native uses powf128(fl(1/sqrt 2), k), off in the last
bits for 399 of 401 exponents. With the exact construction on native
too, all 39 match. SoftFloat runs at ~0.7x native time (geomean, runs
over 0.5 s). make test and test-grover-f128 pass with SOFTFLOAT=1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q87VyzNBV8gtH2ntMeKoM
Draft: exploration. Stacked on #46. This makes f128 build on arm64 macOS, which has no 128-bit floating-point type at all, by using Berkeley SoftFloat 3e. SoftFloat setup and install instructions apply only to the Mac; Linux builds neither need it nor change.
How it's switched on
SOFTFLOAT=1selects it. That's the default on macOS only, somakethere builds f128 on SoftFloat.make init-softfloatfetches SoftFloat at a pinned commit and builds it, the same waymake init-sylvanworks. If it's missing, the link stops early with that hint.Code changes
leaf_f128_soft.h(new, ~120 lines)float128_t. The header implements the same*_genericAPI andsnap()on SoftFloat calls. Native builds keep their existing code unchanged.leaf_reim_double.c,norm_track.c,mtbdd.c, tests)+ - * ==, so the few raw scalar operations outside the leaf primitives go throughLEAF_S_*macros. On native builds these are plain operators.prob_tdoublein SoftFloat mode, which affects measurement probabilities and--norm-csv.to_strprints%.30gexactly as glibc'sstrfromf128does, via exact integer conversion with round-half-even. It matched on 2 million random and edge-case values (subnormals, extremes).Verified on x86_64 Linux, against native
__float128no-measure, plus--symbolicforLP-*), every classic run gives a bit-identicalres.dot.--symbolicruns that differ do so for one reason: (1/√2)^k.powf128(fl(1/√2), k), a power of the rounded constant. That's off in the last bits for 399 of 401 exponents; even k = −200 gives 2^100 + 0.017.powf128path is the less accurate one, and switching it to the exact construction is worth a separate small PR.make testandmake test-grover-f128pass withSOFTFLOAT=1. The native suites still pass after the macro changes.CI (this PR)
make init-softfloatand is expected to pass every f128 step.Ubuntu 24.04, SoftFloatjob: runs the same suites on SoftFloat f128 where native f128 also runs, so a regression on either side shows up next to the other.Open questions
--symbolicoutputs.prob_tasdouble: whether that's acceptable on macOS, or worth carrying through asfloat128_t(about 17 more call sites).🤖 Generated with Claude Code
https://claude.ai/code/session_012q87VyzNBV8gtH2ntMeKoM