
Extended-precision floating point math library
Fast · Precise · Lightweight
fltx is a high-performance C++ library for double-double and quad-double arithmetic, with a broad std-style math library and extensive constexpr support throughout.
- Overview: highlights and core types
- Get Started: quick start and installation
- Library Guide: headers, numeric types, math, and IO
- Advanced Features: template dispatch
- Development: building and testing fltx
- Project Details: benchmarks, implementation notes, and license
Features:
- Fixed-size extended-precision scalar types:
bl::fddandbl::fqd - Library-wide constexpr support for math, parsing, static formatting, and conversions
- Familiar
std::style APIs; many call sites can be migrated by simply swappingstd::forbl:: - No required runtime dependencies
- Standard-library integration for streams, manipulators,
std::numeric_limits,std::numbers,std::hash, andstd::format
Accuracy:
- Accuracy validated against Boost.Multiprecision's MPFR backend
- Correct handling of infinities, NaNs and signed zeros (non-fast-math builds)
- Extreme-range arithmetic follows a deliberate range/performance policy
Performance:
- Optimised for runtime and constexpr performance, with predictable build-time and binary-size scaling
- Compatible with fast-math builds
- Internal
bl::fqdexpression fusion reduces intermediate rounding and temporary value materialisation for common arithmetic shapes (optional) - Hardware acceleration where supported, including SSE2 on x86-64, FMA where available, AArch64 NEON, and WebAssembly SIMD128 via Emscripten
- Suitable for lightweight native and WebAssembly builds
- Optional dispatch helper that maps runtime choices to template specialisations
| Type | Representation | Precision |
|---|---|---|
bl::f32 |
Alias for native float |
Native float precision |
bl::f64 |
Alias for native double |
Native double precision |
bl::fdd |
Double-double | 106 bits (~31 significant decimal digits) |
bl::fqd |
Quad-double | 212 bits (~63 significant decimal digits) |
#include <iostream>
#include <iomanip>
#include <fltx.h>
using namespace bl::types;
using namespace bl::literals;
int main()
{
constexpr fqd a = 1_qd / 3_qd;
constexpr fqd b = 2_qd / 3_qd;
constexpr fqd c = a + b;
constexpr fqd d = bl::atan2(a, b);
std::cout
<< std::fixed
<< std::setprecision(std::numeric_limits<fqd>::digits10)
<< "a = " << a << "\n"
<< "b = " << b << "\n"
<< "a + b = " << c << "\n"
<< "atan2(a, b) = " << d << "\n";
}Output:
a = 0.333333333333333333333333333333333333333333333333333333333333333
b = 0.666666666666666666666666666666666666666666666666666666666666667
a + b = 1.000000000000000000000000000000000000000000000000000000000000000
atan2(a, b) = 0.463647609000806116214256231461214402028537054286120263810933089
More examples
| Example | Shows |
|---|---|
example_basic.cpp |
Basic fdd / fqd arithmetic, literals, and output. |
example_constexpr_io.cpp |
Compile-time parsing, formatting, and string conversion. |
example_dispatch.cpp |
Generic code dispatching across fltx precision types. |
example_mandelbrot.cpp |
Higher-precision numeric work in a small visual workload. |
example_consteval_ellipse.cpp |
Consteval geometry using fltx math. |
example_consteval_library_sweep.cpp |
Library-wide constexpr coverage across the supported function groups. |
example_random.cpp |
Random value generation for fltx types. |
example_pow.cpp |
bl::pow / bl::ipow overload behavior, return-type policy, and fast paths. |
example_newton_solver.cpp |
Generic Newton solving across: f64, fdd, fqd. |
example_charconv.cpp |
Buffer-oriented bl::to_chars / bl::from_chars parsing and formatting. |
example_std_integration.cpp |
Standard-library integration with numbers, limits, hashing, comparisons, and optional std::format. |
example_storage_interop.cpp |
Using fdd_s / fqd_s aggregate storage forms at fixed-size API boundaries. |
example_special_values.cpp |
NaN, infinity, signed zero, classification, and layout helpers such as bl::frexp / bl::modf. |
example_polynomial.cpp |
Polynomial evaluation with bl::fma and precision-sensitive cancellation. |
example_consteval_coefficients.cpp |
Compile-time generation of high-precision coefficient tables. |
Add the bitloop registry to vcpkg-configuration.json, pinning the registry commit you want to consume:
{
"registries": [
{
"kind": "git",
"baseline": "5504123246482f0bbb58fd53783ae1b1e3fa88f9",
"repository": "https://github.com/bitloop-dev/bitloop-registry.git",
"packages": ["fltx"]
}
]
}Then add fltx to vcpkg.json:
{
"name": "myapp",
"version": "1.0.0",
"dependencies": [
"fltx"
]
}With vcpkg:
find_package(fltx CONFIG REQUIRED)
target_link_libraries(main PRIVATE fltx::fltx)Or include it directly:
add_subdirectory(/path/to/fltx fltx-build)
target_link_libraries(main PRIVATE fltx::fltx)| Setting | Default | Configured when | Purpose |
|---|---|---|---|
FLTX_ENABLE_FQD_EXPRESSIONS=0|1 |
0 |
Compiling your application | Enable QD expression fusion. Works with an already installed library. |
FLTX_FMA_MODE=AUTO|OFF|ASSUME |
AUTO |
Building fltx | Automatically detect x86 hardware-FMA support, disable hardware FMA, or require FMA support. ASSUME performs no runtime feature check and exports FMA compiler options where required. |
FLTX_SIMD=ON|OFF |
ON |
Building fltx | Enable SIMD implementations available for the target. Emscripten builds require SIMD128 when enabled and propagate -msimd128 to consuming targets. |
The defaults enable supported acceleration. Most applications need no configuration changes. FMA and SIMD settings are recorded in the built package; changing them requires rebuilding fltx. Expression templates are selected by the consuming application.
Normal build-type optimization flags remain controlled by the selected CMake configuration and toolchain.
To opt into fqd expression templates for your target:
target_compile_definitions(main PRIVATE FLTX_ENABLE_FQD_EXPRESSIONS=1)This is a consumer setting, not a FLTX build option; it also works with vcpkg without rebuilding the installed library.
| Header | Provides |
|---|---|
fltx.h |
Umbrella header: Core types, math, I/O, random, hashing and template dispatch helper |
fltx/core.h |
Core numeric types, storage types, arithmetic, conversions, and numeric integration |
fltx/math.h |
Core types plus the constexpr Math API |
fltx/io.h |
Character-conversion functions, string conversion, stream output, literals and std::formatter specializations |
fltx/random.h |
Standard-shaped random engines and distributions |
fltx/hash.h |
std::hash specializations for the extended types |
Individual headers are also available when you want a smaller include surface.
| Header | Provides |
|---|---|
fltx/fdd.hfltx/fqd.h |
Individual extended-precision types, storage types, and core operations |
fltx/f32_math.hfltx/f64_math.hfltx/fdd_math.hfltx/fqd_math.h |
Math APIs for individual floating-point types |
fltx/fdd_io.hfltx/fqd_io.h |
Extended-type string conversion, stream output, and _dd / _qd literals |
fltx/charconv.h |
bl::to_chars, bl::from_chars, bl::parse<T>, bl::parse<T>(text, fallback), and bl::try_parse<T> for f32, f64, fdd, and fqd |
fltx/string.h |
String conversion for all floating-point families, including the supporting result types and formatting options |
fltx/static_string.h |
bl::static_string<N> and the fixed-capacity string aliases returned by bl::to_static_string |
fltx/string_options.h |
bl::precision_info and bl::trailing_zero_policy formatting options |
fltx/aliases.h |
Fundamental aliases such as f32, f64, fdd, and fqd |
fltx/traits.h |
Concepts, type traits, FloatType enum, and bl::to_string(FloatType) |
fltx/format.h |
Optional std::formatter specializations when <format> is available |
fltx/dispatch.h |
Runtime-to-compile-time type dispatch for FloatType and custom enums. |
The main user-facing types are:
bl::f32 // float alias
bl::f64 // double alias
bl::fdd // double-double scalar type
bl::fqd // quad-double scalar type
bl::fdd_s // aggregate storage form
bl::fqd_s // aggregate storage form (no fused-expression support)The aggregate storage forms are useful when an API requires aggregate storage or an eager representation without expression fusion. Each converts directly to and from its corresponding fdd or fqd value type.
Non-mutating arithmetic involving fdd/fqd returns a value type (or an enabled fqd expression); storage-only arithmetic returns storage types.
bl::fdd_s a { 5.0 };
bl::fdd b = 5.0f;
bl::fdd_s c = a + b;
bl::fdd d = c;Common type concepts and traits are available from fltx/traits.h:
// Concepts
bl::fltx_f32<T> // matches bl::f32
bl::fltx_f64<T> // matches bl::f64
bl::fltx_fdd<T> // matches the bl::fdd or bl::fdd_s
bl::fltx_fqd<T> // matches the bl::fqd or bl::fqd_s
bl::fltx_extended_float<T> // matches the fdd or fqd family
bl::fltx_float<T> // matches the f32, f64, fdd, or fqd family
bl::fltx_arithmetic<T> // matches a native arithmetic or extended fltx type
bl::fltx_expression<T> // matches an internal deferred arithmetic result
bl::value_t<T> // canonical scalar value type
bl::storage_t<T> // aggregate storage type
// Boolean variable templates
bl::is_f32_v<T>
bl::is_f64_v<T>
bl::is_fdd_v<T>
bl::is_fqd_v<T>
bl::is_fltx_extended_float_v<T>
bl::is_fltx_float_v<T>
bl::is_arithmetic_v<T>fltx/math.h provides a constexpr interface modeled after the <cmath> API across bl::f32, bl::f64, bl::fdd, and bl::fqd.
This lets generic numeric code switch precision without changing its math calls:
template<class T>
constexpr T radius(T x, T y)
{
return bl::sqrt(x * x + y * y);
}
constexpr bl::f32 a = radius(1.0f, 2.0f);
constexpr bl::f64 b = radius(1.0, 2.0);
constexpr bl::fdd c = radius(1_dd, 2_dd);
constexpr bl::fqd d = radius(1_qd, 2_qd);All listed functions support constant evaluation.
| Category | Functions |
|---|---|
| Arithmetic | abs, fabs, sqr, fma |
| Rounding | floor, ceil, trunc, round, lround, llround, round_decimals, round_significant, roundeven |
| Remainders | fmod, remainder, remquo |
| Min / max / sign | fmin, fmax, fdim, copysign, signbit |
| Selection / interpolation | min, max, minmax, clamp, lerp, midpoint |
| Roots / powers | sqrt, cbrt, hypot, pow, ipow |
| Exp / log | exp, exp2, expm1, log, log2, log10, log1p, logb, ilogb |
| Trigonometry | sin, cos, tan, sincos, asin, acos, atan, atan2 |
| Hyperbolic | sinh, cosh, tanh, asinh, acosh, atanh |
| Special functions | erf, erfc, lgamma, tgamma |
| Classification / comparison | fpclassify, isfinite, isinf, isnan, isnormal, isunordered, isgreater, isgreaterequal, isless, islessequal, islessgreater, iszero |
| Scaling / layout | ldexp, scalbn, scalbln, frexp, modf, nextafter, nexttoward |
For an example of the library-wide constexpr capabilities, see example_consteval_library_sweep.cpp.
The two-argument min, max, and minmax, and the three-argument clamp,
accept mixed numeric types and expressions. Each input is materialized once;
results are owning values (minmax returns a pair), never references or nodes.
Floating inputs follow common_float_type_t; integer-only selection and
midpoint use std::common_type_t without going through floating point
(including its usual signed/unsigned conversion rules). min and max use
< and keep the first argument on ties, unlike the NaN-skipping fmin/fmax.
clamp requires ordered bounds, low <= high.
lerp(a, b, t) interpolates or extrapolates in the common floating type;
midpoint(a, b) avoids overflow in the half-sum and rounds integer ties toward
a. These are numeric helpers, not iterator/range or comparator overloads.
fltx/io.h provides constexpr-capable parsing, formatting, stream output, string conversion, and the _dd / _qd literals:
#include <fltx/io.h>
using namespace bl::types;
using namespace bl::literals;
constexpr fdd PI_dd = 3.14159265358979323846264338327950288_dd;
constexpr fqd PI_qd = 3.1415926535897932384626433832795028841971693993751058209749445923078164_qd;// serialize
std::string s1 = bl::to_string(PI_qd); // string with default precision
std::string s2 = bl::to_string(PI_qd, 16, true); // string with fixed precision
constexpr auto s3 = bl::to_static_string(PI_qd, 10); // static_string with fixed precision// deserialize (fallback)
constexpr fqd value1 = bl::parse<fqd>("__3.1415", 0_qd); // invalid input, use fallback value: 0.0
// deserialize (try/catch)
fqd value2;
try
{
value2 = bl::parse<fqd>("__3.1415");
}
catch (const std::exception& e)
{
std::cout << e.what() << "\n"; // exception: "bl::parse invalid input"
}
// deserialize (error in result.ec)
constexpr auto result = bl::try_parse<fqd>("__3.1415");
if (result)
std::cout << "success: " << result.value;
else
std::cout << "invalid input";Use:
bl::to_static_stringfor compile-time fixed-capacity string output.bl::to_stringwhen you want astd::string.bl::parse<T>for strict whole-string parsing that throws on invalid input.bl::parse<T>(text, fallback)when a fallback value is enough.bl::try_parse<T>for non-throwing parsing with error and consumed-character details.
Stream output supports std::setprecision, std::fixed, std::scientific, std::showpoint, std::showpos, and std::uppercase.
#include <fltx/io.h>
using namespace bl::types;
// serialize
char buffer[128]{};
auto written = bl::to_chars(buffer, buffer + sizeof(buffer), PI_qd, std::chars_format::fixed, 32);
if (written.ec == std::errc{})
std::cout << std::string_view{ buffer, static_cast<std::size_t>(written.ptr - buffer) };
// deserialize
std::string_view text = "3.1415";
fqd parsed{};
auto read = bl::from_chars(text.data(), text.data() + text.size(), parsed);
if (read.ec == std::errc{} && read.ptr == text.data() + text.size())
std::cout << "success: " << parsed;For buffer-oriented code, fltx/charconv.h provides:
bl::to_charswrites into buffer and reports the end pointer plusstd::errc.bl::from_charsparses the leading numeric token and reports where parsing stopped.
Include fltx/format.h when you want std::format support for fdd/fqd, including fqd expressions such as std::format("{}", a * b).
The formatter supports common numeric presentation options such as precision, fixed/scientific/general notation, sign, width, alignment, fill, alternate form, and uppercase output.
#include <fltx/format.h>
std::string fixed = std::format("{:.32f}", PI_qd); // "3.14159265358979323846264338327950"
std::string scientific = std::format("{:+.16e}", PI_qd); // "+3.1415926535897932e+00"
std::string padded = std::format("{:>25.20g}", PI_qd); // " 3.1415926535897932385"
std::string hex = std::format("{:.8a}", PI_qd); // "0x1.921fb544p+1"
std::string hex_alt = std::format("{:+#.12A}", PI_qd); // "+0X1.921FB54442D2P+1"fltx/dispatch.h includes a small runtime-to-template dispatch layer.
This lets runtime values such as FloatType::FDD or FloatType::FQD select a compile-time type, so the called function still compiles as a normal template specialization.
#include <iostream>
#include <fltx.h>
using namespace bl;
template<class T>
void run_kernel(int width, int height)
{
T x = T{ width } / T{ height };
T y = bl::sqrt(x) + bl::sin(x);
std::cout << y << '\n';
}
int main()
{
FloatType precision = FloatType::FQD;
bl_table_invoke(
bl_dispatch_table(run_kernel, 1920, 1080),
bl_enum_type(precision) // selects compile-time type fqd from runtime FloatType::FQD value
);
}Mapping a custom enum to a compile-time type
fltx/util/template_dispatch.h is the lower-level dispatch utility used by fltx/dispatch.h. It can also be used directly when you want your own runtime enum to select one of several compile-time types:
#include <fltx/util/template_dispatch.h>
enum struct Backend { Cpu, Gpu, COUNT };
struct CpuBackend {};
struct GpuBackend {};
bl_map_enum_to_type(Backend::Cpu, CpuBackend);
bl_map_enum_to_type(Backend::Gpu, GpuBackend);
template<class BackendT, bool Debug>
void run()
{
if constexpr (Debug)
{
// Debug-only path.
}
}
int main()
{
Backend backend = Backend::Gpu;
bool debug = true;
bl_table_invoke(
bl_dispatch_table(run),
bl_enum_type(backend),
debug
);
}These steps are for contributors who want to build the fltx repository itself, including tests and benchmarks. They install the repo-local vcpkg dependencies used by the test suite, such as Catch2, Boost.Multiprecision, GMP, and MPFR.
The validation and metrics tools require Python 3.10 or newer. Use that version when invoking the Python scripts directly as well as when configuring CMake.
If you only want to use fltx from your own project, you do not need this full setup. Use the vcpkg or CMake installation instructions above instead.
Windows
Install Visual Studio with the C++ desktop workload and Python 3, then use a Developer PowerShell or Developer Command Prompt:
git clone --recurse-submodules https://github.com/bitloop-dev/fltx.git
cd fltx
.\vcpkg\bootstrap-vcpkg.bat
cmake --preset windows-x64-msvc-release
cmake --build --preset windows-x64-msvc-release --parallelThe windows-x64-msvc-release preset uses Ninja with the MSVC compiler from the active Visual Studio developer environment.
macOS
For an Apple silicon Mac:
# 1. Install Homebrew, if missing
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. Add Homebrew to zsh PATH
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# 3. Install tools needed by CMake and vcpkg ports
brew install ninja cmake python pkg-config autoconf autoconf-archive automake libtool m4
# Refresh command lookup after installing Python; expect Homebrew Python 3.10+
eval "$(/opt/homebrew/bin/brew shellenv)"
rehash
command -v python3
python3 --version
# 4. Clone fltx with submodules
cd ~/Documents
git clone --recurse-submodules https://github.com/bitloop-dev/fltx.git
cd fltx
# 5. Bootstrap repo-local vcpkg
./vcpkg/bootstrap-vcpkg.sh
# 6. Configure; this installs vcpkg dependencies like Catch2, GMP, and MPFR
cmake --preset macos-arm64-appleclang-release
# 7. Build
cmake --build --preset macos-arm64-appleclang-release --parallelFor an already-cloned repo:
eval "$(/opt/homebrew/bin/brew shellenv)"
rehash
python3 --version # Must be 3.10 or newer
cd ~/Documents/fltx
git submodule update --init --recursive
./vcpkg/bootstrap-vcpkg.sh
cmake --preset macos-arm64-appleclang-release
cmake --build --preset macos-arm64-appleclang-release --parallelIf python3 still selects Apple's Python 3.9, use
/opt/homebrew/bin/python3 explicitly for validation and metrics commands.
CMake can find Homebrew Python independently, but scripts launched with
python3 use the interpreter selected by your shell, including for subprocesses.
Linux
The Ubuntu/Debian flow is:
sudo apt update
sudo apt install -y git curl zip unzip tar build-essential cmake ninja-build clang python3 pkg-config autoconf autoconf-archive automake libtool m4
git clone --recurse-submodules https://github.com/bitloop-dev/fltx.git
cd fltx
./vcpkg/bootstrap-vcpkg.sh
cmake --preset linux-x64-gcc-release
cmake --build --preset linux-x64-gcc-release --parallelOther distributions should use equivalent packages for a C++20 compiler, CMake, Ninja, Python 3, pkg-config, and the autotools used by the GMP/MPFR vcpkg ports.
For the recommended complete local check:
python .\validation\run_preset_checks.py --preset windows-x64-msvc-releaseThis configures the selected preset and runs the same contract, constexpr, package, tooling, and standard accuracy checks used by CI.
To run the registered CTest suite in an already configured and built tree:
ctest --preset windows-x64-msvc-releaseOn Linux and macOS, invoke the validation script with python3 and use the corresponding preset, such as linux-x64-gcc-release or macos-arm64-appleclang-release.
For detailed validation and metrics workflows, see validation/README.md.
Note
These results compare fltx against reference libraries at broadly comparable precision levels. They are not strict like-for-like comparisons: the underlying representations, exponent ranges, semantics, and implementation goals differ. These types are nevertheless included to illustrate cases where fltx may offer a favourable precision/performance trade-off.
- TLFloat (
Quad/Octuple) uses IEEE-754 floating-point representations and supports a substantially wider exponent range than fltx's double-double and quad-double types. Comparisons therefore cover only the range relevant tobl::fddandbl::fqd. boost::multiprecision::mpfr_float_backend<64>provides roughly the same nominal significand precision as bl::fqd, but uses MPFR and has a fundamentally different representation and performance model.- qdpp (
dd_real/qd_real) andboost::cpp_double_doubleare substantially closer to like-for-like, as they use the same underlying floating-point expansion representation.
Tested on:
- Windows: AMD Ryzen 9 5950X, 32 GB DDR4
- Linux: AMD EPYC 7232P, 64 GB DDR4
- macOS: Apple M2, 16 GB unified memory
Cross-platform timings reflect different hardware as well as compilers and operating systems.
Tip
Expand a platform section, then click the table image to open the detailed, non-compact version.
Metrics table definitions
| Metric | Definition |
|---|---|
mean/worst bits accurate |
Mean and minimum MPFR-relative accuracy across finite samples; exact (=) means exact equality |
domain pass |
Named input regions whose worst sample meets the corresponding fltx release threshold; examples include general and moderate inputs, near-one values, cancellation, boundaries, wide exponents, > extreme finite values, and argument or quadrant reduction |
performance |
Nanoseconds per iteration; relative speed is implementation speed ÷ fltx speed |
Inf/NaN |
Both, Inf, NaN, or No indicates which non-finite-value probes pass; - means not applicable |
±0 |
✓ means all applicable signed-zero probes preserve the required sign; ✗ means at least one fails; - means not applicable |
Compared with reference libraries
Compared with reference libraries
Expression templates are opt-in. To use expression templates, define FLTX_ENABLE_FQD_EXPRESSIONS=1 (or before any fltx headers), e.g.
target_compile_definitions(myapp PRIVATE FLTX_ENABLE_FQD_EXPRESSIONS=1)or
#define FLTX_ENABLE_FQD_EXPRESSIONS 1
#include <fltx.h>With expression templates enabled, bl::fqd recognises a set of common arithmetic expression patterns, including sums of products, dot products with a bias term, and scaled linear combinations.
These expressions are represented as small compile-time nodes and lowered to highly optimised fused implementations. Delaying intermediate normalisation reduces rounding and avoids temporary values.
Use a consistent setting wherever inline functions or template instantiations
are shared. If your library's public headers depend on this policy, propagate
the definition with PUBLIC rather than PRIVATE.
Warning
Expressions have distinct types, so C++ template deduction rules mean functions requiring matching argument types (such as std::max) may need an explicit value type or conversion, e.g.
std::max<fqd>(a, b * c); // Specify the value type
std::max(a, fqd{b * c}); // Manually materialize the expressionAlternatively, use the corresponding expression-aware bl:: functions to avoid explicit conversion, e.g.
bl::max(a, b * c);This expression-template limitation also motivates P3398R0 (decays_to), illustrated with matrix arithmetic. It proposes automatic value-type deduction, but has not been adopted into C++.
Hot low-level kernels deliberately avoid unconditional range checks when hardware FMA is not used or unavailable (e.g. during constant evaluation). This keeps ordinary arithmetic inexpensive; it is an intentional performance trade-off.
This project is licensed under the MIT License. See LICENSE for details.