| # ULP Accuracy Measurement Tool | |
| Standalone tool for measuring the accuracy of Eigen's vectorized math functions | |
| in units of ULP (Unit in the Last Place). Compares Eigen's SIMD implementations | |
| against either MPFR (128-bit high-precision reference) or the standard C++ math | |
| library. | |
| ## Building | |
| From the Eigen build directory: | |
| ```bash | |
| cd build | |
| cmake .. | |
| cmake --build . --target ulp_accuracy | |
| ``` | |
| If MPFR and GMP are installed, the build automatically enables MPFR support | |
| (`EIGEN_HAS_MPFR`). Without them, only `--ref=std` is available. | |
| ### Installing MPFR (Debian/Ubuntu) | |
| ```bash | |
| sudo apt install libmpfr-dev libgmp-dev | |
| ``` | |
| ## Usage | |
| ``` | |
| ./test/ulp_accuracy [options] | |
| Options: | |
| --func=NAME Function to test (required unless --list) | |
| --lo=VAL Start of range (default: -inf) | |
| --hi=VAL End of range (default: +inf) | |
| --double Test double precision (default: float) | |
| --step=EPS Sampling step: advance by (1+EPS)*nextafter(x) | |
| (default: 0 = exhaustive; useful for double, e.g. 1e-6) | |
| --threads=N Number of threads (default: all cores) | |
| --batch=N Batch size for Eigen eval (default: 4096) | |
| --ref=MODE Reference: 'std' (default) or 'mpfr' | |
| --hist_width=N Histogram half-width in ULPs (default: 10) | |
| --list List available functions | |
| ``` | |
| ## Examples | |
| List all supported functions: | |
| ```bash | |
| ./test/ulp_accuracy --list | |
| ``` | |
| Exhaustive float test of sin against std (tests all ~4.28 billion finite floats): | |
| ```bash | |
| ./test/ulp_accuracy --func=sin | |
| ``` | |
| Float test against MPFR (more accurate reference, but slower): | |
| ```bash | |
| ./test/ulp_accuracy --func=sin --ref=mpfr | |
| ``` | |
| Double precision test with geometric sampling (exhaustive is impractical for double): | |
| ```bash | |
| ./test/ulp_accuracy --func=exp --double --step=1e-6 | |
| ``` | |
| Test a specific range: | |
| ```bash | |
| ./test/ulp_accuracy --func=sin --lo=0 --hi=6.2832 | |
| ``` | |
| ## Output | |
| The tool prints: | |
| - **Test configuration**: function, range, reference mode, thread count | |
| - **Max |ULP error|**: worst-case absolute ULP error with the offending input value | |
| - **Mean |ULP error|**: average absolute ULP error across all tested values | |
| - **Signed ULP histogram**: distribution of signed errors showing bias direction | |
| Example output: | |
| ``` | |
| Function: sin (float) | |
| Range: [-inf, inf] | |
| Representable values in range: 4278190082 | |
| Reference: MPFR (128-bit) | |
| Threads: 32 | |
| Batch size: 4096 | |
| Results: | |
| Values tested: 4278190081 | |
| Time: 529.04 seconds (8.1 Mvalues/s) | |
| Max |ULP error|: 2 | |
| at x = -1.5413464e+38 (Eigen=-0.482218683, ref=-0.482218742) | |
| Mean |ULP error|: 0.0874 | |
| Signed ULP error histogram [-10, +10]: | |
| -2 : 51988 ( 0.001%) | |
| -1 : 186805349 ( 4.366%) | |
| 0 : 3904475407 ( 91.265%) | |
| 1 : 186805349 ( 4.366%) | |
| 2 : 51988 ( 0.001%) | |
| ``` | |
| ## How it works | |
| 1. **Range splitting**: The input range is divided evenly across threads by | |
| splitting the linear ULP space. | |
| 2. **Batched evaluation**: Each thread fills batches of input values, evaluates | |
| them through Eigen's vectorized path (using `Eigen::Array` operations), and | |
| computes reference values one at a time. | |
| 3. **ULP computation**: IEEE 754 bit patterns are mapped to a linear integer | |
| scale where adjacent representable values are adjacent integers. The signed | |
| ULP error is the difference between Eigen's result and the reference on this | |
| scale. Special cases (NaN, infinity mismatches) report infinite error. | |
| 4. **Result reduction**: Per-thread statistics (max error, mean error, histogram) | |
| are merged after all threads complete. | |
| ## Supported functions | |
| | Category | Functions | | |
| |----------|-----------| | |
| | Trigonometric | sin, cos, tan, asin, acos, atan | | |
| | Hyperbolic | sinh, cosh, tanh, asinh, acosh, atanh | | |
| | Exponential/Log | exp, exp2, expm1, log, log1p, log10, log2 | | |
| | Error/Gamma | erf, erfc, lgamma | | |
| | Other | logistic, sqrt, cbrt, rsqrt | | |
| ## File organization | |
| - `ulp_accuracy.cpp` — Main tool: ULP computation, worker threads, CLI, result printing | |
| - `mpfr_reference.h` — MPFR reference function wrappers and scalar conversion helpers | |
| ## Performance tips | |
| - Float exhaustive sweeps test ~4.28 billion values. With `--ref=std` this takes | |
| ~50 seconds per function; with `--ref=mpfr` it takes ~500 seconds (10x slower). | |
| - For double precision, exhaustive testing is impractical. Use `--step=1e-6` to | |
| sample ~2.88 billion values geometrically. | |
| - Thread count defaults to all available cores. MPFR is the bottleneck (single | |
| MPFR call per value per thread), so more cores help significantly. |