- ^(accuracy_tests|hash_tests|io_tests|metrics_tests|parity_tests|precision_tests|random_tests)$
+ ^(fltx_contract_tests|fltx_constexpr_tests)$
--verbosity high --list-tests --reporter xml ~[bench]
-
- 1
-
Executable
60000
Combine
@@ -40,11 +37,9 @@ endif()
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
-include(cmake/fltxOptions.cmake)
-include(cmake/fltxFastMath.cmake)
+include(cmake/fltxBuildPolicy.cmake)
include(cmake/fltxMsvcTiming.cmake)
include(cmake/fltxMsvcParallel.cmake)
-include(cmake/fltxSimd.cmake)
include(cmake/fltxSources.cmake)
set(FLTX_PUBLIC_SCOPE PUBLIC)
@@ -87,9 +82,8 @@ if(MSVC)
)
endif()
-target_compile_features(fltx ${FLTX_PUBLIC_SCOPE} cxx_std_23)
-fltx_apply_simd_options(fltx ${FLTX_PUBLIC_SCOPE})
-fltx_apply_fast_math_options(fltx)
+fltx_configure_library_target(fltx)
+fltx_assert_package_safe_interface(fltx)
fltx_enable_msvc_parallel_compile(fltx)
fltx_apply_msvc_timing_reports(fltx)
@@ -133,10 +127,5 @@ if(FLTX_DEVELOPER_BUILD)
include(CTest)
add_subdirectory(examples)
- add_subdirectory(tests)
- add_subdirectory(tests/unit_compile_tests)
-
- if(NOT TARGET f256_expression_compile_bench)
- add_subdirectory(tests/expressions/f256)
- endif()
+ add_subdirectory(validation)
endif()
diff --git a/CMakePresets.json b/CMakePresets.json
index b9cfcc0..e5caa86 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -8,183 +8,266 @@
"configurePresets": [
{
- "name": "native-debug",
+ "name": "_ninja-release",
+ "hidden": true,
"generator": "Ninja",
- "binaryDir": "${sourceDir}/build/native-debug",
"toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Debug",
+ "CMAKE_BUILD_TYPE": "Release",
"CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
}
},
{
- "name": "native-release",
- "generator": "Ninja",
- "binaryDir": "${sourceDir}/build/native-release",
- "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
+ "name": "_windows-ninja-release",
+ "hidden": true,
+ "inherits": "_ninja-release",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Release",
- "CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
+ "VCPKG_OVERLAY_PORTS": "${sourceDir}/cmake/vcpkg-ports"
+ }
+ },
+ {
+ "name": "windows-x64-msvc-release",
+ "displayName": "Windows x64 / MSVC / Release",
+ "inherits": "_windows-ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows"},
+ "binaryDir": "${sourceDir}/build/windows-x64-msvc-release",
+ "cacheVariables": {
+ "CMAKE_C_COMPILER": "cl.exe",
+ "CMAKE_CXX_COMPILER": "cl.exe",
+ "VCPKG_TARGET_TRIPLET": "x64-windows",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "x86_64"
}
},
{
- "name": "clang-debug",
- "displayName": "Clang Debug",
+ "name": "windows-x64-clangcl-release",
+ "displayName": "Windows x64 / clang-cl / Release",
+ "inherits": "_windows-ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows"},
+ "binaryDir": "${sourceDir}/build/windows-x64-clangcl-release",
+ "environment": {
+ "PATH": "$env{PROGRAMFILES}/LLVM/bin;$penv{PATH}"
+ },
+ "cacheVariables": {
+ "CMAKE_C_COMPILER": "clang-cl.exe",
+ "CMAKE_CXX_COMPILER": "clang-cl.exe",
+ "CMAKE_LINKER": "lld-link.exe",
+ "CMAKE_AR": "llvm-lib.exe",
+ "VCPKG_TARGET_TRIPLET": "x64-windows",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "x86_64",
+ "FLTX_METRICS_QDPP": "OFF"
+ }
+ },
+ {
+ "name": "windows-x64-mingw-release",
+ "displayName": "Windows x64 / MinGW / Release",
+ "inherits": "_windows-ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows"},
+ "binaryDir": "${sourceDir}/build/windows-x64-mingw-release",
+ "cacheVariables": {
+ "CMAKE_C_COMPILER": "gcc.exe",
+ "CMAKE_CXX_COMPILER": "g++.exe",
+ "VCPKG_TARGET_TRIPLET": "x64-mingw-static",
+ "VCPKG_HOST_TRIPLET": "x64-mingw-static",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "x86_64"
+ }
+ },
+ {
+ "name": "windows-arm64-msvc-release",
+ "displayName": "Windows ARM64 / MSVC / Release",
+ "inherits": "_windows-ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows"},
+ "binaryDir": "${sourceDir}/build/windows-arm64-msvc-release",
+ "cacheVariables": {
+ "CMAKE_C_COMPILER": "cl.exe",
+ "CMAKE_CXX_COMPILER": "cl.exe",
+ "VCPKG_TARGET_TRIPLET": "arm64-windows",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "arm64",
+ "FLTX_METRICS_QDPP": "OFF",
+ "FLTX_METRICS_TLFLOAT": "OFF"
+ }
+ },
+ {
+ "name": "windows-arm64-clangcl-release",
+ "displayName": "Windows ARM64 / clang-cl / Release",
+ "inherits": "_windows-ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows"},
+ "binaryDir": "${sourceDir}/build/windows-arm64-clangcl-release",
+ "environment": {
+ "PATH": "$env{PROGRAMFILES}/LLVM/bin;$penv{PATH}"
+ },
+ "cacheVariables": {
+ "CMAKE_C_COMPILER": "clang-cl.exe",
+ "CMAKE_CXX_COMPILER": "clang-cl.exe",
+ "CMAKE_LINKER": "lld-link.exe",
+ "CMAKE_AR": "llvm-lib.exe",
+ "VCPKG_TARGET_TRIPLET": "arm64-windows",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "arm64",
+ "FLTX_METRICS_QDPP": "OFF",
+ "FLTX_METRICS_TLFLOAT": "OFF"
+ }
+ },
+ {
+ "name": "linux-x64-gcc-release",
+ "displayName": "Linux x64 / GCC / Release",
+ "inherits": "_ninja-release",
"condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux"},
- "generator": "Ninja",
- "binaryDir": "${sourceDir}/build/clang-debug",
- "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
+ "binaryDir": "${sourceDir}/build/linux-x64-gcc-release",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Debug",
- "CMAKE_CXX_COMPILER": "${sourceDir}/cmake/toolchains/fltx-clangxx",
+ "CMAKE_C_COMPILER": "gcc",
+ "CMAKE_CXX_COMPILER": "g++",
+ "VCPKG_TARGET_TRIPLET": "x64-linux",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "x86_64"
+ }
+ },
+ {
+ "name": "linux-x64-clang-release",
+ "displayName": "Linux x64 / Clang / Release",
+ "inherits": "_ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux"},
+ "binaryDir": "${sourceDir}/build/linux-x64-clang-release",
+ "cacheVariables": {
+ "CMAKE_C_COMPILER": "clang",
+ "CMAKE_CXX_COMPILER": "clang++",
"CMAKE_INSTALL_LIBDIR": "lib",
- "CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
+ "VCPKG_TARGET_TRIPLET": "x64-linux",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "x86_64"
}
},
{
- "name": "clang-release",
- "displayName": "Clang Release",
+ "name": "linux-arm64-gcc-release",
+ "displayName": "Linux ARM64 / GCC / Release",
+ "inherits": "_ninja-release",
"condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux"},
- "generator": "Ninja",
- "binaryDir": "${sourceDir}/build/clang-release",
- "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
+ "binaryDir": "${sourceDir}/build/linux-arm64-gcc-release",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Release",
- "CMAKE_CXX_COMPILER": "${sourceDir}/cmake/toolchains/fltx-clangxx",
+ "CMAKE_C_COMPILER": "gcc",
+ "CMAKE_CXX_COMPILER": "g++",
+ "VCPKG_TARGET_TRIPLET": "arm64-linux",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "arm64"
+ }
+ },
+ {
+ "name": "linux-arm64-clang-release",
+ "displayName": "Linux ARM64 / Clang / Release",
+ "inherits": "_ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux"},
+ "binaryDir": "${sourceDir}/build/linux-arm64-clang-release",
+ "cacheVariables": {
+ "CMAKE_C_COMPILER": "clang",
+ "CMAKE_CXX_COMPILER": "clang++",
"CMAKE_INSTALL_LIBDIR": "lib",
- "CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
+ "VCPKG_TARGET_TRIPLET": "arm64-linux",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "arm64"
}
},
{
- "name": "mingw-debug",
- "generator": "Ninja",
- "binaryDir": "${sourceDir}/build/mingw-debug",
- "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
+ "name": "macos-x64-appleclang-release",
+ "displayName": "macOS x64 / AppleClang / Release",
+ "inherits": "_ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin"},
+ "binaryDir": "${sourceDir}/build/macos-x64-appleclang-release",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Debug",
- "VCPKG_TARGET_TRIPLET": "x64-mingw-static",
- "CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
+ "CMAKE_C_COMPILER": "clang",
+ "CMAKE_CXX_COMPILER": "clang++",
+ "VCPKG_TARGET_TRIPLET": "x64-osx",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "x86_64"
}
},
{
- "name": "mingw-release",
- "generator": "Ninja",
- "binaryDir": "${sourceDir}/build/mingw-release",
- "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
+ "name": "macos-arm64-appleclang-release",
+ "displayName": "macOS ARM64 / AppleClang / Release",
+ "inherits": "_ninja-release",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin"},
+ "binaryDir": "${sourceDir}/build/macos-arm64-appleclang-release",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Release",
- "VCPKG_TARGET_TRIPLET": "x64-mingw-static",
- "VCPKG_HOST_TRIPLET": "x64-mingw-static",
- "CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
+ "CMAKE_C_COMPILER": "clang",
+ "CMAKE_CXX_COMPILER": "clang++",
+ "VCPKG_TARGET_TRIPLET": "arm64-osx",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "arm64"
}
},
{
- "name": "wasm32-release",
- "generator": "Ninja",
- "binaryDir": "${sourceDir}/build/wasm32-release",
- "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
+ "name": "wasm32-emscripten-release",
+ "displayName": "WebAssembly wasm32 / Emscripten / Release",
+ "inherits": "_ninja-release",
+ "binaryDir": "${sourceDir}/build/wasm32-emscripten-release",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Release",
- "CMAKE_EXPORT_COMPILE_COMMANDS": "YES",
"VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
"VCPKG_TARGET_TRIPLET": "wasm32-emscripten",
"VCPKG_OVERLAY_PORTS": "${sourceDir}/cmake/vcpkg-ports",
- "VCPKG_OVERLAY_TRIPLETS": "${sourceDir}/cmake/vcpkg-triplets"
+ "VCPKG_OVERLAY_TRIPLETS": "${sourceDir}/cmake/vcpkg-triplets",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "wasm32",
+ "FLTX_METRICS_QDPP": "ON",
+ "FLTX_METRICS_QDPP_INCLUDE_DIR": "${sourceDir}/validation/extern/qdpp/include"
}
},
{
"name": "vs2026",
+ "displayName": "Visual Studio 2026 x64",
"condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows"},
"generator": "Visual Studio 18 2026",
+ "architecture": "x64",
"binaryDir": "${sourceDir}/build/vs2026",
"toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Debug;Release;RelWithDebInfo",
- "CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
+ "CMAKE_EXPORT_COMPILE_COMMANDS": "YES",
+ "VCPKG_TARGET_TRIPLET": "x64-windows",
+ "FLTX_VALIDATION_EXPECTED_ARCHITECTURE": "x86_64"
}
},
{
- "name": "macos-release",
- "displayName": "macOS Release",
- "generator": "Unix Makefiles",
- "binaryDir": "${sourceDir}/build/macos-release",
+ "name": "xcode",
+ "displayName": "Xcode",
+ "condition": {"type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin"},
+ "generator": "Xcode",
+ "binaryDir": "${sourceDir}/build/xcode",
+ "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"cacheVariables": {
- "CMAKE_BUILD_TYPE": "Release",
- "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
- "VCPKG_TARGET_TRIPLET": "arm64-osx"
+ "CMAKE_CONFIGURATION_TYPES": "Debug;Release;RelWithDebInfo"
}
}
],
"buildPresets": [
- { "name": "01. native-debug", "configurePreset": "native-debug" },
- { "name": "02. native-release", "configurePreset": "native-release" },
- { "name": "03. mingw-debug", "configurePreset": "mingw-debug" },
- { "name": "04. mingw-release", "configurePreset": "mingw-release" },
- { "name": "05. wasm32-release", "configurePreset": "wasm32-release" },
- { "name": "06. macos-release", "configurePreset": "macos-release" },
- { "name": "clang-debug", "configurePreset": "clang-debug" },
- { "name": "clang-release", "configurePreset": "clang-release" }
+ {"name": "windows-x64-msvc-release", "configurePreset": "windows-x64-msvc-release"},
+ {"name": "windows-x64-clangcl-release", "configurePreset": "windows-x64-clangcl-release"},
+ {"name": "windows-x64-mingw-release", "configurePreset": "windows-x64-mingw-release"},
+ {"name": "windows-arm64-msvc-release", "configurePreset": "windows-arm64-msvc-release"},
+ {"name": "windows-arm64-clangcl-release", "configurePreset": "windows-arm64-clangcl-release"},
+ {"name": "linux-x64-gcc-release", "configurePreset": "linux-x64-gcc-release"},
+ {"name": "linux-x64-clang-release", "configurePreset": "linux-x64-clang-release"},
+ {"name": "linux-arm64-gcc-release", "configurePreset": "linux-arm64-gcc-release"},
+ {"name": "linux-arm64-clang-release", "configurePreset": "linux-arm64-clang-release"},
+ {"name": "macos-x64-appleclang-release", "configurePreset": "macos-x64-appleclang-release"},
+ {"name": "macos-arm64-appleclang-release", "configurePreset": "macos-arm64-appleclang-release"},
+ {"name": "wasm32-emscripten-release", "configurePreset": "wasm32-emscripten-release"},
+ {"name": "vs2026-debug", "configurePreset": "vs2026", "configuration": "Debug"},
+ {"name": "vs2026-release", "configurePreset": "vs2026", "configuration": "Release"},
+ {"name": "vs2026-relwithdebinfo", "configurePreset": "vs2026", "configuration": "RelWithDebInfo"},
+ {"name": "xcode-debug", "configurePreset": "xcode", "configuration": "Debug"},
+ {"name": "xcode-release", "configurePreset": "xcode", "configuration": "Release"},
+ {"name": "xcode-relwithdebinfo", "configurePreset": "xcode", "configuration": "RelWithDebInfo"}
],
"testPresets": [
- {
- "name": "native-debug",
- "configurePreset": "native-debug",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "native-release",
- "configurePreset": "native-release",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "mingw-debug",
- "configurePreset": "mingw-debug",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "mingw-release",
- "configurePreset": "mingw-release",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "wasm32-release",
- "configurePreset": "wasm32-release",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "macos-release",
- "configurePreset": "macos-release",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "clang-debug",
- "configurePreset": "clang-debug",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "clang-release",
- "configurePreset": "clang-release",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "vs2026-debug",
- "configurePreset": "vs2026",
- "configuration": "Debug",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "vs2026-release",
- "configurePreset": "vs2026",
- "configuration": "Release",
- "output": { "outputOnFailure": true }
- },
- {
- "name": "vs2026-relwithdebinfo",
- "configurePreset": "vs2026",
- "configuration": "RelWithDebInfo",
- "output": { "outputOnFailure": true }
- }
+ {"name": "windows-x64-msvc-release", "configurePreset": "windows-x64-msvc-release", "output": {"outputOnFailure": true}},
+ {"name": "windows-x64-clangcl-release", "configurePreset": "windows-x64-clangcl-release", "output": {"outputOnFailure": true}},
+ {"name": "windows-x64-mingw-release", "configurePreset": "windows-x64-mingw-release", "output": {"outputOnFailure": true}},
+ {"name": "windows-arm64-msvc-release", "configurePreset": "windows-arm64-msvc-release", "output": {"outputOnFailure": true}},
+ {"name": "windows-arm64-clangcl-release", "configurePreset": "windows-arm64-clangcl-release", "output": {"outputOnFailure": true}},
+ {"name": "linux-x64-gcc-release", "configurePreset": "linux-x64-gcc-release", "output": {"outputOnFailure": true}},
+ {"name": "linux-x64-clang-release", "configurePreset": "linux-x64-clang-release", "output": {"outputOnFailure": true}},
+ {"name": "linux-arm64-gcc-release", "configurePreset": "linux-arm64-gcc-release", "output": {"outputOnFailure": true}},
+ {"name": "linux-arm64-clang-release", "configurePreset": "linux-arm64-clang-release", "output": {"outputOnFailure": true}},
+ {"name": "macos-x64-appleclang-release", "configurePreset": "macos-x64-appleclang-release", "output": {"outputOnFailure": true}},
+ {"name": "macos-arm64-appleclang-release", "configurePreset": "macos-arm64-appleclang-release", "output": {"outputOnFailure": true}},
+ {"name": "wasm32-emscripten-release", "configurePreset": "wasm32-emscripten-release", "output": {"outputOnFailure": true}},
+ {"name": "vs2026-debug", "configurePreset": "vs2026", "configuration": "Debug", "output": {"outputOnFailure": true}},
+ {"name": "vs2026-release", "configurePreset": "vs2026", "configuration": "Release", "output": {"outputOnFailure": true}},
+ {"name": "vs2026-relwithdebinfo", "configurePreset": "vs2026", "configuration": "RelWithDebInfo", "output": {"outputOnFailure": true}},
+ {"name": "xcode-debug", "configurePreset": "xcode", "configuration": "Debug", "output": {"outputOnFailure": true}},
+ {"name": "xcode-release", "configurePreset": "xcode", "configuration": "Release", "output": {"outputOnFailure": true}},
+ {"name": "xcode-relwithdebinfo", "configurePreset": "xcode", "configuration": "RelWithDebInfo", "output": {"outputOnFailure": true}}
]
}
diff --git a/LICENSE b/LICENSE
index 1e78390..9251c72 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 willmh93
+Copyright (c) 2026 William Hemsworth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 913aa29..43081f1 100644
--- a/README.md
+++ b/README.md
@@ -1,56 +1,65 @@

- Extended-precision floating point and constexpr math for C++23
+ Extended-precision floating point and constexpr math for C++20
Fast · Precise · Lightweight
-[](https://github.com/willmh93/fltx/actions/workflows/precision-tests-linux.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/precision-tests-windows.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/precision-tests-mac.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/precision-tests-wasm32.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/parity-tests-linux.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/parity-tests-windows.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/parity-tests-mac.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/parity-tests-wasm32.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/io-tests-linux.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/io-tests-windows.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/io-tests-mac.yml)
-[](https://github.com/willmh93/fltx/actions/workflows/io-tests-wasm32.yml)
-
-`fltx` is for projects that needs more precision than `double`, without giving up fixed-size scalar types, predictable performance, `constexpr` support, or familiar C++ ergonomics.
+[](https://github.com/willmh93/fltx/actions/workflows/tests-linux.yml)
+[](https://github.com/willmh93/fltx/actions/workflows/tests-windows.yml)
+[](https://github.com/willmh93/fltx/actions/workflows/tests-macos.yml)
+[](https://github.com/willmh93/fltx/actions/workflows/tests-wasm32.yml)
+
+`fltx` is for projects that need more precision than `double`, without giving up fixed-size scalar types, predictable performance, `constexpr` support, or familiar C++ ergonomics.
## Menu
- [Overview](https://github.com/willmh93/fltx#highlights): highlights and core types
- [Get Started](https://github.com/willmh93/fltx#quick-start): quick start and installation
- [Library Guide](https://github.com/willmh93/fltx#use-cases): use cases, headers, numeric types, math, and IO
-- [Advanced Features](https://github.com/willmh93/fltx#f256-expression-fusion): f256 expression fusion and template dispatch
+- [Advanced Features](https://github.com/willmh93/fltx#template-dispatch): template dispatch
- [Developer](https://github.com/willmh93/fltx#building-fltx): building fltx and running tests
-- [Project Details](https://github.com/willmh93/fltx#benchmarks): benchmarks and license
+- [Project Details](https://github.com/willmh93/fltx#benchmarks): benchmarks, implementation notes, and license
## Highlights
Features:
+- C++20 minimum language requirement
- Fixed-size extended-precision scalar types: [`bl::f128`](include/fltx/f128.h) and [`bl::f256`](include/fltx/f256.h)
-- Full `constexpr` support for arithmetic, comparisons, conversions, parsing, formatting, and all math functions
-- A familiar standard-library shaped API; in most cases you can simply replace `std::` with `bl::`
+- Library-wide **constexpr** support for math, parsing, static formatting, and conversions
+- Familiar `std::` style APIs; many calls work by swapping `std::` for `bl::`
- No required runtime dependencies
-- Standard-library integration for IO streams / manipulators, `std::numeric_limits`, `std::numbers`, `std::hash`, `std::format`
+- Standard-library integration for streams, manipulators, `std::numeric_limits`, `std::numbers`, `std::hash`, and `std::format`
Accuracy:
- Accuracy validated against [boost::multiprecision::mpfr_float_backend](https://www.boost.org/doc/libs/latest/libs/multiprecision/doc/html/boost_multiprecision/tut/floats/mpfr_float.html)
-- Enable `FLTX_CONSTEXPR_PARITY` for bitwise-identical runtime and `constexpr` results (reduces performance)
+- **Infinity** / **NaN** correctness
+- Runtime, genuine `constexpr`, and fast-math paths are accuracy-gated independently; exact cross-path bits are not promised
+- Consumer fast-math treats finite inputs as a precondition for runtime f128/f256 `+`, `-`, `*`, and `/`; strict builds, genuine constant evaluation, and other math APIs retain their special-value contracts
Performance:
+- Benchmarked against comparable libraries, demonstrating overall superior performance
- Compatible with fast-math builds
-- [`bl::f256`](include/fltx/f256.h) has an expression node system which recognises and fuses common arithmetic shapes, reducing intermediate rounding and temporary value materialisation, allowing those shapes to run specialised fused kernels.
-- Hardware acceleration where supported, including x86/x64 SSE2 with FMA when available, AArch64 NEON, and WebAssembly `wasm128` SIMD via Emscripten
+- Internal [`bl::f256`](include/fltx/f256.h) expression fusion reduces intermediate rounding and temporary value materialisation for common arithmetic shapes.
+- Hardware acceleration where supported, including x86/x64 SSE2 with FMA when available, AArch64 NEON, and WebAssembly SIMD128 via Emscripten
- Suitable for lightweight native builds, WebAssembly / Emscripten
- Optional runtime-to-compile-time dispatch helper for template-specialized kernels
+MinGW/GCC consumers that need extended precision down into binary64's
+subnormal range should compile with `-ffast-math` but link with
+`-ffast-math -mno-daz-ftz`. Bare link-time `-ffast-math` adds GCC's
+`crtfastmath.o`, globally enabling FTZ/DAZ and discarding low expansion limbs.
+The MinGW consumer-fast-math validation profile uses the gradual-underflow
+form.
+
+MSVC C++20 builds require MSVC 19.36 or newer, and GCC C++20 builds require GCC
+12 or newer. To preserve compile time and object size in both optimized and
+debug builds, fltx uses their accepted `if consteval` extensions internally in
+C++20 mode. C++23 builds use the standard feature; Clang-family C++20 builds
+use `std::is_constant_evaluated()`.
+
## Core Types
| Type | Representation | Accuracy |
@@ -67,7 +76,35 @@ sizeof(bl::f128) == 16
sizeof(bl::f256) == 32
```
-[`bl::f128`](include/fltx/f128.h) and [`bl::f256`](include/fltx/f256.h) are not IEEE [binary128](https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format#IEEE_754_quadruple-precision_binary_floating-point_format:_binary128) / [binary256](https://en.wikipedia.org/wiki/Octuple-precision_floating-point_format#IEEE_754_octuple-precision_binary_floating-point_format:_binary256) types, and they are not arbitrary-precision numbers.
+[`bl::f128`](include/fltx/f128.h) and [`bl::f256`](include/fltx/f256.h) are *not* IEEE [binary128](https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format#IEEE_754_quadruple-precision_binary_floating-point_format:_binary128) / [binary256](https://en.wikipedia.org/wiki/Octuple-precision_floating-point_format#IEEE_754_octuple-precision_binary_floating-point_format:_binary256) types.
+
+They are fixed-size expansion types: `f128` is double-double, and `f256` is quad-double. The names are intentionally short and uniform with `f32`/`f64`; they describe the precision tier, not the underlying representation.
+
+### Nominal precision policy
+
+The public floating-point model treats `f128` as a 106-bit type and `f256` as
+a 212-bit type. `std::numeric_limits`, `epsilon()`, default text precision, and
+`bl::nextafter`/`bl::nexttoward` follow that nominal model. In particular,
+`nextafter` adds or subtracts one binade-relative nominal ULP; it does not search
+for the smallest possible change to an individual storage limb.
+
+Arithmetic still uses the full nonoverlapping expansion representation and may
+retain useful information in sparse tails beyond the nominal lattice. Results
+are not rounded to 106 or 212 bits at API boundaries. Default `to_string`,
+`to_static_string`, `to_chars`, and general `std::format` output use 33 digits
+for `f128` and 65 for `f256`. Pass an explicit larger precision when sparse-tail
+detail must be preserved; the fixed-format buffer capacities are unchanged.
+
+Accuracy reports measure the two text directions independently. A `parse` row
+compares the stored expansion with the exact input decimal, while a formatting
+row has MPFR read the emitted decimal directly and deliberately bypasses the
+fltx parser. Formatting therefore can score more bits than the nominal binary
+destination can retain; matching parse/format scores are not an I/O contract.
+For values on the nominal lattice, the default decimal round trip is stable:
+the parsed value is within half a nominal ULP and emits the same default text.
+Identical limb encodings are not promised because parsing may retain a closer
+sparse tail. Explicit scientific precision 33 for `f128` and 67 for `f256`
+remains the tested component-preserving envelope for ordinary expansion tails.
## Quick Start
@@ -87,6 +124,7 @@ int main()
constexpr f256 d = bl::sin(a + b);
std::cout
+ << std::fixed
<< std::setprecision(std::numeric_limits::digits10)
<< "a = " << a << "\n"
<< "b = " << b << "\n"
@@ -100,11 +138,29 @@ Output:
```text
a = 0.333333333333333333333333333333333333333333333333333333333333333
b = 0.666666666666666666666666666666666666666666666666666666666666667
-c = 1
-d = 0.84147098480789650665250232163029899962256306079837106567275171
+c = 1.000000000000000000000000000000000000000000000000000000000000000
+d = 0.841470984807896506652502321630298999622563060798371065672751710
```
-More examples are available in [examples/](examples/)
+More examples:
+
+| Example | Shows |
+|---|---|
+| [`example_basic.cpp`](examples/example_basic.cpp) | Basic `f128` / `f256` arithmetic, literals, and output. |
+| [`example_constexpr_io.cpp`](examples/example_constexpr_io.cpp) | Compile-time parsing, formatting, and string conversion. |
+| [`example_dispatch.cpp`](examples/example_dispatch.cpp) | Generic code dispatching across FLTX precision types. |
+| [`example_mandelbrot.cpp`](examples/example_mandelbrot.cpp) | Higher-precision numeric work in a small visual workload. |
+| [`example_consteval_ellipse.cpp`](examples/example_consteval_ellipse.cpp) | Consteval geometry using FLTX math. |
+| [`example_consteval_library_sweep.cpp`](examples/example_consteval_library_sweep.cpp) | Library-wide constexpr coverage across the supported function groups. |
+| [`example_random.cpp`](examples/example_random.cpp) | Random value generation for FLTX types. |
+| [`example_pow.cpp`](examples/example_pow.cpp) | `bl::pow` / `bl::ipow` overload behavior, return-type policy, and fast paths. |
+| [`example_newton_solver.cpp`](examples/example_newton_solver.cpp) | Generic Newton solving across `f64`, `f128`, and `f256`. |
+| [`example_charconv.cpp`](examples/example_charconv.cpp) | Buffer-oriented `to_chars` / `from_chars` parsing and formatting. |
+| [`example_std_integration.cpp`](examples/example_std_integration.cpp) | Standard-library integration with numbers, limits, hashing, comparisons, and optional `std::format`. |
+| [`example_storage_interop.cpp`](examples/example_storage_interop.cpp) | Using `f128_s` / `f256_s` storage forms at fixed-size API boundaries. |
+| [`example_special_values.cpp`](examples/example_special_values.cpp) | NaN, infinity, signed zero, classification, and layout helpers such as `frexp` / `modf`. |
+| [`example_polynomial.cpp`](examples/example_polynomial.cpp) | Polynomial evaluation with `fma` and precision-sensitive cancellation. |
+| [`example_consteval_coefficients.cpp`](examples/example_consteval_coefficients.cpp) | Compile-time generation of high-precision coefficient tables. |
## Installation
@@ -153,6 +209,16 @@ add_subdirectory(/path/to/fltx fltx-build)
target_link_libraries(main PRIVATE fltx::fltx)
```
+The package has three performance controls:
+
+| Option | Default | Meaning |
+|---|---|---|
+| `FLTX_FMA_MODE=AUTO\|OFF\|ASSUME` | `AUTO` | Safely detect x86 FMA at runtime, disable hardware FMA, or let the consumer guarantee FMA support. `ASSUME` exports `-mfma` where GNU-style compilers require it and performs no runtime feature check. |
+| `FLTX_SIMD=ON\|OFF` | `ON` | Enable supported SIMD paths. Emscripten `ON` supplies `-msimd128` to both the library and consuming translation units. |
+| `FLTX_INTERNAL_FAST_MATH=AUTO\|ON\|OFF` | `AUTO` | Control only the compiled `fltx` runtime sources. `AUTO` enables a platform only after its accuracy and performance gate is approved; the current approval list is empty. These flags are never exported to consumers. |
+
+Normal build-type optimization flags remain controlled by the selected CMake configuration and toolchain.
+
## Use Cases
`fltx` is aimed at workloads where both precision and speed matter:
@@ -182,10 +248,10 @@ Individual headers are also available when you want a smaller include surface:
|---|---|
| [`fltx/f128.h`](include/fltx/f128.h)
[`fltx/f256.h`](include/fltx/f256.h) | Individual extended-precision types, storage types, and core operations |
| [`fltx/f32_math.h`](include/fltx/f32_math.h)
[`fltx/f64_math.h`](include/fltx/f64_math.h)
[`fltx/f128_math.h`](include/fltx/f128_math.h)
[`fltx/f256_math.h`](include/fltx/f256_math.h) | Math APIs for individual floating-point types |
-| [`fltx/f128_io.h`](include/fltx/f128_io.h)
[`fltx/f256_io.h`](include/fltx/f256_io.h) | `bl::to_f128`, `bl::to_f256`
`bl::to_string`, `bl::to_static_string`, `bl::to_string_collapsed`
`_dd` / `_qd` literals |
-| [`fltx/charconv.h`](include/fltx/charconv.h) | `bl::to_chars` and `bl::from_chars` for extended types |
+| [`fltx/f128_io.h`](include/fltx/f128_io.h)
[`fltx/f256_io.h`](include/fltx/f256_io.h) | Extended-type string conversion, stream output, and `_dd` / `_qd` literals |
+| [`fltx/charconv.h`](include/fltx/charconv.h) | `bl::to_chars`, `bl::from_chars`, `bl::parse`, `bl::parse(text, fallback)`, and `bl::try_parse` for `f32`, `f64`, `f128`, and `f256` |
| [`fltx/aliases.h`](include/fltx/aliases.h) | Fundamental aliases such as `f32`, `f64`, `f128`, and `f256` |
-| [`fltx/traits.h`](include/fltx/traits.h) | Concepts, type traits, `FloatType` enum, and `FloatTypeNames` |
+| [`fltx/traits.h`](include/fltx/traits.h) | Concepts, type traits, `FloatType` enum, and `bl::to_string(FloatType)` |
| [`fltx/format.h`](include/fltx/format.h) | Optional `std::formatter` specializations when `` is available |
| [`fltx/dispatch.h`](include/fltx/dispatch.h) | Includes standalone [`fltx/template_dispatch.h`](include/fltx/template_dispatch.h) runtime-to-compile-time dispatch-table helper,
plus mappings for `FloatType` values to `f32`, `f64`, `f128`, or `f256` |
@@ -249,23 +315,40 @@ constexpr T radius(T x, T y)
return bl::sqrt(x * x + y * y);
}
-using namespace bl::literals;
-
constexpr bl::f32 a = radius(1.0f, 2.0f);
constexpr bl::f64 b = radius(1.0, 2.0);
-constexpr bl::f128 c = radius(1.0_dd, 2.0_dd);
-constexpr bl::f256 d = radius(1.0_qd, 2.0_qd);
+constexpr bl::f128 c = radius(1_dd, 2_dd);
+constexpr bl::f256 d = radius(1_qd, 2_qd);
+```
+
+Exact operators, hashing, and serialization remain bit-sensitive. For numerical
+closeness, use the same-precision `bl::almost_equal` overloads:
+
+```cpp
+constexpr bl::f128 a128{1.0};
+constexpr bl::f128 b128 = a128 + bl::f128{0x1p-80};
+constexpr bool close128 = bl::almost_equal(a128, b128);
+
+constexpr bl::f256 a256{1.0};
+constexpr bl::f256 b256 = a256 + bl::f256{0x1p-180};
+constexpr bool close256 = bl::almost_equal(
+ a256, b256, bl::f256{0x1p-179});
```
+The defaults are relative tolerances of `2^-74` for `f128` and `2^-169` for
+`f256`. Pass an explicit absolute tolerance for comparisons near zero. Scalar
+and mixed-precision arguments must be promoted explicitly so the chosen
+precision and tolerance are never implicit.
+
Supported function groups:
| constexpr | Category | Functions |
|---|---|---|
| ✅ | Arithmetic | `abs`, `fabs`, `fma` |
-| ✅ | Rounding | `floor`, `ceil`, `trunc`, `round`, `lround`, `llround`, `nearbyint`, `rint`, `lrint`, `llrint` |
+| ✅ | Rounding | `floor`, `ceil`, `trunc`, `round`, `lround`, `llround`, `round_to` |
| ✅ | Remainders | `fmod`, `remainder`, `remquo` |
| ✅ | Min / max / sign | `fmin`, `fmax`, `fdim`, `copysign`, `signbit` |
-| ✅ | Roots / powers | `sqrt`, `cbrt`, `hypot`, `pow` |
+| ✅ | 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` |
@@ -273,40 +356,113 @@ Supported function groups:
| ✅ | 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`](examples/example_consteval_library_sweep.cpp).
+
+Use `bl::roundeven(x)` for nearest-integer rounding with halfway cases rounded to even. The standard-shaped `bl::round(x)`, `bl::trunc(x)`, `bl::ceil(x)`, and `bl::floor(x)` functions provide nearest-away-from-zero and the three directed rounding behaviors. Use `bl::round_decimals(x, precision)` for decimal-place rounding and `bl::round_significant(x, precision)` for significant-figure rounding. Use `bl::pow(T{ base }, n)` or `bl::ipow(T{ base }, n)` for integral powers, including powers of ten.
## IO and Literals
-[`fltx/io.h`](include/fltx/io.h) adds `constexpr`-capable parsing, formatting, stream output, string conversion, and the `_dd` / `_qd` literals:
+[`fltx/io.h`](include/fltx/io.h) provides constexpr-capable parsing, formatting, stream output, string conversion, and the `_dd` / `_qd` literals:
+
+### Literals example:
```cpp
+#include
+using namespace bl;
using namespace bl::literals;
-constexpr f128 a = 1.25_dd;
-constexpr f256 b = 3.1415926535897932384626433832795028841971_qd;
+constexpr f128 pi_128 = 3.14159265358979323846264338327950288_dd;
+constexpr f256 pi_256 = 3.1415926535897932384626433832795028841971693993751058209749445923078164_qd;
+```
+
+### Serialize example:
-constexpr auto s1 = bl::to_static_string(b); // static_string<512>
-std::string s2 = bl::to_string(b);
+```cpp
+// serialize
+std::string s1 = bl::to_string(pi_256); // string with default precision
+std::string s2 = bl::to_string(pi_256, 16, true); // string with fixed precision
+constexpr auto s3 = bl::to_static_string(pi_256, 10); // static_string with fixed precision
```
-Stream output supports `std::setprecision`, `std::fixed`, `std::scientific`, `std::showpoint`, `std::showpos`, and `std::uppercase`.
+The default precision is the nominal round-trip precision (33 digits for
+`f128`, 65 for `f256`). Explicit precision remains available for expansion-aware
+serialization, including 67-digit `f256` output when sparse tail detail matters.
-For buffer-oriented code, [`fltx/charconv.h`](include/fltx/charconv.h) provides `bl::to_chars` and `bl::from_chars` overloads for extended types. Include [`fltx/format.h`](include/fltx/format.h) when you want `std::format` support on standard libraries that provide ``.
+### Deserialize example:
-## f256 Expression Fusion
+```cpp
+// deserialize (fallback)
+constexpr f256 value1 = bl::parse("__3.1415", 0_qd); // invalid input, use fallback value: 0.0
-[`bl::f256`](include/fltx/f256.h) has an expression node system which recognises common arithmetic shapes, including product sums, dot-product-plus-bias expressions, and scaled linear combinations.
+// deserialize (try/catch)
+f256 value2;
+try
+{
+ value2 = bl::parse("__3.1415");
+}
+catch (const std::exception& e)
+{
+ std::cout << e.what() << "\n"; // exception: "bl::parse invalid input"
+}
-For example:
+// deserialize (error in result.ec)
+constexpr auto result = bl::try_parse("__3.1415");
+if (result)
+ std::cout << "success: " << result.value;
+else
+ std::cout << "invalid input";
+```
+
+Use:
+- `bl::to_static_string` for compile-time fixed-capacity string output.
+- `bl::to_string` when you want a `std::string`.
+- `bl::parse` for strict whole-string parsing that returns the parsed value.
+- `bl::parse(text, fallback)` when a fallback value is enough.
+- `bl::try_parse` when you need error and consumed-character details.
+
+String input is intentionally routed through the parsing APIs. Constructors for `f128` and `f256` are for numeric and storage-form values, not direct text parsing.
+
+Stream output supports `std::setprecision`, `std::fixed`, `std::scientific`, `std::showpoint`, `std::showpos`, and `std::uppercase`.
+
+### Buffer-oriented serializing/deserializing example:
```cpp
-f256 r = a * b + c * d + e;
+#include
+
+// serialize
+char buffer[128]{};
+auto written = bl::to_chars(buffer, buffer + sizeof(buffer), pi_256, std::chars_format::fixed, 32);
+if (written.ec == std::errc{})
+ std::cout << std::string_view{ buffer, static_cast(written.ptr - buffer) };
+
+// deserialize
+std::string_view text = "3.1415";
+f256 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;
```
-is kept as a small compile-time expression and lowered to the existing fused product-sum body. This avoids materialising and normalising each intermediate quad-double result before the final value is needed.
+For buffer-oriented code, [`fltx/charconv.h`](include/fltx/charconv.h) provides:
-The matcher also accepts selected equivalent spellings, such as reordered product/value terms and scaled linear forms. That gives common kernels like affine transforms, small matrix-vector operations, polynomial-style updates, and recurrence relations a faster path without requiring users to call specialised helpers or the implementation to maintain a full symbolic optimiser.
+- `bl::to_chars` writes into buffer and reports the end pointer plus `std::errc`.
+- `bl::from_chars` parses the leading numeric token and reports where parsing stopped.
-The fused bodies remain explicit and bounded, which keeps compile-time and code-size costs under control.
+### std::format example:
+
+Include [`fltx/format.h`](include/fltx/format.h) when you want `std::format` support for `f128`/`f256`.
+
+The formatter supports common numeric presentation options such as precision, fixed/scientific/general notation, sign, width, alignment, fill, alternate form, and uppercase output.
+
+```cpp
+#include
+
+std::string fixed = std::format("{:.32f}", pi_256); // "3.14159265358979323846264338327950"
+std::string scientific = std::format("{:+.16e}", pi_256); // "+3.1415926535897932e+00"
+std::string padded = std::format("{:>25.20g}", pi_256); // " 3.1415926535897932385"
+std::string hex = std::format("{:.8a}", pi_256); // "0x1.921fb544p+1"
+std::string hex_alt = std::format("{:+#.12A}", pi_256); // "+0X1.921FB54442D2P+1"
+```
## Template Dispatch
@@ -335,7 +491,7 @@ int main()
bl_table_invoke(
bl_dispatch_table(run_kernel, 1920, 1080),
- bl_enum_type(precision)
+ bl_enum_type(precision) // selects compile-time type f256 from runtime FloatType::F256 value
);
}
```
@@ -430,10 +586,10 @@ cd fltx
./vcpkg/bootstrap-vcpkg.sh
# 6. Configure; this installs vcpkg dependencies like Catch2, GMP, and MPFR
-cmake --preset macos-release
+cmake --preset macos-arm64-appleclang-release
# 7. Build
-cmake --build build/macos-release --parallel
+cmake --build --preset macos-arm64-appleclang-release --parallel
```
For an already-cloned repo:
@@ -443,8 +599,8 @@ cd ~/Documents/fltx
git pull
git submodule update --init --recursive
./vcpkg/bootstrap-vcpkg.sh
-cmake --preset macos-release
-cmake --build build/macos-release --parallel
+cmake --preset macos-arm64-appleclang-release
+cmake --build --preset macos-arm64-appleclang-release --parallel
```
@@ -463,11 +619,11 @@ cd fltx
./vcpkg/bootstrap-vcpkg.sh
-cmake --preset native-release
-cmake --build build/native-release --parallel
+cmake --preset linux-x64-gcc-release
+cmake --build --preset linux-x64-gcc-release --parallel
```
-Other distributions should use equivalent packages for a C++23 compiler, CMake, Ninja, pkg-config, and the autotools used by the GMP/MPFR vcpkg ports.
+Other distributions should use equivalent packages for a C++20 compiler, CMake, Ninja, pkg-config, and the autotools used by the GMP/MPFR vcpkg ports.
@@ -476,10 +632,14 @@ Other distributions should use equivalent packages for a C++23 compiler, CMake,
Test executables are registered with CTest. After building, you can run the registered test cases from the command line:
```bash
-ctest --preset native-release
+ctest --preset linux-x64-gcc-release
```
-Use the matching preset for other configured build trees, such as `macos-release`, `mingw-release`, or `wasm32-release`.
+Use the matching explicit platform/architecture/compiler preset on other
+hosts, such as `macos-arm64-appleclang-release`,
+`windows-x64-mingw-release`, or `wasm32-emscripten-release`. CLion and other
+CMake-aware IDEs can import these Ninja presets directly; `vs2026` and `xcode`
+remain available when native IDE project generation is wanted.
For a multi-config Visual Studio build, include the configuration:
@@ -487,29 +647,61 @@ For a multi-config Visual Studio build, include the configuration:
ctest --preset vs2026-release
```
+The compact gate used by CI builds the public-header and C++ standard
+contracts, comparison-library smokes, package smoke, contract and genuine
+constexpr suites, normal and fixed-constexpr accuracy in both strict and
+consumer-fast-math modes, and Python harness tests:
```powershell
-.\build\vs2026\tests\Release\metrics_tests.exe "[precision],[domain],[bench]"
+cmake --build --preset windows-x64-msvc-release --target fltx_ci_checks
```
-## Benchmarks
+See [validation/README.md](validation/README.md) for focused runner commands, full
+accuracy/metrics publication, installed-package checks, and compile/size
+telemetry.
-Tested on:
-- **Windows:** AMD Ryzen 9 5950X, Memory 32 GB LPDDR5
-- **Linux:** AMD Ryzen 9 5950X, Memory 32 GB LPDDR5
-- **MacOS:** Apple M2 Pro, Memory 16 GB LPDDR5
+## Benchmarks / Metrics
-`ns/iter` for `bl::f128` / `bl::f256` metrics, colored by speed ratio vs the stronger available reference.
+The normalized harness measures the same operation/input corpus for FLTX and
+each comparator, retains independent accuracy evidence and every benchmark
+trial, and publishes only complete f128/f256 runs. Comparators are:
-- cpp_dd (boost::multiprecision::cpp_double_double)
-- mpfr (boost::multiprecision::mpfr_float_backend<64>)
-- dd_real (qdpp)
-- qd_real (qdpp)
+- qdpp `dd_real` / `qd_real`;
+- Boost.Multiprecision `cpp_double_double` / `mpfr_float_backend<64>`;
+- TLFloat `Quad` / `Octuple`.
-
+The checked-in development table currently contains Windows MSVC/MinGW and
+WebAssembly Emscripten results. Linux GCC/Clang and macOS AppleClang become
+canonical only after their final same-source runs are published. Each run's
+JSON metadata records the actual compiler, flags, source fingerprint, host,
+sample profile, comparator set, and artifact hashes.
+The metrics workflow measures strict and consumer-fast-math targets
+independently. Fast-math runners use the real consumer compiler option while
+linking the same compiled fltx library, and publish separate `_fastmath` data,
+metadata, and SVG reports.
-Checked-in metrics CSVs currently live under [res/metrics/](res/metrics/) for the platform/compiler combinations that have generated reports.
+
+
+Checked-in CSV/JSON evidence lives under [validation/metrics/data/](validation/metrics/data/), with
+reproducible SVG reports under [validation/metrics/generated/](validation/metrics/generated/). The
+complete methodology and commands are in [validation/README.md](validation/README.md).
+
+## f256 Expression Fusion
+
+[`bl::f256`](include/fltx/f256.h) uses an internal expression node system which recognises common arithmetic shapes, including product sums, dot-product-plus-bias expressions, and scaled linear combinations.
+
+For example:
+
+```cpp
+f256 r = a * b + c * d + e;
+```
+
+is kept as a small compile-time expression and lowered to the existing fused product-sum body. This avoids materialising and normalising each intermediate quad-double result before the final value is needed.
+
+The matcher also accepts selected equivalent spellings, such as reordered product/value terms and scaled linear forms. That gives common kernels like affine transforms, small matrix-vector operations, polynomial-style updates, and recurrence relations a faster path without requiring users to call specialised helpers or the implementation to maintain a full symbolic optimiser.
+
+The fused bodies remain explicit and bounded, which keeps compile-time and code-size costs under control.
## License
diff --git a/cmake/fltxBuildPolicy.cmake b/cmake/fltxBuildPolicy.cmake
new file mode 100644
index 0000000..6723ac1
--- /dev/null
+++ b/cmake/fltxBuildPolicy.cmake
@@ -0,0 +1,347 @@
+include_guard(GLOBAL)
+
+set(FLTX_BUILD_POLICY_INCLUDED TRUE)
+
+option(
+ FLTX_DEVELOPER_BUILD
+ "Build fltx developer targets: tests, examples, and isolated compile units."
+ "${PROJECT_IS_TOP_LEVEL}"
+)
+
+set(
+ FLTX_INTERNAL_FAST_MATH
+ AUTO
+ CACHE STRING "Fast-math mode for fltx compiled runtime sources: ON, OFF, or AUTO (validated platforms only)."
+)
+set_property(CACHE FLTX_INTERNAL_FAST_MATH PROPERTY STRINGS ON OFF AUTO)
+
+option(
+ FLTX_SIMD
+ "Enable SIMD for fltx compiled sources and supported consuming translation units."
+ ON
+)
+
+set(
+ FLTX_FMA_MODE
+ AUTO
+ CACHE STRING "FMA policy for fltx and consuming translation units: AUTO, OFF, or ASSUME."
+)
+set_property(CACHE FLTX_FMA_MODE PROPERTY STRINGS AUTO OFF ASSUME)
+
+option(
+ FLTX_MSVC_TIMING_REPORTS
+ "Emit MSVC compiler and linker timing reports for fltx developer targets."
+ "${FLTX_DEVELOPER_BUILD}"
+)
+
+option(
+ FLTX_MSVC_PARALLEL_COMPILE
+ "Enable MSVC /MP multi-processor compilation for eligible fltx targets."
+ "${FLTX_DEVELOPER_BUILD}"
+)
+
+option(
+ FLTX_MSVC_DETAILED_TIMING_REPORTS
+ "Emit detailed MSVC frontend and codegen timing reports for fltx developer targets."
+ OFF
+)
+
+mark_as_advanced(
+ FLTX_MSVC_TIMING_REPORTS
+ FLTX_MSVC_PARALLEL_COMPILE
+ FLTX_MSVC_DETAILED_TIMING_REPORTS
+)
+
+set(_FLTX_FAST_MATH_SUPPORTED OFF)
+if(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
+ set(_FLTX_FAST_MATH_SUPPORTED ON)
+endif()
+
+# AUTO is an approval list, not a compiler-capability check. Add a platform
+# here only after its required accuracy/special-value suite passes and its
+# representative compiled-runtime benchmark set improves geometrically by 3%.
+set(_FLTX_INTERNAL_FAST_MATH_AUTO_APPROVED OFF)
+
+set(_FLTX_TARGET_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR};${CMAKE_VS_PLATFORM_NAME}")
+string(TOLOWER "${_FLTX_TARGET_PROCESSOR}" _FLTX_TARGET_PROCESSOR)
+
+set(_FLTX_X86_FMA_TARGET_OPTIONS_SUPPORTED OFF)
+if(_FLTX_TARGET_PROCESSOR MATCHES "(^|;)(x86_64|amd64|x64|win32|i[3-6]86)(;|$)")
+ set(_FLTX_X86_FMA_TARGET_OPTIONS_SUPPORTED ON)
+endif()
+
+set(_FLTX_CLANG_CL OFF)
+if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
+ CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+ set(_FLTX_CLANG_CL ON)
+endif()
+
+function(fltx_resolve_fast_math_mode _OUT _MODE)
+ string(TOUPPER "${_MODE}" _FLTX_MODE)
+
+ if(_FLTX_MODE STREQUAL "AUTO")
+ set(${_OUT} ${_FLTX_FAST_MATH_SUPPORTED} PARENT_SCOPE)
+ elseif(_FLTX_MODE STREQUAL "ON")
+ if(_FLTX_FAST_MATH_SUPPORTED)
+ set(${_OUT} ON PARENT_SCOPE)
+ else()
+ message(WARNING "fltx fast-math mode is ON, but no supported option set exists for ${CMAKE_CXX_COMPILER_ID}/${CMAKE_SYSTEM_NAME}; no fast-math options will be applied")
+ set(${_OUT} OFF PARENT_SCOPE)
+ endif()
+ elseif(_FLTX_MODE STREQUAL "OFF")
+ set(${_OUT} OFF PARENT_SCOPE)
+ else()
+ message(FATAL_ERROR "Invalid fltx fast-math mode '${_MODE}'. Expected AUTO, ON, or OFF.")
+ endif()
+endfunction()
+
+function(fltx_resolve_internal_fast_math_mode _OUT _MODE)
+ string(TOUPPER "${_MODE}" _FLTX_MODE)
+
+ if(_FLTX_MODE STREQUAL "AUTO")
+ set(${_OUT} ${_FLTX_INTERNAL_FAST_MATH_AUTO_APPROVED} PARENT_SCOPE)
+ return()
+ endif()
+
+ fltx_resolve_fast_math_mode(_FLTX_EXPLICIT_FAST_MATH "${_MODE}")
+ set(${_OUT} ${_FLTX_EXPLICIT_FAST_MATH} PARENT_SCOPE)
+endfunction()
+
+function(fltx_target_link_options_if_supported _TARGET)
+ get_target_property(_TARGET_TYPE ${_TARGET} TYPE)
+
+ if(_TARGET_TYPE STREQUAL "EXECUTABLE" OR
+ _TARGET_TYPE STREQUAL "SHARED_LIBRARY" OR
+ _TARGET_TYPE STREQUAL "MODULE_LIBRARY")
+ target_link_options(${_TARGET} PRIVATE ${ARGN})
+ endif()
+endfunction()
+
+function(fltx_apply_private_wasm_simd_options _TARGET)
+ if(NOT EMSCRIPTEN OR NOT FLTX_SIMD)
+ return()
+ endif()
+
+ target_compile_options(${_TARGET} PRIVATE -msimd128)
+ fltx_target_link_options_if_supported(${_TARGET} -msimd128)
+endfunction()
+
+function(fltx_apply_private_fast_math_options _TARGET _MODE)
+ fltx_resolve_fast_math_mode(_FLTX_ENABLE_TARGET_FAST_MATH "${_MODE}")
+ if(NOT _FLTX_ENABLE_TARGET_FAST_MATH)
+ return()
+ endif()
+
+ target_compile_definitions(${_TARGET} PRIVATE FLTX_FAST_MATH)
+
+ if(MSVC)
+ target_compile_options(${_TARGET} PRIVATE /fp:fast)
+ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(${_TARGET} PRIVATE
+ -ffp-contract=fast
+ -fno-math-errno
+ -fno-trapping-math
+ )
+ elseif(CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$")
+ # Clang's plain "fast" mode can override source pragmas. This variant
+ # keeps contraction fast outside protected error-free-transform blocks
+ # while allowing their clang fp contract(off) directives to take effect.
+ target_compile_definitions(${_TARGET} PRIVATE FLTX_DETAIL_FP_CONTRACT_FAST=1)
+ target_compile_options(${_TARGET} PRIVATE
+ -ffp-contract=fast-honor-pragmas
+ -fno-math-errno
+ -fno-trapping-math
+ )
+ endif()
+endfunction()
+
+function(fltx_configure_compiled_x86_fma_backend _TARGET)
+ string(TOUPPER "${FLTX_FMA_MODE}" _FLTX_FMA_MODE_NORMALIZED)
+ if(NOT _FLTX_FMA_MODE_NORMALIZED STREQUAL "AUTO" OR
+ EMSCRIPTEN OR
+ MSVC OR
+ NOT _FLTX_X86_FMA_TARGET_OPTIONS_SUPPORTED OR
+ NOT CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
+ return()
+ endif()
+
+ get_target_property(_FLTX_TARGET_SOURCES ${_TARGET} SOURCES)
+ foreach(_FLTX_SOURCE IN LISTS _FLTX_TARGET_SOURCES)
+ if(_FLTX_SOURCE MATCHES "(^|[/\\\\])fma_x86\\.cpp$")
+ set_property(SOURCE "${_FLTX_SOURCE}" APPEND PROPERTY COMPILE_OPTIONS -mfma)
+ set_property(SOURCE "${_FLTX_SOURCE}" APPEND PROPERTY COMPILE_DEFINITIONS
+ FLTX_BUILD_X86_FMA_BACKEND=1
+ )
+ endif()
+ endforeach()
+endfunction()
+
+function(fltx_configure_public_header_contract _TARGET)
+ set(_FLTX_VISIBILITY PUBLIC)
+ if(ARGC GREATER 1)
+ set(_FLTX_VISIBILITY "${ARGV1}")
+ endif()
+
+ string(TOUPPER "${FLTX_FMA_MODE}" _FLTX_FMA_MODE_NORMALIZED)
+ if(NOT _FLTX_FMA_MODE_NORMALIZED MATCHES "^(AUTO|OFF|ASSUME)$")
+ message(FATAL_ERROR "Invalid FLTX_FMA_MODE='${FLTX_FMA_MODE}'. Expected AUTO, OFF, or ASSUME.")
+ endif()
+
+ set(_FLTX_HEADER_DEFINITIONS)
+ if(NOT FLTX_SIMD)
+ list(APPEND _FLTX_HEADER_DEFINITIONS FLTX_HEADER_SIMD_OFF=1)
+ endif()
+
+ if(_FLTX_FMA_MODE_NORMALIZED STREQUAL "OFF")
+ list(APPEND _FLTX_HEADER_DEFINITIONS FLTX_HEADER_FMA_OFF=1)
+ elseif(_FLTX_FMA_MODE_NORMALIZED STREQUAL "ASSUME")
+ list(APPEND _FLTX_HEADER_DEFINITIONS FLTX_HEADER_FMA_ASSUME=1)
+ elseif(_FLTX_X86_FMA_TARGET_OPTIONS_SUPPORTED AND
+ NOT EMSCRIPTEN AND
+ NOT MSVC AND
+ CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
+ list(APPEND _FLTX_HEADER_DEFINITIONS FLTX_HAS_COMPILED_X86_FMA_BACKEND=1)
+ endif()
+
+ if(_FLTX_HEADER_DEFINITIONS)
+ target_compile_definitions(${_TARGET} ${_FLTX_VISIBILITY} ${_FLTX_HEADER_DEFINITIONS})
+ endif()
+
+ if(_FLTX_FMA_MODE_NORMALIZED STREQUAL "ASSUME" AND
+ _FLTX_X86_FMA_TARGET_OPTIONS_SUPPORTED AND
+ NOT EMSCRIPTEN)
+ if(_FLTX_CLANG_CL)
+ target_compile_options(${_TARGET} ${_FLTX_VISIBILITY} /clang:-mfma)
+ elseif(NOT MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
+ target_compile_options(${_TARGET} ${_FLTX_VISIBILITY} -mfma)
+ endif()
+ endif()
+
+ if(EMSCRIPTEN AND FLTX_SIMD)
+ target_compile_options(${_TARGET} ${_FLTX_VISIBILITY} -msimd128)
+ if(_FLTX_VISIBILITY STREQUAL "PUBLIC" OR _FLTX_VISIBILITY STREQUAL "INTERFACE")
+ target_link_options(${_TARGET} INTERFACE -msimd128)
+ endif()
+ endif()
+endfunction()
+
+function(fltx_configure_internal_target _TARGET)
+ cmake_parse_arguments(FLTX_INTERNAL "" "FAST_MATH_MODE" "" ${ARGN})
+
+ # Consumer -ffast-math can enable process-wide FTZ/DAZ on MinGW, while
+ # Emscripten may optimize the final linked module as a whole. Compile the
+ # affected runtime implementations with their local evaluation barriers so
+ # public APIs can retain the normal out-of-line dispatch boundary.
+ if(MINGW OR EMSCRIPTEN)
+ target_compile_definitions(${_TARGET} PRIVATE
+ FLTX_DETAIL_COMPILED_RUNTIME_FP_BARRIERS=1
+ )
+ endif()
+
+ if(DEFINED FLTX_INTERNAL_FAST_MATH_MODE)
+ set(_FLTX_INTERNAL_FAST_MATH_MODE "${FLTX_INTERNAL_FAST_MATH_MODE}")
+ else()
+ set(_FLTX_INTERNAL_FAST_MATH_MODE "${FLTX_INTERNAL_FAST_MATH}")
+ endif()
+
+ string(TOUPPER "${_FLTX_INTERNAL_FAST_MATH_MODE}" _FLTX_INTERNAL_FAST_MATH_MODE_NORMALIZED)
+ if(NOT _FLTX_INTERNAL_FAST_MATH_MODE_NORMALIZED MATCHES "^(AUTO|ON|OFF)$")
+ message(FATAL_ERROR
+ "Invalid internal fast-math mode '${_FLTX_INTERNAL_FAST_MATH_MODE}'. "
+ "Expected AUTO, ON, or OFF."
+ )
+ endif()
+ target_compile_definitions(${_TARGET} PRIVATE
+ "FLTX_INTERNAL_FAST_MATH_REQUEST_${_FLTX_INTERNAL_FAST_MATH_MODE_NORMALIZED}=1"
+ )
+
+ fltx_resolve_internal_fast_math_mode(
+ _FLTX_ENABLE_INTERNAL_FAST_MATH
+ "${_FLTX_INTERNAL_FAST_MATH_MODE}"
+ )
+ if(_FLTX_ENABLE_INTERNAL_FAST_MATH)
+ fltx_apply_private_fast_math_options(${_TARGET} ON)
+ endif()
+
+ if(NOT FLTX_SIMD)
+ target_compile_definitions(${_TARGET} PRIVATE
+ FLTX_HEADER_SIMD_OFF=1
+ FLTX_F128_ENABLE_SIMD=0
+ FLTX_F256_ENABLE_SIMD=0
+ FLTX_F256_ENABLE_TRIG_SIMD=0
+ )
+ endif()
+
+ fltx_apply_private_wasm_simd_options(${_TARGET})
+ fltx_configure_compiled_x86_fma_backend(${_TARGET})
+endfunction()
+
+function(fltx_configure_library_target _TARGET)
+ target_compile_features(${_TARGET} PUBLIC cxx_std_20)
+ fltx_configure_public_header_contract(${_TARGET} PUBLIC)
+ fltx_configure_internal_target(${_TARGET})
+endfunction()
+
+function(fltx_configure_consumer_fast_math _TARGET)
+ cmake_parse_arguments(FLTX_CONSUMER "" "MODE" "" ${ARGN})
+ if(NOT DEFINED FLTX_CONSUMER_MODE)
+ set(FLTX_CONSUMER_MODE ON)
+ endif()
+
+ string(TOUPPER "${FLTX_CONSUMER_MODE}" _FLTX_CONSUMER_MODE_NORMALIZED)
+ if(NOT _FLTX_CONSUMER_MODE_NORMALIZED MATCHES "^(ON|OFF)$")
+ message(FATAL_ERROR
+ "Invalid consumer fast-math mode '${FLTX_CONSUMER_MODE}'. "
+ "Expected ON or OFF."
+ )
+ endif()
+
+ if(MSVC)
+ if(_FLTX_CONSUMER_MODE_NORMALIZED STREQUAL "ON")
+ target_compile_options(${_TARGET} PRIVATE /fp:fast)
+ else()
+ target_compile_options(${_TARGET} PRIVATE /fp:precise)
+ endif()
+ elseif(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
+ if(_FLTX_CONSUMER_MODE_NORMALIZED STREQUAL "ON")
+ target_compile_options(${_TARGET} PRIVATE -ffast-math)
+ fltx_target_link_options_if_supported(${_TARGET} -ffast-math)
+ else()
+ target_compile_options(${_TARGET} PRIVATE -fno-fast-math)
+ fltx_target_link_options_if_supported(${_TARGET} -fno-fast-math)
+ endif()
+ else()
+ message(FATAL_ERROR
+ "Consumer fast-math validation is unsupported for "
+ "${CMAKE_CXX_COMPILER_ID}/${CMAKE_SYSTEM_NAME}."
+ )
+ endif()
+endfunction()
+
+function(fltx_assert_package_safe_interface _TARGET)
+ foreach(_PROPERTY IN ITEMS INTERFACE_COMPILE_OPTIONS INTERFACE_LINK_OPTIONS)
+ get_target_property(_VALUE ${_TARGET} ${_PROPERTY})
+ if(NOT _VALUE)
+ continue()
+ endif()
+
+ foreach(_FORBIDDEN IN ITEMS "/arch:AVX" "/arch:AVX2" "-mavx" "-mavx2" "/fp:fast" "-ffast-math")
+ if("${_VALUE}" MATCHES "(^|;)${_FORBIDDEN}($|;)")
+ message(FATAL_ERROR "${_TARGET} ${_PROPERTY} contains package-unsafe option '${_FORBIDDEN}': ${_VALUE}")
+ endif()
+ endforeach()
+
+ string(TOUPPER "${FLTX_FMA_MODE}" _FLTX_FMA_MODE_NORMALIZED)
+ if("${_VALUE}" MATCHES "(^|;)(-mfma|/clang:-mfma)($|;)" AND
+ NOT _FLTX_FMA_MODE_NORMALIZED STREQUAL "ASSUME")
+ message(FATAL_ERROR
+ "${_TARGET} ${_PROPERTY} contains an FMA target option outside "
+ "FLTX_FMA_MODE=ASSUME: ${_VALUE}"
+ )
+ endif()
+
+ if("${_VALUE}" MATCHES "(^|;)-msimd128($|;)" AND NOT (EMSCRIPTEN AND FLTX_SIMD))
+ message(FATAL_ERROR "${_TARGET} ${_PROPERTY} contains -msimd128 while Wasm SIMD is disabled: ${_VALUE}")
+ endif()
+ endforeach()
+endfunction()
diff --git a/cmake/fltxFastMath.cmake b/cmake/fltxFastMath.cmake
deleted file mode 100644
index 4471cb0..0000000
--- a/cmake/fltxFastMath.cmake
+++ /dev/null
@@ -1,107 +0,0 @@
-if(NOT DEFINED FLTX_OPTIONS_INCLUDED)
- include("${CMAKE_CURRENT_LIST_DIR}/fltxOptions.cmake")
-endif()
-
-set(_FLTX_FAST_MATH_SUPPORTED OFF)
-if(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
- set(_FLTX_FAST_MATH_SUPPORTED ON)
-endif()
-
-set(_FLTX_FAST_MATH_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR};${CMAKE_VS_PLATFORM_NAME}")
-string(TOLOWER "${_FLTX_FAST_MATH_PROCESSOR}" _FLTX_FAST_MATH_PROCESSOR)
-
-set(_FLTX_FAST_MATH_ENABLE_AVX2 OFF)
-if(_FLTX_FAST_MATH_PROCESSOR MATCHES "(^|;)(x86_64|amd64|x64|win32|i[3-6]86)(;|$)")
- set(_FLTX_FAST_MATH_ENABLE_AVX2 ON)
-endif()
-
-function(fltx_resolve_fast_math_mode _OUT _MODE)
- string(TOUPPER "${_MODE}" _FLTX_MODE)
-
- if(_FLTX_MODE STREQUAL "AUTO")
- set(${_OUT} ${_FLTX_FAST_MATH_SUPPORTED} PARENT_SCOPE)
- elseif(_FLTX_MODE STREQUAL "ON")
- if(_FLTX_FAST_MATH_SUPPORTED)
- set(${_OUT} ON PARENT_SCOPE)
- else()
- message(WARNING "fltx fast-math mode is ON, but no supported option set exists for ${CMAKE_CXX_COMPILER_ID}/${CMAKE_SYSTEM_NAME}; no fast-math options will be applied")
- set(${_OUT} OFF PARENT_SCOPE)
- endif()
- elseif(_FLTX_MODE STREQUAL "OFF")
- set(${_OUT} OFF PARENT_SCOPE)
- else()
- message(FATAL_ERROR "Invalid fltx fast-math mode '${_MODE}'. Expected AUTO, ON, or OFF.")
- endif()
-endfunction()
-
-fltx_resolve_fast_math_mode(FLTX_ENABLE_FAST_MATH "${FLTX_FAST_MATH_MODE}")
-
-function(fltx_target_link_options_if_supported _TARGET)
- get_target_property(_TARGET_TYPE ${_TARGET} TYPE)
-
- if(_TARGET_TYPE STREQUAL "EXECUTABLE" OR
- _TARGET_TYPE STREQUAL "SHARED_LIBRARY" OR
- _TARGET_TYPE STREQUAL "MODULE_LIBRARY")
- target_link_options(${_TARGET} PRIVATE ${ARGN})
- endif()
-endfunction()
-
-function(fltx_apply_fast_math_options _TARGET)
- set(_FLTX_TARGET_FAST_MATH ${FLTX_ENABLE_FAST_MATH})
-
- if(ARGC GREATER 1)
- cmake_parse_arguments(FLTX_APPLY_FAST_MATH "" "MODE" "" ${ARGN})
- if(DEFINED FLTX_APPLY_FAST_MATH_MODE)
- fltx_resolve_fast_math_mode(_FLTX_TARGET_FAST_MATH "${FLTX_APPLY_FAST_MATH_MODE}")
- endif()
- endif()
-
- if(NOT _FLTX_TARGET_FAST_MATH)
- return()
- endif()
-
- target_compile_definitions(${_TARGET} PRIVATE
- $<$:BL_FAST_MATH>
- $<$,$>:FMA_AVAILABLE>
- $<$,$>>:FLTX_DISABLE_FMA_AVAILABLE=1>
- $<$,$>:BL_FLTX_SIMD_USE_FMA_TWO_PROD=1>
- $<$,$>>:BL_FLTX_SIMD_USE_FMA_TWO_PROD=0>
- )
-
- if(MSVC)
- target_compile_options(${_TARGET} PRIVATE
- $<$:/fp:fast>
- )
-
- if(_FLTX_FAST_MATH_ENABLE_AVX2)
- target_compile_options(${_TARGET} PRIVATE
- $<$:/arch:AVX2>
- )
- endif()
- else()
- target_compile_options(${_TARGET} PRIVATE
- $<$:-O3>
- $<$:-ffp-contract=fast>
- $<$:-fno-math-errno>
- $<$:-fno-trapping-math>
- )
-
- if(_FLTX_FAST_MATH_ENABLE_AVX2)
- target_compile_options(${_TARGET} PRIVATE
- $<$:-mavx2>
- $<$:-mfma>
- )
- endif()
-
- if(FLTX_FAST_MATH_NATIVE)
- target_compile_options(${_TARGET} PRIVATE
- $<$:-march=native>
- $<$:-mtune=native>
- )
- endif()
-
- fltx_target_link_options_if_supported(${_TARGET}
- $<$:-O3>
- )
- endif()
-endfunction()
diff --git a/cmake/fltxMsvcParallel.cmake b/cmake/fltxMsvcParallel.cmake
index 1ef45b8..47db995 100644
--- a/cmake/fltxMsvcParallel.cmake
+++ b/cmake/fltxMsvcParallel.cmake
@@ -1,5 +1,5 @@
-if(NOT DEFINED FLTX_OPTIONS_INCLUDED)
- include("${CMAKE_CURRENT_LIST_DIR}/fltxOptions.cmake")
+if(NOT DEFINED FLTX_BUILD_POLICY_INCLUDED)
+ include("${CMAKE_CURRENT_LIST_DIR}/fltxBuildPolicy.cmake")
endif()
function(fltx_enable_msvc_parallel_compile _TARGET)
diff --git a/cmake/fltxMsvcTiming.cmake b/cmake/fltxMsvcTiming.cmake
index c6f451b..4fa6ec3 100644
--- a/cmake/fltxMsvcTiming.cmake
+++ b/cmake/fltxMsvcTiming.cmake
@@ -1,5 +1,5 @@
-if(NOT DEFINED FLTX_OPTIONS_INCLUDED)
- include("${CMAKE_CURRENT_LIST_DIR}/fltxOptions.cmake")
+if(NOT DEFINED FLTX_BUILD_POLICY_INCLUDED)
+ include("${CMAKE_CURRENT_LIST_DIR}/fltxBuildPolicy.cmake")
endif()
function(fltx_msvc_target_link_options_if_supported _TARGET)
diff --git a/cmake/fltxOptions.cmake b/cmake/fltxOptions.cmake
deleted file mode 100644
index aea9c26..0000000
--- a/cmake/fltxOptions.cmake
+++ /dev/null
@@ -1,148 +0,0 @@
-include_guard(GLOBAL)
-
-set(FLTX_OPTIONS_INCLUDED TRUE)
-
-function(fltx_define_cache_bool _NAME _DEFAULT _DOC)
- set(_DEFAULT_CACHE_NAME "_${_NAME}_DEFAULT_VALUE")
-
- get_property(_HAS_VALUE CACHE "${_NAME}" PROPERTY VALUE SET)
- get_property(_HAS_OLD_DEFAULT CACHE "${_DEFAULT_CACHE_NAME}" PROPERTY VALUE SET)
-
- if(_HAS_VALUE)
- get_property(_VALUE CACHE "${_NAME}" PROPERTY VALUE)
- if(_HAS_OLD_DEFAULT)
- get_property(_OLD_DEFAULT CACHE "${_DEFAULT_CACHE_NAME}" PROPERTY VALUE)
- if(_VALUE STREQUAL _OLD_DEFAULT)
- set(_VALUE "${_DEFAULT}")
- endif()
- endif()
- else()
- set(_VALUE "${_DEFAULT}")
- endif()
-
- set(${_NAME} "${_VALUE}" CACHE BOOL "${_DOC}" FORCE)
- set(${_DEFAULT_CACHE_NAME} "${_DEFAULT}" CACHE INTERNAL "Last default value for ${_NAME}" FORCE)
-endfunction()
-
-function(fltx_define_cache_string _NAME _DEFAULT _DOC)
- set(_DEFAULT_CACHE_NAME "_${_NAME}_DEFAULT_VALUE")
-
- get_property(_HAS_VALUE CACHE "${_NAME}" PROPERTY VALUE SET)
- get_property(_HAS_OLD_DEFAULT CACHE "${_DEFAULT_CACHE_NAME}" PROPERTY VALUE SET)
-
- if(_HAS_VALUE)
- get_property(_VALUE CACHE "${_NAME}" PROPERTY VALUE)
- if(_HAS_OLD_DEFAULT)
- get_property(_OLD_DEFAULT CACHE "${_DEFAULT_CACHE_NAME}" PROPERTY VALUE)
- if(_VALUE STREQUAL _OLD_DEFAULT)
- set(_VALUE "${_DEFAULT}")
- endif()
- endif()
- else()
- set(_VALUE "${_DEFAULT}")
- endif()
-
- set(${_NAME} "${_VALUE}" CACHE STRING "${_DOC}" FORCE)
- set(${_DEFAULT_CACHE_NAME} "${_DEFAULT}" CACHE INTERNAL "Last default value for ${_NAME}" FORCE)
-
- if(ARGC GREATER 3)
- set_property(CACHE ${_NAME} PROPERTY STRINGS ${ARGN})
- endif()
-endfunction()
-
-function(fltx_import_transient_cache_value _NAME _DEFAULT)
- if(DEFINED CACHE{${_NAME}})
- set(_VALUE "${${_NAME}}")
- unset(${_NAME} CACHE)
- set(${_NAME} "${_VALUE}" PARENT_SCOPE)
- elseif(NOT DEFINED ${_NAME})
- set(${_NAME} "${_DEFAULT}" PARENT_SCOPE)
- endif()
-endfunction()
-
-fltx_define_cache_bool(
- FLTX_DEVELOPER_BUILD
- "${PROJECT_IS_TOP_LEVEL}"
- "Build fltx developer targets: tests, examples, and isolated compile units."
-)
-
-fltx_define_cache_bool(
- FLTX_SIMD_ENABLED
- ON
- "Enable fltx SIMD paths when supported by the target compiler and architecture."
-)
-
-fltx_define_cache_string(
- FLTX_FAST_MATH_MODE
- ON
- "fltx fast-math mode for compiled runtime sources: ON, OFF, or AUTO."
- ON OFF AUTO
-)
-
-fltx_define_cache_bool(
- FLTX_FAST_MATH_NATIVE
- OFF
- "Tune fast-math builds for the local host CPU with -march=native/-mtune=native where supported."
-)
-
-set(_FLTX_DEFAULT_FMA_AVAILABLE ON)
-if(EMSCRIPTEN)
- set(_FLTX_DEFAULT_FMA_AVAILABLE OFF)
-endif()
-
-fltx_define_cache_bool(
- FLTX_FMA_AVAILABLE
- "${_FLTX_DEFAULT_FMA_AVAILABLE}"
- "Use FMA-based error-free transforms where the source checks FMA_AVAILABLE."
-)
-
-set(_FLTX_SIMD_FMA_TWO_PROD_DEFAULT ON)
-if(DEFINED CACHE{FLTX_F256_FMA_TWO_PROD} AND NOT DEFINED CACHE{FLTX_SIMD_FMA_TWO_PROD})
- get_property(_FLTX_SIMD_FMA_TWO_PROD_DEFAULT CACHE FLTX_F256_FMA_TWO_PROD PROPERTY VALUE)
-endif()
-
-fltx_define_cache_bool(
- FLTX_SIMD_FMA_TWO_PROD
- "${_FLTX_SIMD_FMA_TWO_PROD_DEFAULT}"
- "Use hardware FMA for SIMD two-product error terms when available."
-)
-
-if(DEFINED CACHE{FLTX_F256_FMA_TWO_PROD})
- unset(FLTX_F256_FMA_TWO_PROD CACHE)
-endif()
-
-fltx_define_cache_bool(
- FLTX_MSVC_TIMING_REPORTS
- "${FLTX_DEVELOPER_BUILD}"
- "Emit MSVC compiler and linker timing reports for fltx developer targets."
-)
-
-fltx_define_cache_bool(
- FLTX_MSVC_PARALLEL_COMPILE
- "${FLTX_DEVELOPER_BUILD}"
- "Enable MSVC /MP multi-processor compilation for eligible fltx targets."
-)
-
-fltx_define_cache_bool(
- FLTX_MSVC_DETAILED_TIMING_REPORTS
- OFF
- "Emit detailed MSVC frontend and codegen timing reports for fltx developer targets."
-)
-
-fltx_define_cache_bool(
- VERBOSE_TESTS
- ON
- "Print metrics console tables while running metrics_tests."
-)
-
-mark_as_advanced(
- FLTX_FAST_MATH_NATIVE
- FLTX_FMA_AVAILABLE
- FLTX_SIMD_FMA_TWO_PROD
- FLTX_MSVC_TIMING_REPORTS
- FLTX_MSVC_PARALLEL_COMPILE
- FLTX_MSVC_DETAILED_TIMING_REPORTS
-)
-
-fltx_import_transient_cache_value(FLTX_PRECISION_TESTS_CONSTEXPR_PARITY OFF)
-fltx_import_transient_cache_value(FLTX_PRECISION_TESTS_SIMULATE_CONSTEVAL OFF)
diff --git a/cmake/fltxSimd.cmake b/cmake/fltxSimd.cmake
deleted file mode 100644
index 2319b42..0000000
--- a/cmake/fltxSimd.cmake
+++ /dev/null
@@ -1,37 +0,0 @@
-if(NOT DEFINED FLTX_OPTIONS_INCLUDED)
- include("${CMAKE_CURRENT_LIST_DIR}/fltxOptions.cmake")
-endif()
-
-function(fltx_apply_simd_options _TARGET)
- set(_FLTX_SIMD_VISIBILITY PRIVATE)
- if(ARGC GREATER 1)
- set(_FLTX_SIMD_VISIBILITY "${ARGV1}")
- endif()
-
- if(EMSCRIPTEN)
- target_compile_options(${_TARGET} ${_FLTX_SIMD_VISIBILITY} -msimd128)
-
- get_target_property(_FLTX_SIMD_TARGET_TYPE ${_TARGET} TYPE)
-
- if(_FLTX_SIMD_TARGET_TYPE STREQUAL "STATIC_LIBRARY")
- if(_FLTX_SIMD_VISIBILITY STREQUAL "PUBLIC" OR
- _FLTX_SIMD_VISIBILITY STREQUAL "INTERFACE")
- target_link_options(${_TARGET} INTERFACE -msimd128)
- endif()
- elseif(_FLTX_SIMD_TARGET_TYPE STREQUAL "EXECUTABLE" OR
- _FLTX_SIMD_TARGET_TYPE STREQUAL "SHARED_LIBRARY" OR
- _FLTX_SIMD_TARGET_TYPE STREQUAL "MODULE_LIBRARY")
- target_link_options(${_TARGET} ${_FLTX_SIMD_VISIBILITY} -msimd128)
- endif()
- endif()
-
- if(NOT FLTX_SIMD_ENABLED)
- target_compile_definitions(${_TARGET} ${_FLTX_SIMD_VISIBILITY}
- BL_F128_ENABLE_SIMD=0
- BL_F256_ENABLE_SIMD=0
- BL_F256_ENABLE_TRIG_SIMD=0
- )
- return()
- endif()
-
-endfunction()
diff --git a/cmake/fltxSources.cmake b/cmake/fltxSources.cmake
index 87cfdbe..77db1e1 100644
--- a/cmake/fltxSources.cmake
+++ b/cmake/fltxSources.cmake
@@ -1,10 +1,13 @@
set(FLTX_LIBRARY_SOURCE_FILES
+ src/build_info.cpp
+ src/fltx_io.cpp
src/f128.cpp
src/f128_math.cpp
src/f128_transcendental.cpp
src/f256.cpp
src/f256_math.cpp
src/f256_transcendental.cpp
+ src/fma_x86.cpp
)
get_filename_component(FLTX_PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
diff --git a/cmake/validation/fltxValidationCommon.cmake b/cmake/validation/fltxValidationCommon.cmake
new file mode 100644
index 0000000..f1ab9fb
--- /dev/null
+++ b/cmake/validation/fltxValidationCommon.cmake
@@ -0,0 +1,191 @@
+# Shared configuration for validation executables and libraries.
+
+set(FLTX_VALIDATION_EXPECTED_ARCHITECTURE "" CACHE STRING
+ "Expected validation target architecture (x86_64, arm64, or wasm32)."
+)
+set_property(CACHE FLTX_VALIDATION_EXPECTED_ARCHITECTURE PROPERTY STRINGS
+ "" x86_64 arm64 wasm32
+)
+if(NOT FLTX_VALIDATION_EXPECTED_ARCHITECTURE STREQUAL "" AND
+ NOT FLTX_VALIDATION_EXPECTED_ARCHITECTURE MATCHES "^(x86_64|arm64|wasm32)$")
+ message(FATAL_ERROR
+ "Unsupported FLTX_VALIDATION_EXPECTED_ARCHITECTURE="
+ "'${FLTX_VALIDATION_EXPECTED_ARCHITECTURE}'."
+ )
+endif()
+
+function(fltx_tests_build_configuration_identity config output_hash output_optimized)
+ string(TOUPPER "${config}" config_upper)
+ set(compile_flags
+ "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${config_upper}}"
+ )
+ set(link_flags
+ "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS_${config_upper}}"
+ )
+ set(identity
+ "compiler=${CMAKE_CXX_COMPILER}|id=${CMAKE_CXX_COMPILER_ID}|"
+ "version=${CMAKE_CXX_COMPILER_VERSION}|generator=${CMAKE_GENERATOR}|"
+ "config=${config}|cxx=${compile_flags}|link=${link_flags}|"
+ "ipo=${CMAKE_INTERPROCEDURAL_OPTIMIZATION_${config_upper}}"
+ )
+ string(SHA256 flags_hash "${identity}")
+
+ # Read the flags in command-line order so an explicit trailing /Od or -O0
+ # cannot be published merely because the configuration is named Release.
+ separate_arguments(flag_tokens NATIVE_COMMAND "${compile_flags}")
+ set(optimized 0)
+ foreach(flag IN LISTS flag_tokens)
+ if(MSVC)
+ if(flag MATCHES "^[-/]Od$")
+ set(optimized 0)
+ elseif(flag MATCHES "^[-/]O(1|2|x)$")
+ set(optimized 1)
+ endif()
+ else()
+ if(flag MATCHES "^-O0$" OR flag MATCHES "^-Og$")
+ set(optimized 0)
+ elseif(flag MATCHES "^-O([1-3s]|fast)$" OR flag STREQUAL "-O")
+ set(optimized 1)
+ endif()
+ endif()
+ endforeach()
+
+ set(${output_hash} "${flags_hash}" PARENT_SCOPE)
+ set(${output_optimized} "${optimized}" PARENT_SCOPE)
+endfunction()
+
+foreach(config Debug Release RelWithDebInfo MinSizeRel)
+ string(TOUPPER "${config}" config_upper)
+ fltx_tests_build_configuration_identity(
+ "${config}"
+ "FLTX_TESTS_FLAGS_HASH_${config_upper}"
+ "FLTX_TESTS_OPTIMIZED_${config_upper}"
+ )
+endforeach()
+
+function(fltx_tests_add_build_identity target)
+ string(CONCAT flags_hash
+ "$,${FLTX_TESTS_FLAGS_HASH_RELEASE},"
+ "$,${FLTX_TESTS_FLAGS_HASH_RELWITHDEBINFO},"
+ "$,${FLTX_TESTS_FLAGS_HASH_MINSIZEREL},"
+ "${FLTX_TESTS_FLAGS_HASH_DEBUG}>>>"
+ )
+ string(CONCAT optimized
+ "$,${FLTX_TESTS_OPTIMIZED_RELEASE},"
+ "$,${FLTX_TESTS_OPTIMIZED_RELWITHDEBINFO},"
+ "$,${FLTX_TESTS_OPTIMIZED_MINSIZEREL},"
+ "${FLTX_TESTS_OPTIMIZED_DEBUG}>>>"
+ )
+ target_compile_definitions(${target} PRIVATE
+ "FLTX_TESTS_COMPILER_ID=\"${CMAKE_CXX_COMPILER_ID}\""
+ "FLTX_TESTS_COMPILER_VERSION=\"${CMAKE_CXX_COMPILER_VERSION}\""
+ "FLTX_TESTS_COMPILER_FRONTEND_VARIANT=\"${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}\""
+ "FLTX_TESTS_BUILD_CONFIG=\"$\""
+ "FLTX_TESTS_SYSTEM_NAME=\"${CMAKE_SYSTEM_NAME}\""
+ "FLTX_TESTS_SYSTEM_PROCESSOR=\"${CMAKE_SYSTEM_PROCESSOR}\""
+ "FLTX_TESTS_BUILD_FLAGS_HASH=\"${flags_hash}\""
+ "FLTX_TESTS_BUILD_OPTIMIZED=${optimized}"
+ )
+ if(FLTX_VALIDATION_EXPECTED_ARCHITECTURE STREQUAL "x86_64")
+ target_compile_definitions(${target} PRIVATE FLTX_TESTS_EXPECT_X86_64=1)
+ elseif(FLTX_VALIDATION_EXPECTED_ARCHITECTURE STREQUAL "arm64")
+ target_compile_definitions(${target} PRIVATE FLTX_TESTS_EXPECT_ARM64=1)
+ elseif(FLTX_VALIDATION_EXPECTED_ARCHITECTURE STREQUAL "wasm32")
+ target_compile_definitions(${target} PRIVATE FLTX_TESTS_EXPECT_WASM32=1)
+ endif()
+endfunction()
+
+function(fltx_tests_prepare_target target)
+ target_compile_features(${target} PRIVATE cxx_std_20)
+ fltx_tests_add_build_identity(${target})
+
+ if(MSVC)
+ target_compile_options(${target} PRIVATE /bigobj)
+ elseif(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(${target} PRIVATE -Wa,-mbig-obj)
+ endif()
+
+ fltx_configure_public_header_contract(${target} PRIVATE)
+ fltx_apply_private_wasm_simd_options(${target})
+ fltx_enable_msvc_parallel_compile(${target})
+endfunction()
+
+function(fltx_tests_configure_consumer_mode target mode)
+ string(TOUPPER "${mode}" normalized_mode)
+ if(normalized_mode STREQUAL "ON")
+ set(expected_fast_math 1)
+ elseif(normalized_mode STREQUAL "OFF")
+ set(expected_fast_math 0)
+ else()
+ message(FATAL_ERROR
+ "Invalid validation consumer fast-math mode '${mode}'."
+ )
+ endif()
+
+ fltx_configure_consumer_fast_math(${target} MODE "${normalized_mode}")
+ target_compile_definitions(${target} PRIVATE
+ "FLTX_TESTS_EXPECT_CONSUMER_FAST_MATH=${expected_fast_math}"
+ )
+endfunction()
+
+function(fltx_tests_prepare_test target label library)
+ target_link_libraries(${target} PRIVATE ${library} Catch2::Catch2)
+ target_include_directories(${target} PRIVATE "${FLTX_TESTS_SUPPORT_DIR}")
+ target_compile_definitions(${target} PRIVATE "FLTX_TESTS_NAME=\"${label}\"")
+ fltx_tests_prepare_target(${target})
+
+ if(EMSCRIPTEN)
+ # Catch2 must be able to observe public APIs which report errors by
+ # throwing. Emscripten disables exception catching unless the final
+ # application explicitly opts in.
+ target_compile_options(${target} PRIVATE -fexceptions)
+ target_link_options(${target} PRIVATE -fexceptions)
+ endif()
+
+ if(BUILD_TESTING)
+ if(EMSCRIPTEN)
+ add_test(NAME ${target} COMMAND ${target})
+ set_tests_properties(${target} PROPERTIES
+ LABELS "fltx;${label}"
+ )
+ else()
+ catch_discover_tests(
+ ${target}
+ TEST_PREFIX "${target}::"
+ # catch_discover_tests forwards PROPERTIES as a list. A
+ # semicolon-separated label value is therefore emitted as
+ # separate property arguments; keep the suite label singular
+ # so `ctest -L` reliably selects these discovered cases.
+ PROPERTIES LABELS "${label}"
+ DISCOVERY_MODE POST_BUILD
+ )
+ endif()
+ endif()
+endfunction()
+
+function(fltx_tests_prepare_runner target)
+ set(library fltx::fltx)
+ if(ARGC GREATER 1)
+ set(library "${ARGV1}")
+ endif()
+
+ target_link_libraries(${target} PRIVATE ${library})
+ fltx_tests_prepare_target(${target})
+
+ if(EMSCRIPTEN)
+ # Boost's libc++ integration currently uses deprecated allocator
+ # members under modern standard modes. Keep that third-party noise out
+ # of metrics builds without weakening warnings for the library itself.
+ target_compile_options(${target} PRIVATE
+ -fexceptions
+ -Wno-deprecated-declarations
+ )
+ target_link_options(${target} PRIVATE
+ -fexceptions
+ -sNODERAWFS=1
+ -sALLOW_MEMORY_GROWTH=1
+ -sINITIAL_MEMORY=268435456
+ -sMAXIMUM_MEMORY=1073741824
+ )
+ endif()
+endfunction()
diff --git a/cmake/validation/fltxValidationDependencies.cmake b/cmake/validation/fltxValidationDependencies.cmake
new file mode 100644
index 0000000..43d1ef0
--- /dev/null
+++ b/cmake/validation/fltxValidationDependencies.cmake
@@ -0,0 +1,90 @@
+# Validation-only numerical oracles and comparison libraries.
+
+find_path(FLTX_TESTS_MPFR_INCLUDE_DIR NAMES mpfr.h REQUIRED)
+find_library(FLTX_TESTS_MPFR_LIBRARY NAMES mpfr REQUIRED)
+find_path(FLTX_TESTS_GMP_INCLUDE_DIR NAMES gmp.h REQUIRED)
+find_library(FLTX_TESTS_GMP_LIBRARY NAMES gmp REQUIRED)
+
+option(FLTX_METRICS_EXTERNAL_COMPARISONS
+ "Build external implementations used only by metrics comparisons."
+ ON
+)
+
+option(FLTX_METRICS_TLFLOAT
+ "Build the vendored TLFloat dependency for metrics comparisons."
+ ON
+)
+
+set(FLTX_METRICS_TLFLOAT_CONTRACT_TARGET)
+if(FLTX_METRICS_EXTERNAL_COMPARISONS AND FLTX_METRICS_TLFLOAT)
+ set(FLTX_METRICS_TLFLOAT_SOURCE_DIR
+ "${FLTX_VALIDATION_SOURCE_DIR}/extern/tlfloat"
+ )
+ set(FLTX_METRICS_TLFLOAT_BINARY_DIR
+ "${FLTX_VALIDATION_BINARY_DIR}/extern/tlfloat"
+ )
+
+ if(NOT EXISTS "${FLTX_METRICS_TLFLOAT_SOURCE_DIR}/CMakeLists.txt")
+ message(FATAL_ERROR
+ "FLTX_METRICS_TLFLOAT is ON, but validation/extern/tlfloat is unavailable."
+ )
+ endif()
+
+ # TLFloat's standalone project uses intentionally broad option names.
+ # Keep them in this function scope so they cannot become fltx options or
+ # alter the parent build. Only its static library is needed here.
+ function(fltx_tests_add_tlfloat)
+ set(BUILD_LIBS ON)
+ set(BUILD_TESTS OFF)
+ set(BUILD_UTILS OFF)
+ set(BUILD_BENCH OFF)
+ set(BUILD_SHARED_LIBS OFF)
+ set(ENABLE_DOXYGEN OFF)
+ set(ENABLE_COVERAGE OFF)
+ set(BUILD_EXHAUSTIVE_TESTING OFF)
+ set(ENABLE_EXHAUSTIVE_TESTING OFF)
+ add_subdirectory(
+ "${FLTX_METRICS_TLFLOAT_SOURCE_DIR}"
+ "${FLTX_METRICS_TLFLOAT_BINARY_DIR}"
+ EXCLUDE_FROM_ALL
+ )
+ endfunction()
+ fltx_tests_add_tlfloat()
+
+ set_target_properties(tlfloat PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${FLTX_METRICS_TLFLOAT_BINARY_DIR}/lib"
+ LIBRARY_OUTPUT_DIRECTORY "${FLTX_METRICS_TLFLOAT_BINARY_DIR}/lib"
+ RUNTIME_OUTPUT_DIRECTORY "${FLTX_METRICS_TLFLOAT_BINARY_DIR}/bin"
+ )
+ set_target_properties(tlfloat_inline PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${FLTX_METRICS_TLFLOAT_BINARY_DIR}/lib"
+ )
+
+ add_library(fltx_tlfloat_dependency INTERFACE)
+ target_link_libraries(fltx_tlfloat_dependency INTERFACE tlfloat)
+ target_include_directories(fltx_tlfloat_dependency INTERFACE
+ "${FLTX_METRICS_TLFLOAT_SOURCE_DIR}/src/include"
+ "${FLTX_METRICS_TLFLOAT_BINARY_DIR}/include"
+ )
+ add_library(fltx::test_tlfloat ALIAS fltx_tlfloat_dependency)
+
+ add_executable(fltx_tlfloat_smoke
+ "${FLTX_VALIDATION_SOURCE_DIR}/support/tlfloat_smoke.cpp"
+ )
+ target_link_libraries(fltx_tlfloat_smoke PRIVATE
+ fltx::test_tlfloat
+ )
+ target_compile_features(fltx_tlfloat_smoke PRIVATE cxx_std_20)
+ set(FLTX_METRICS_TLFLOAT_CONTRACT_TARGET fltx_tlfloat_smoke)
+
+ if(BUILD_TESTING)
+ add_test(NAME fltx.tlfloat.link COMMAND fltx_tlfloat_smoke)
+ set_tests_properties(fltx.tlfloat.link PROPERTIES
+ LABELS "fltx;metrics;tlfloat"
+ )
+ endif()
+
+ message(STATUS
+ "fltx metrics: TLFloat dependency enabled from validation/extern/tlfloat."
+ )
+endif()
diff --git a/cmake/validation/fltxValidationPackage.cmake b/cmake/validation/fltxValidationPackage.cmake
new file mode 100644
index 0000000..98bfeb9
--- /dev/null
+++ b/cmake/validation/fltxValidationPackage.cmake
@@ -0,0 +1,93 @@
+# In-tree and installed-package consumer validation.
+
+# This quick in-tree executable is useful while editing the consumer. The release
+# target below runs the authoritative installed find_package fixture.
+add_executable(fltx_package_smoke
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/package/consumer.cpp"
+)
+target_link_libraries(fltx_package_smoke PRIVATE fltx::fltx)
+target_compile_features(fltx_package_smoke PRIVATE cxx_std_20)
+fltx_tests_add_build_identity(fltx_package_smoke)
+
+if(BUILD_TESTING)
+ add_test(NAME fltx.package.in_tree COMMAND fltx_package_smoke)
+ set_tests_properties(fltx.package.in_tree PROPERTIES LABELS "fltx;package")
+endif()
+
+set(FLTX_TESTS_PACKAGE_INSTALL_DIR
+ "${FLTX_VALIDATION_BINARY_DIR}/package_install"
+)
+string(MAKE_C_IDENTIFIER
+ "${CMAKE_SYSTEM_NAME}_${CMAKE_CXX_COMPILER_ID}"
+ FLTX_TESTS_PACKAGE_BUILD_ID
+)
+set(FLTX_TESTS_PACKAGE_BUILD_DIR
+ "${FLTX_VALIDATION_BINARY_DIR}/package_build_${FLTX_TESTS_PACKAGE_BUILD_ID}"
+)
+set(FLTX_TESTS_PACKAGE_GENERATOR_ARGS -G "${CMAKE_GENERATOR}")
+if(CMAKE_GENERATOR_PLATFORM)
+ list(APPEND FLTX_TESTS_PACKAGE_GENERATOR_ARGS -A "${CMAKE_GENERATOR_PLATFORM}")
+endif()
+if(CMAKE_GENERATOR_TOOLSET)
+ list(APPEND FLTX_TESTS_PACKAGE_GENERATOR_ARGS -T "${CMAKE_GENERATOR_TOOLSET}")
+endif()
+
+set(FLTX_TESTS_PACKAGE_TOOLCHAIN_ARGS)
+if(EMSCRIPTEN)
+ if(NOT VCPKG_CHAINLOAD_TOOLCHAIN_FILE)
+ message(FATAL_ERROR
+ "fltx package tests require the Emscripten chainload toolchain path."
+ )
+ endif()
+ list(APPEND FLTX_TESTS_PACKAGE_TOOLCHAIN_ARGS
+ "-DCMAKE_TOOLCHAIN_FILE=${VCPKG_CHAINLOAD_TOOLCHAIN_FILE}"
+ "-DCMAKE_CROSSCOMPILING_EMULATOR=${CMAKE_CROSSCOMPILING_EMULATOR}"
+ )
+else()
+ list(APPEND FLTX_TESTS_PACKAGE_TOOLCHAIN_ARGS -U CMAKE_TOOLCHAIN_FILE)
+endif()
+if(NOT EMSCRIPTEN AND NOT CMAKE_GENERATOR MATCHES "Visual Studio|Xcode")
+ list(APPEND FLTX_TESTS_PACKAGE_TOOLCHAIN_ARGS
+ "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
+ )
+ if(CMAKE_MAKE_PROGRAM)
+ list(APPEND FLTX_TESTS_PACKAGE_TOOLCHAIN_ARGS
+ "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
+ )
+ endif()
+endif()
+set(FLTX_TESTS_PACKAGE_BUILD_TYPE_ARG)
+if(NOT CMAKE_CONFIGURATION_TYPES)
+ set(FLTX_TESTS_PACKAGE_BUILD_TYPE_ARG "-DCMAKE_BUILD_TYPE=$")
+endif()
+
+add_custom_target(fltx_package_tests
+ # These directories belong only to this fixture. Recreate both on every
+ # invocation so removed headers, targets, and compile definitions cannot
+ # survive in the install tree or nested CMake cache.
+ COMMAND "${CMAKE_COMMAND}" -E rm -rf
+ "${FLTX_TESTS_PACKAGE_INSTALL_DIR}"
+ "${FLTX_TESTS_PACKAGE_BUILD_DIR}"
+ COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}"
+ --config "$"
+ --prefix "${FLTX_TESTS_PACKAGE_INSTALL_DIR}"
+ COMMAND "${CMAKE_COMMAND}"
+ -S "${FLTX_VALIDATION_SOURCE_DIR}/tests/package"
+ -B "${FLTX_TESTS_PACKAGE_BUILD_DIR}"
+ ${FLTX_TESTS_PACKAGE_GENERATOR_ARGS}
+ ${FLTX_TESTS_PACKAGE_TOOLCHAIN_ARGS}
+ "-DCMAKE_PREFIX_PATH=${FLTX_TESTS_PACKAGE_INSTALL_DIR}"
+ "-Dfltx_DIR=${FLTX_TESTS_PACKAGE_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/fltx"
+ ${FLTX_TESTS_PACKAGE_BUILD_TYPE_ARG}
+ -DBUILD_TESTING=ON
+ COMMAND "${CMAKE_COMMAND}" --build "${FLTX_TESTS_PACKAGE_BUILD_DIR}"
+ --config "$"
+ COMMAND "${CMAKE_CTEST_COMMAND}"
+ --test-dir "${FLTX_TESTS_PACKAGE_BUILD_DIR}"
+ --build-config "$"
+ --output-on-failure
+ DEPENDS fltx fltx_package_smoke
+ USES_TERMINAL
+ COMMENT "Testing installed fltx package consumers"
+ VERBATIM
+)
diff --git a/cmake/validation/fltxValidationRunners.cmake b/cmake/validation/fltxValidationRunners.cmake
new file mode 100644
index 0000000..e6c563b
--- /dev/null
+++ b/cmake/validation/fltxValidationRunners.cmake
@@ -0,0 +1,406 @@
+# Accuracy, benchmark, source-identity, and smoke-runner targets.
+
+set(FLTX_ACCURACY_SOURCE_FILE_PATHS
+ "${FLTX_VALIDATION_SOURCE_DIR}/accuracy/main.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/accuracy/f128.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/accuracy/f256.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/accuracy/native.cpp"
+)
+
+function(fltx_tests_add_accuracy_runner
+ target library simulated_consteval consumer_fast_math)
+ add_executable(${target} ${FLTX_ACCURACY_SOURCE_FILE_PATHS})
+ fltx_tests_prepare_runner(${target} ${library})
+ fltx_tests_configure_consumer_mode(${target} "${consumer_fast_math}")
+ target_compile_definitions(${target} PRIVATE
+ "FLTX_ACCURACY_EXPECTS_SIMULATED_CONSTEVAL=${simulated_consteval}"
+ )
+endfunction()
+
+fltx_tests_add_accuracy_runner(fltx_accuracy fltx::fltx 0 OFF)
+fltx_tests_add_accuracy_runner(fltx_ci_accuracy fltx::fltx 0 OFF)
+fltx_tests_add_accuracy_runner(
+ fltx_constexpr_accuracy
+ fltx_constexpr_accuracy_lib
+ 1
+ OFF
+)
+fltx_tests_add_accuracy_runner(fltx_accuracy_fastmath fltx::fltx 0 ON)
+fltx_tests_add_accuracy_runner(
+ fltx_constexpr_accuracy_fastmath
+ fltx_constexpr_accuracy_lib
+ 1
+ ON
+)
+
+# Match genuine constant evaluation's uncontracted arithmetic in the strict
+# fixed-simulation consumer. The fast-math simulation remains diagnostic and
+# deliberately retains its consumer compiler policy.
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(fltx_constexpr_accuracy PRIVATE
+ -ffp-contract=off
+ )
+endif()
+
+set(FLTX_ACCURACY_TARGETS
+ fltx_accuracy
+ fltx_ci_accuracy
+ fltx_constexpr_accuracy
+ fltx_accuracy_fastmath
+ fltx_constexpr_accuracy_fastmath
+)
+set(FLTX_RUNTIME_ACCURACY_TARGETS
+ fltx_accuracy
+ fltx_ci_accuracy
+ fltx_accuracy_fastmath
+)
+set(FLTX_CONSTEXPR_ACCURACY_TARGETS
+ fltx_constexpr_accuracy
+ fltx_constexpr_accuracy_fastmath
+)
+foreach(target IN LISTS FLTX_ACCURACY_TARGETS)
+ target_include_directories(${target} PRIVATE
+ "${FLTX_TESTS_MPFR_INCLUDE_DIR}"
+ "${FLTX_TESTS_GMP_INCLUDE_DIR}"
+ )
+ target_link_libraries(${target} PRIVATE
+ "${FLTX_TESTS_MPFR_LIBRARY}"
+ "${FLTX_TESTS_GMP_LIBRARY}"
+ )
+endforeach()
+
+if(FLTX_METRICS_EXTERNAL_COMPARISONS)
+ target_compile_definitions(fltx_accuracy PRIVATE
+ FLTX_TESTS_ENABLE_EXTERNAL_COMPARISONS=1
+ )
+else()
+ target_compile_definitions(fltx_accuracy PRIVATE
+ FLTX_TESTS_ENABLE_EXTERNAL_COMPARISONS=0
+ )
+endif()
+foreach(target
+ fltx_ci_accuracy
+ fltx_constexpr_accuracy
+ fltx_accuracy_fastmath
+ fltx_constexpr_accuracy_fastmath)
+ target_compile_definitions(${target} PRIVATE
+ FLTX_TESTS_ENABLE_EXTERNAL_COMPARISONS=0
+ )
+endforeach()
+
+if(FLTX_METRICS_EXTERNAL_COMPARISONS AND FLTX_METRICS_TLFLOAT)
+ target_link_libraries(fltx_accuracy PRIVATE fltx::test_tlfloat)
+ target_compile_definitions(fltx_accuracy PRIVATE FLTX_METRICS_HAS_TLFLOAT=1)
+else()
+ target_compile_definitions(fltx_accuracy PRIVATE FLTX_METRICS_HAS_TLFLOAT=0)
+endif()
+foreach(target
+ fltx_ci_accuracy
+ fltx_accuracy_fastmath
+ fltx_constexpr_accuracy
+ fltx_constexpr_accuracy_fastmath)
+ target_compile_definitions(${target} PRIVATE FLTX_METRICS_HAS_TLFLOAT=0)
+endforeach()
+
+set(FLTX_BENCHMARK_SOURCE_FILE_PATHS
+ "${FLTX_VALIDATION_SOURCE_DIR}/benchmarks/runtime/main.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/benchmarks/runtime/operations.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/benchmarks/runtime/workloads.cpp"
+)
+
+function(fltx_tests_add_benchmark_runner target consumer_fast_math)
+ add_executable(${target} ${FLTX_BENCHMARK_SOURCE_FILE_PATHS})
+ fltx_tests_prepare_runner(${target})
+ fltx_tests_configure_consumer_mode(${target} "${consumer_fast_math}")
+ target_include_directories(${target} PRIVATE
+ "${FLTX_TESTS_MPFR_INCLUDE_DIR}"
+ "${FLTX_TESTS_GMP_INCLUDE_DIR}"
+ )
+ target_link_libraries(${target} PRIVATE
+ "${FLTX_TESTS_MPFR_LIBRARY}"
+ "${FLTX_TESTS_GMP_LIBRARY}"
+ )
+ if(FLTX_METRICS_EXTERNAL_COMPARISONS AND
+ FLTX_METRICS_TLFLOAT AND NOT consumer_fast_math)
+ target_link_libraries(${target} PRIVATE fltx::test_tlfloat)
+ target_compile_definitions(${target} PRIVATE FLTX_METRICS_HAS_TLFLOAT=1)
+ else()
+ target_compile_definitions(${target} PRIVATE FLTX_METRICS_HAS_TLFLOAT=0)
+ endif()
+endfunction()
+
+fltx_tests_add_benchmark_runner(fltx_benchmark OFF)
+fltx_tests_add_benchmark_runner(fltx_benchmark_fastmath ON)
+
+if(FLTX_METRICS_EXTERNAL_COMPARISONS)
+ target_compile_definitions(fltx_benchmark PRIVATE
+ FLTX_TESTS_ENABLE_EXTERNAL_COMPARISONS=1
+ )
+else()
+ target_compile_definitions(fltx_benchmark PRIVATE
+ FLTX_TESTS_ENABLE_EXTERNAL_COMPARISONS=0
+ )
+endif()
+target_compile_definitions(fltx_benchmark_fastmath PRIVATE
+ FLTX_TESTS_ENABLE_EXTERNAL_COMPARISONS=0
+)
+
+set(FLTX_BENCHMARK_TARGETS fltx_benchmark fltx_benchmark_fastmath)
+
+set(FLTX_METRICS_BUNDLED_QDPP_INCLUDE_DIR
+ "${FLTX_VALIDATION_SOURCE_DIR}/extern/qdpp/include"
+)
+option(FLTX_METRICS_QDPP
+ "Build qdpp-backed accuracy and benchmark comparisons."
+ ON
+)
+set(FLTX_METRICS_QDPP_INCLUDE_DIR
+ "${FLTX_METRICS_BUNDLED_QDPP_INCLUDE_DIR}"
+ CACHE PATH "qdpp include directory used by fltx metrics."
+)
+if(FLTX_METRICS_QDPP_INCLUDE_DIR STREQUAL "")
+ set(FLTX_METRICS_QDPP_INCLUDE_DIR
+ "${FLTX_METRICS_BUNDLED_QDPP_INCLUDE_DIR}"
+ CACHE PATH "qdpp include directory used by fltx metrics."
+ FORCE
+ )
+endif()
+
+set(FLTX_METRICS_QDPP_CONTRACT_TARGET)
+if(FLTX_METRICS_EXTERNAL_COMPARISONS AND FLTX_METRICS_QDPP)
+ if(EXISTS "${FLTX_METRICS_QDPP_INCLUDE_DIR}/qd/dd.h" AND
+ EXISTS "${FLTX_METRICS_QDPP_INCLUDE_DIR}/qd/qd_real.h")
+ foreach(target fltx_accuracy fltx_benchmark)
+ target_include_directories(${target} PRIVATE
+ "${FLTX_METRICS_QDPP_INCLUDE_DIR}"
+ )
+ target_compile_definitions(${target} PRIVATE
+ FLTX_METRICS_HAS_QDPP=1
+ FLTX_METRICS_QDPP_ENABLED=1
+ )
+ endforeach()
+
+ # Consumer-fast-math reports intentionally contain FLTX rows only.
+ # Do not parse qdpp headers in those translation units: qdpp rejects
+ # aggressive fast-math at compile time even when its rows are disabled.
+ foreach(target fltx_accuracy_fastmath fltx_benchmark_fastmath)
+ target_compile_definitions(${target} PRIVATE
+ FLTX_METRICS_HAS_QDPP=0
+ FLTX_METRICS_QDPP_ENABLED=0
+ )
+ endforeach()
+
+ add_executable(fltx_qdpp_smoke
+ "${FLTX_VALIDATION_SOURCE_DIR}/support/qdpp_smoke.cpp"
+ )
+ target_include_directories(fltx_qdpp_smoke PRIVATE
+ "${FLTX_METRICS_QDPP_INCLUDE_DIR}"
+ )
+ target_compile_features(fltx_qdpp_smoke PRIVATE cxx_std_20)
+ set(FLTX_METRICS_QDPP_CONTRACT_TARGET fltx_qdpp_smoke)
+
+ if(BUILD_TESTING)
+ add_test(NAME fltx.qdpp.smoke COMMAND fltx_qdpp_smoke)
+ set_tests_properties(fltx.qdpp.smoke PROPERTIES
+ LABELS "fltx;metrics;qdpp"
+ )
+ endif()
+
+ message(STATUS
+ "fltx metrics: qdpp dependency enabled from "
+ "${FLTX_METRICS_QDPP_INCLUDE_DIR}."
+ )
+ else()
+ message(FATAL_ERROR
+ "FLTX_METRICS_QDPP is ON, but FLTX_METRICS_QDPP_INCLUDE_DIR does not "
+ "contain qd/dd.h and qd/qd_real.h."
+ )
+ endif()
+else()
+ foreach(target IN LISTS FLTX_RUNTIME_ACCURACY_TARGETS FLTX_BENCHMARK_TARGETS)
+ target_compile_definitions(${target} PRIVATE
+ FLTX_METRICS_HAS_QDPP=0
+ FLTX_METRICS_QDPP_ENABLED=0
+ )
+ endforeach()
+ if(FLTX_METRICS_EXTERNAL_COMPARISONS)
+ message(STATUS
+ "fltx metrics: qdpp explicitly disabled; Boost comparisons remain enabled."
+ )
+ else()
+ message(STATUS
+ "fltx metrics: external comparisons disabled for this build."
+ )
+ endif()
+endif()
+foreach(target IN LISTS FLTX_CONSTEXPR_ACCURACY_TARGETS)
+ target_compile_definitions(${target} PRIVATE
+ FLTX_METRICS_HAS_QDPP=0
+ FLTX_METRICS_QDPP_ENABLED=0
+ )
+endforeach()
+target_compile_definitions(fltx_ci_accuracy PRIVATE
+ FLTX_METRICS_HAS_QDPP=0
+ FLTX_METRICS_QDPP_ENABLED=0
+)
+
+# Recompute this header on every metrics build. The Python helper only touches
+# it when the source identity changes, so normal incremental builds stay cheap
+# while a stale runner cannot masquerade as a newer checkout.
+set(FLTX_METRICS_SOURCE_IDENTITY_DIR
+ "${FLTX_VALIDATION_BINARY_DIR}/generated/source_identity"
+)
+set(FLTX_METRICS_SOURCE_IDENTITY_HEADER
+ "${FLTX_METRICS_SOURCE_IDENTITY_DIR}/fltx_source_identity.generated.hpp"
+)
+add_custom_target(fltx_source_identity
+ COMMAND "${Python3_EXECUTABLE}"
+ "${PROJECT_SOURCE_DIR}/validation/metrics/_internal/source_fingerprint.py"
+ --root "${PROJECT_SOURCE_DIR}"
+ --output "${FLTX_METRICS_SOURCE_IDENTITY_HEADER}"
+ BYPRODUCTS "${FLTX_METRICS_SOURCE_IDENTITY_HEADER}"
+ COMMENT "Refreshing fltx source fingerprint"
+ VERBATIM
+)
+foreach(target IN LISTS FLTX_ACCURACY_TARGETS FLTX_BENCHMARK_TARGETS)
+ add_dependencies(${target} fltx_source_identity)
+ target_include_directories(${target} PRIVATE
+ "${FLTX_METRICS_SOURCE_IDENTITY_DIR}"
+ )
+ target_compile_definitions(${target} PRIVATE
+ FLTX_METRICS_EMBED_SOURCE_FINGERPRINT=1
+ )
+endforeach()
+
+function(fltx_tests_add_native_accuracy_target
+ target runner output_name label consumer_mode)
+ add_custom_target(${target}
+ COMMAND "${Python3_EXECUTABLE}"
+ "${PROJECT_SOURCE_DIR}/validation/metrics/_internal/run_native_accuracy.py"
+ --accuracy "$"
+ --output-root "${FLTX_VALIDATION_BINARY_DIR}/${output_name}"
+ --sample-mode full
+ --consumer-mode "${consumer_mode}"
+ DEPENDS ${runner}
+ USES_TERMINAL
+ COMMENT "Running complete ${label} f32/f64 accuracy domains"
+ VERBATIM
+ )
+endfunction()
+
+fltx_tests_add_native_accuracy_target(
+ fltx_native_accuracy_full
+ fltx_accuracy
+ native_accuracy
+ strict-consumer
+ strict
+)
+fltx_tests_add_native_accuracy_target(
+ fltx_native_accuracy_fastmath_full
+ fltx_accuracy_fastmath
+ native_accuracy_fastmath
+ fast-math-consumer
+ fastmath
+)
+
+if(BUILD_TESTING)
+ function(fltx_tests_add_accuracy_smoke
+ target test_name output_name precision labels)
+ set(output
+ "${FLTX_VALIDATION_BINARY_DIR}/smoke/${output_name}_${precision}.csv"
+ )
+ add_test(
+ NAME "${test_name}.${precision}.smoke"
+ COMMAND ${target}
+ --precision "${precision}"
+ --output "${output}"
+ --run-id "ctest-smoke"
+ --source-revision "ctest"
+ --sample-mode "smoke"
+ ${ARGN}
+ )
+ set_tests_properties("${test_name}.${precision}.smoke" PROPERTIES
+ LABELS "${labels}"
+ )
+ endfunction()
+
+ foreach(precision f32 f64 f128 f256)
+ set(FLTX_SIMULATED_FASTMATH_SMOKE_POLICY)
+ if(precision STREQUAL "f32" OR precision STREQUAL "f64")
+ set(FLTX_SIMULATED_FASTMATH_SMOKE_POLICY --advisory)
+ endif()
+
+ fltx_tests_add_accuracy_smoke(
+ fltx_accuracy
+ fltx.accuracy
+ accuracy
+ "${precision}"
+ "fltx;accuracy;smoke"
+ )
+ fltx_tests_add_accuracy_smoke(
+ fltx_accuracy_fastmath
+ fltx.accuracy.fastmath
+ accuracy_fastmath
+ "${precision}"
+ "fltx;accuracy;fastmath;smoke"
+ )
+ fltx_tests_add_accuracy_smoke(
+ fltx_constexpr_accuracy
+ fltx.constexpr_accuracy
+ constexpr_accuracy
+ "${precision}"
+ "fltx;constexpr;accuracy;smoke"
+ )
+ fltx_tests_add_accuracy_smoke(
+ fltx_constexpr_accuracy_fastmath
+ fltx.constexpr_accuracy.fastmath
+ constexpr_accuracy_fastmath
+ "${precision}"
+ "fltx;constexpr;accuracy;fastmath;smoke"
+ ${FLTX_SIMULATED_FASTMATH_SMOKE_POLICY}
+ )
+ endforeach()
+
+ function(fltx_tests_add_benchmark_smoke
+ target test_name output_name precision labels)
+ set(output
+ "${FLTX_VALIDATION_BINARY_DIR}/smoke/${output_name}_${precision}.csv"
+ )
+ add_test(
+ NAME "${test_name}.${precision}.smoke"
+ COMMAND ${target}
+ --precision "${precision}"
+ --output "${output}"
+ --run-id "ctest-smoke"
+ --source-revision "ctest"
+ --sample-mode "smoke"
+ )
+ set_tests_properties("${test_name}.${precision}.smoke" PROPERTIES
+ LABELS "${labels}"
+ )
+ endfunction()
+
+ foreach(precision f128 f256)
+ fltx_tests_add_benchmark_smoke(
+ fltx_benchmark
+ fltx.benchmark
+ benchmark
+ "${precision}"
+ "fltx;benchmark;smoke"
+ )
+ fltx_tests_add_benchmark_smoke(
+ fltx_benchmark_fastmath
+ fltx.benchmark.fastmath
+ benchmark_fastmath
+ "${precision}"
+ "fltx;benchmark;fastmath;smoke"
+ )
+ endforeach()
+
+ add_test(
+ NAME fltx.metrics.tools
+ COMMAND "${Python3_EXECUTABLE}"
+ "${PROJECT_SOURCE_DIR}/validation/metrics/_internal/test_tools.py"
+ )
+ set_tests_properties(fltx.metrics.tools PROPERTIES LABELS "fltx;metrics")
+endif()
diff --git a/cmake/validation/fltxValidationTests.cmake b/cmake/validation/fltxValidationTests.cmake
new file mode 100644
index 0000000..afaa6af
--- /dev/null
+++ b/cmake/validation/fltxValidationTests.cmake
@@ -0,0 +1,128 @@
+# Contract, constexpr, and compile-surface validation targets.
+
+set(FLTX_CONTRACT_TEST_SOURCE_FILE_PATHS
+ "${FLTX_VALIDATION_SOURCE_DIR}/support/catch_main.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/core.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/math.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/io.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/random.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/expressions.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/dispatch.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/edge_cases.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/overloads.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/overload_matrix.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/approx_comparison.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/decimal.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/contracts/benchmark_policy.cpp"
+)
+
+function(fltx_tests_add_contract_runner target label consumer_fast_math)
+ add_executable(${target} ${FLTX_CONTRACT_TEST_SOURCE_FILE_PATHS})
+ fltx_tests_prepare_test(${target} "${label}" fltx::fltx)
+ fltx_tests_configure_consumer_mode(${target} "${consumer_fast_math}")
+endfunction()
+
+fltx_tests_add_contract_runner(fltx_contract_tests contracts OFF)
+fltx_tests_add_contract_runner(
+ fltx_contract_tests_fastmath
+ contracts-fastmath
+ ON
+)
+
+# Compile an isolated fixed-consteval library so the complete accuracy runner can
+# exercise constexpr algorithms at runtime without changing normal applications.
+add_library(fltx_constexpr_accuracy_lib STATIC ${FLTX_LIBRARY_SOURCE_FILE_PATHS})
+target_include_directories(fltx_constexpr_accuracy_lib PUBLIC
+ "${PROJECT_SOURCE_DIR}/include"
+)
+target_compile_definitions(fltx_constexpr_accuracy_lib PUBLIC
+ FLTX_SIMULATE_FIXED_CONSTEVAL_MODE
+)
+fltx_configure_library_target(fltx_constexpr_accuracy_lib)
+fltx_enable_msvc_parallel_compile(fltx_constexpr_accuracy_lib)
+
+# GCC defaults to permitting contraction even in otherwise strict translation
+# units, and its STDC FP_CONTRACT pragma does not reliably protect the inline
+# error-free transforms on AArch64. Fixed simulation must retain the separate
+# rounding steps that genuine constant evaluation observes.
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(fltx_constexpr_accuracy_lib PRIVATE
+ -ffp-contract=off
+ )
+endif()
+
+set(FLTX_CONSTEXPR_TEST_SOURCE_FILE_PATHS
+ "${FLTX_VALIDATION_SOURCE_DIR}/support/catch_main.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/constexpr/core.cpp"
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/constexpr/accuracy.cpp"
+)
+
+# The genuine-constant-evaluation corpus stores compiler-evaluated results and
+# checks them against the same high-precision oracle used by the accuracy runner.
+function(fltx_tests_add_constexpr_runner target label consumer_fast_math)
+ add_executable(${target} ${FLTX_CONSTEXPR_TEST_SOURCE_FILE_PATHS})
+ fltx_tests_prepare_test(${target} "${label}" fltx::fltx)
+ fltx_tests_configure_consumer_mode(${target} "${consumer_fast_math}")
+
+ target_include_directories(${target} PRIVATE
+ "${FLTX_TESTS_MPFR_INCLUDE_DIR}"
+ "${FLTX_TESTS_GMP_INCLUDE_DIR}"
+ )
+ target_link_libraries(${target} PRIVATE
+ "${FLTX_TESTS_MPFR_LIBRARY}"
+ "${FLTX_TESTS_GMP_LIBRARY}"
+ )
+endfunction()
+
+fltx_tests_add_constexpr_runner(fltx_constexpr_tests constexpr OFF)
+fltx_tests_add_constexpr_runner(
+ fltx_constexpr_tests_fastmath
+ constexpr-fastmath
+ ON
+)
+
+# Every public header must compile by itself. Sources are generated in the build
+# tree so the source tree stays small and the probes always follow the headers.
+file(GLOB FLTX_TESTS_PUBLIC_HEADERS CONFIGURE_DEPENDS
+ RELATIVE "${PROJECT_SOURCE_DIR}/include"
+ "${PROJECT_SOURCE_DIR}/include/*.h"
+ "${PROJECT_SOURCE_DIR}/include/fltx/*.h"
+)
+
+set(FLTX_TESTS_HEADER_PROBE_SOURCES)
+foreach(header IN LISTS FLTX_TESTS_PUBLIC_HEADERS)
+ string(MAKE_C_IDENTIFIER "${header}" probe_name)
+ set(source "${FLTX_VALIDATION_BINARY_DIR}/header_probes/${probe_name}.cpp")
+ file(GENERATE OUTPUT "${source}" CONTENT "#include <${header}>\n")
+ list(APPEND FLTX_TESTS_HEADER_PROBE_SOURCES "${source}")
+endforeach()
+
+add_library(fltx_header_contract OBJECT EXCLUDE_FROM_ALL
+ ${FLTX_TESTS_HEADER_PROBE_SOURCES}
+)
+target_link_libraries(fltx_header_contract PRIVATE fltx::fltx)
+target_compile_features(fltx_header_contract PRIVATE cxx_std_20)
+
+function(fltx_tests_add_cxx_standard_contract target standard)
+ add_library(${target} OBJECT EXCLUDE_FROM_ALL
+ "${FLTX_VALIDATION_SOURCE_DIR}/tests/standard/cxx_standard_contract.cpp"
+ )
+ target_link_libraries(${target} PRIVATE fltx::fltx)
+ target_compile_definitions(${target} PRIVATE
+ "FLTX_CXX_STANDARD_CONTRACT=${standard}"
+ )
+ set_target_properties(${target} PROPERTIES
+ CXX_STANDARD ${standard}
+ CXX_STANDARD_REQUIRED ON
+ CXX_EXTENSIONS OFF
+ )
+
+ if(MSVC)
+ target_compile_options(${target} PRIVATE /permissive- /Zc:__cplusplus)
+ else()
+ target_compile_options(${target} PRIVATE -pedantic-errors)
+ endif()
+endfunction()
+
+fltx_tests_add_cxx_standard_contract(fltx_cxx20_contract 20)
+fltx_tests_add_cxx_standard_contract(fltx_cxx23_contract 23)
diff --git a/cmake/vcpkg-ports/gmp/portfile.cmake b/cmake/vcpkg-ports/gmp/portfile.cmake
index 112d952..27ae05d 100644
--- a/cmake/vcpkg-ports/gmp/portfile.cmake
+++ b/cmake/vcpkg-ports/gmp/portfile.cmake
@@ -96,8 +96,8 @@ vcpkg_configure_make(
AUTOCONFIG
ADDITIONAL_MSYS_PACKAGES
DIRECT_PACKAGES
- "https://mirror.msys2.org/msys/x86_64/autoconf2.71-2.71-3-any.pkg.tar.zst"
- dd312c428b2e19afd00899eb53ea4255794dea4c19d1d6dea2419cb6a54209ea2130d48abbc20af12196b9f628143436f736fbf889809c2c2291be0c69c0e306
+ "https://mirror.msys2.org/msys/x86_64/autoconf2.71-2.71-4-any.pkg.tar.zst"
+ c93b791eb55893cbe7c425e764074837355fd165deb7b1775f652c8e25d9d1f0cdd4120ab710d56fb859b7df55c4f971eccda7c112448f60615bff8a2dc81166
OPTIONS
${OPTIONS}
${gmp_cxx_option}
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..8196450
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,234 @@
+# fltx architecture
+
+This is a navigation map of stable ownership and dependency flow. It is not a
+file inventory, API reference, or generated index. Read exact source before
+changing behaviour.
+
+## Repository shape
+
+| Area | Responsibility |
+|---|---|
+| `include/fltx.h` | Complete public umbrella include |
+| `include/fltx/*.h` | Public types, operations, and focused include surfaces |
+| `include/fltx/detail/*.h` | Shared and precision-specific implementation mechanics |
+| `src/*.cpp` | Compiled runtime boundaries and heavyweight implementations |
+| `cmake/` | Library build policy, source ownership, packaging, and developer-target wiring |
+| `validation/tests/` | Behavioural, overload, header, language-mode, and genuine-constexpr contracts |
+| `validation/accuracy/` | Deterministic MPFR-backed numerical domains and runner registration |
+| `validation/benchmarks/` | Runtime, compile-time, and linked-size measurement |
+| `validation/support/` | Validation-only domains, oracles, comparison adapters, and shared runner support |
+| `validation/_internal/` | Preset resolution, host-toolchain setup, and local CI-check orchestration |
+| `validation/metrics/` | Metrics orchestration, provenance, publication, and report generation |
+| `examples/` | First-party usage examples and API consumers |
+
+`vcpkg/` and `validation/extern/` are dependency trees, not first-party library
+architecture. Generated metrics data and reports are evidence, not source
+owners. Normal repository search excludes all of these through `.rgignore`.
+
+## Public include structure
+
+`include/fltx.h` composes the main surfaces:
+
+```text
+fltx.h
+ core.h types, conversions, comparisons, classification, arithmetic
+ io.h limits, char conversion, strings, streams
+ math.h f32/f64/f128/f256 -shaped APIs
+ dispatch.h runtime-to-template dispatch and FloatType mappings
+ random.h engines, distributions, and array helpers
+ hash.h std::hash integration
+```
+
+The focused headers remain supported entry points. `f128.h` and `f256.h`
+compose their respective type families; `f128_math.h`, `f256_math.h`,
+`charconv.h`, and similar headers expose smaller surfaces when consumers do not
+want the complete umbrella.
+
+The extended types have two public forms:
+
+- `f128_s` and `f256_s` are trivial storage forms containing two and four
+ `double` limbs respectively;
+- `f128` and `f256` are scalar value forms built on those storage layouts;
+- `f256` additionally participates in the bounded expression-fusion system in
+ `detail/f256_expressions.h`, while `f256_s` remains the eager storage form.
+
+`aliases.h` supplies the type names, `traits.h` owns concepts and promotion
+traits, and the per-precision `*_type.h`, `*_limits.h`, `*_conversions.h`,
+`*_comparison.h`, `*_classification.h`, and `*_arithmetic.h` headers build the
+core numeric surface.
+
+## Library layers and execution paths
+
+### Header implementation layer
+
+Reusable constexpr-capable machinery lives under `include/fltx/detail`:
+
+- `common_fp.h`, `common_math.h`, `math_utils.h`, and `math_promotion.h` own
+ cross-precision floating-point mechanics and promotion;
+- `common_decimal.h`, `common_io.h`, `native_float_decimal.h`, and
+ `native_float_io.h` own shared decimal and text conversion mechanics;
+- `f128_*` and `f256_*` headers own expansion arithmetic, kernels, constants,
+ math implementations, conversions, random traits, and expression lowering;
+- `simd.h`, `f256_simd_config.h`, and `build_info.h` own compiled/consumer SIMD
+ and FMA configuration boundaries.
+
+Internal namespaces reflect the layers:
+
+```text
+bl::detail::_f128 / _f256 primitive expansion operations and kernels
+bl::detail::_f128_impl / _f256_impl algorithm implementations
+bl::detail::_f128_runtime / _f256_runtime compiled entry-point declarations
+bl::detail::_f256_expr bounded f256 expression nodes and lowering
+```
+
+Public constexpr wrappers select the constant-evaluation implementation or a
+compiled runtime entry point. Therefore changes to math, arithmetic,
+conversion, or parsing commonly have two routes to inspect:
+
+```text
+public wrapper
+ constant evaluation -> detail primitives / *_impl in headers
+ runtime -> detail::*_runtime declaration -> src/*.cpp -> *_impl
+```
+
+`config.h` centralizes compiler feature selection and the constant-evaluation
+switch. `build_info.cpp` captures the compiled library configuration and
+runtime hardware-FMA availability; `fma_x86.cpp` supplies separately compiled
+x86 FMA backends when the build policy enables them.
+
+### Compiled runtime layer
+
+`cmake/fltxSources.cmake` is the source of truth for compiled library sources:
+
+| Sources | Ownership |
+|---|---|
+| `f128.cpp`, `f256.cpp` | Runtime conversions, hot arithmetic helpers, and f256 fused-expression bodies |
+| `f128_math.cpp`, `f256_math.cpp` | Core roots, rounding, remainder, and decomposition entry points |
+| `f128_transcendental.cpp`, `f256_transcendental.cpp` | Exponential, logarithmic, power, trigonometric, hyperbolic, and special-function entry points |
+| `fltx_io.cpp` | Runtime string formatting and extended-type parsing entry points |
+| `build_info.cpp` | Compiled configuration identity and runtime feature detection |
+| `fma_x86.cpp` | Optional complete-operation x86 FMA backend |
+
+Heavy runtime work should remain behind these boundaries when header placement
+would enlarge consumer translation units. Header code remains necessary where
+constant evaluation or templates require the implementation to be visible.
+
+## Subsystem ownership
+
+| Subsystem | Public entry points | Detail/runtime owners | Primary validation owners |
+|---|---|---|---|
+| Types, storage, traits, limits | `core.h`, `f128.h`, `f256.h`, `traits.h`, `limits.h` | type/conversion/common-FP headers; `f128.cpp`, `f256.cpp`, `build_info.cpp` | `tests/contracts/core.cpp`, overload matrices, `tests/constexpr/core.cpp`, package tests |
+| Arithmetic and f256 expressions | `f128_arithmetic.h`, `f256_arithmetic.h` | `detail/f128_arithmetic.h`, `detail/f256_arithmetic.h`, `detail/f256_expansion.h`, `detail/f256_expressions.h`, `f256.cpp` | core, expression, and edge-case contracts; accuracy runners; runtime operation benchmarks |
+| Math and classification | `math.h`, per-type math/classification/numbers headers | common math/promotion plus per-precision basic, kernel, and transcendental headers; math/transcendental sources | `tests/contracts/math.cpp`, overload matrices, constexpr corpus, `accuracy/`, runtime benchmarks |
+| Text and streams | `io.h`, `charconv.h`, `string.h`, `stream.h`, `format.h` | common/native/precision I/O and decimal headers; `fltx_io.cpp` | I/O contracts, constexpr corpus, parse/format accuracy rows, I/O benchmarks |
+| Random facilities | `random.h` | header-only common logic plus `detail/f128_random.h` and `detail/f256_random.h` | random contracts, constexpr cases, selected runtime benchmarks |
+| Dispatch | `dispatch.h`, `template_dispatch.h` | header-only mapping and dispatch-table mechanics | dispatch contracts |
+| Standard integration | hash, limits, numbers, format specializations | corresponding focused public/detail headers | core and I/O contracts plus isolated-header probes |
+| Build and package policy | top-level `CMakeLists.txt`, `CMakePresets.json` | `cmake/fltxBuildPolicy.cmake`, `cmake/fltxSources.cmake`, validation CMake modules | header/C++ standard contracts, in-tree and installed-package consumers |
+
+`validation/COVERAGE.md` is the detailed ownership ledger. It should be updated
+when the implemented validation surface changes; this architecture map should
+not duplicate its operation-by-operation inventory.
+
+## Validation flow
+
+The validation layers answer different questions:
+
+```text
+contracts public behaviour, overloads, edge cases, I/O, random, dispatch
+genuine constexpr compiler-evaluated behaviour
+accuracy deterministic numerical comparison against MPFR
+constexpr accuracy runtime execution of the fixed constexpr-algorithm route
+benchmarks performance, compile-time, and linked-size evidence
+package checks installed find_package consumer behaviour
+```
+
+CMake target construction is split across `cmake/validation/`:
+
+- `fltxValidationTests.cmake` owns contracts, genuine constexpr, isolated
+ public-header probes, and C++20/C++23 compile contracts;
+- `fltxValidationRunners.cmake` owns accuracy, benchmark, source-identity, and
+ comparison smoke runners;
+- `fltxValidationDependencies.cmake` owns validation-only comparison
+ dependencies;
+- `fltxValidationPackage.cmake` owns in-tree and installed-package consumers;
+- `fltxValidationCommon.cmake` owns shared target preparation and build
+ identity.
+
+The local CI-check entry points are intentionally separate from metrics
+collection. Their accuracy runner emits and gates FLTX rows only; comparison
+implementations and dependency smoke checks stay on metrics-owned targets:
+
+```text
+run_preset_checks.py ------------------+-> _internal/check_pipeline.py
+run_all_supported_checks.py -----------+ -> configure the selected preset
+ -> build fltx_ci_checks
+```
+
+Shared preset inheritance, host-matrix selection, and Visual Studio developer
+environment setup live in `validation/_internal/preset_support.py` so checks
+and metrics use one toolchain-resolution path.
+
+Read `validation/README.md` for current commands and runner semantics. Read
+`validation/COVERAGE.md` before adding coverage so new work extends the
+existing owner rather than creating a parallel test lane.
+
+## Metrics flow
+
+The normal public entry points are intentionally few:
+
+```text
+run_preset_metrics.py -------------------------------+
+run_all_supported_metrics.py |
+ -> _internal/supported_preset_pipeline.py ----------+
+ -> _internal/preset_pipeline.py
+ -> configure/build and locate C++ runners through the CMake File API
+ -> _internal/run_metrics.py and run_native_accuracy.py
+ -> rebuild_tables.py / _internal/report_pipeline.py
+ -> SVG renderers
+```
+
+`manifest.py` owns expected operation/implementation registration.
+`source_fingerprint.py` owns source identity. `run_metrics.py` owns runner
+preflight, streamed evidence, aggregation, provenance, staging, and atomic
+publication. The `build_*` modules render existing evidence and must not repair
+or reinterpret source rows.
+
+By default, quick, standard, and full staging runs keep profile-separated
+evidence and reports beneath
+`validation/metrics/_unversioned//{data,generated}`. Only an
+explicitly requested complete release workflow may publish canonical evidence beneath
+`validation/metrics/data` and regenerate the matching reports beneath
+`validation/metrics/generated`.
+
+## Change navigation
+
+For a public API change, trace the focused public header into its detail and
+runtime owners, then search first-party consumers in `validation` and
+`examples`. Update them directly rather than preserving a superseded API.
+
+For an arithmetic, math, conversion, or parsing change, inspect both branches
+of the constant-evaluation/runtime split and select validation from the
+existing coverage row. Performance evidence complements correctness; it does
+not replace contracts or accuracy coverage.
+
+For a new compiled source, update `cmake/fltxSources.cmake`. For a new
+validation source, add it to the owning validation CMake module rather than the
+top-level build file.
+
+For metrics work, start at `run_preset_metrics.py` or `rebuild_tables.py`, then
+enter `_internal` only at the stage being changed. Renderer-only work should
+reuse compatible data; collection/publishing work must preserve source
+identity, manifests, provenance, and transactional publication.
+
+For a local CI reproduction, start at `run_preset_checks.py`; use
+`run_all_supported_checks.py` only when every required host toolchain is
+available.
+
+## Maintenance rule
+
+Update this document only when a subsystem is added, removed, moved, or its
+ownership/dependency flow materially changes. Do not update it for ordinary
+function additions, algorithm tuning, test cases, or refactoring within an
+existing owner. Avoid volatile line numbers, counts, generated inventories,
+and timestamps.
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 242cc16..9c6f1c6 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -16,7 +16,7 @@ function(fltx_add_example example_name)
$<$:-fconstexpr-backtrace-limit=100>
)
- target_compile_features(${example_name} PRIVATE cxx_std_23)
+ target_compile_features(${example_name} PRIVATE cxx_std_20)
fltx_enable_msvc_parallel_compile(${example_name})
set_target_properties(${example_name} PROPERTIES FOLDER "fltx/examples")
@@ -26,10 +26,18 @@ function(fltx_add_example example_name)
endif()
endfunction()
-fltx_add_example("1_example_basic" example_basic.cpp)
-fltx_add_example("2_example_constexpr_io" example_constexpr_io.cpp)
-fltx_add_example("3_example_dispatch" example_dispatch.cpp)
-fltx_add_example("4_example_mandelbrot" example_mandelbrot.cpp)
-fltx_add_example("5_example_consteval_ellipse" example_consteval_ellipse.cpp)
-fltx_add_example("6_example_consteval_math" example_consteval_math.cpp)
-fltx_add_example("7_example_random" example_random.cpp)
+fltx_add_example("01_example_basic" example_basic.cpp)
+fltx_add_example("02_example_constexpr_io" example_constexpr_io.cpp)
+fltx_add_example("03_example_dispatch" example_dispatch.cpp)
+fltx_add_example("04_example_mandelbrot" example_mandelbrot.cpp helpers/bmp_writer.h)
+fltx_add_example("05_example_consteval_ellipse" example_consteval_ellipse.cpp)
+fltx_add_example("06_example_consteval_library_sweep" example_consteval_library_sweep.cpp)
+fltx_add_example("07_example_random" example_random.cpp)
+fltx_add_example("08_example_pow" example_pow.cpp)
+fltx_add_example("09_example_newton_solver" example_newton_solver.cpp)
+fltx_add_example("10_example_charconv" example_charconv.cpp)
+fltx_add_example("11_example_std_integration" example_std_integration.cpp)
+fltx_add_example("12_example_storage_interop" example_storage_interop.cpp)
+fltx_add_example("13_example_special_values" example_special_values.cpp)
+fltx_add_example("14_example_polynomial" example_polynomial.cpp)
+fltx_add_example("15_example_consteval_coefficients" example_consteval_coefficients.cpp)
diff --git a/examples/example_charconv.cpp b/examples/example_charconv.cpp
new file mode 100644
index 0000000..f755b5f
--- /dev/null
+++ b/examples/example_charconv.cpp
@@ -0,0 +1,74 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace bl;
+using namespace bl::literals;
+
+const char* errc_name(std::errc ec)
+{
+ if (ec == std::errc{})
+ return "ok";
+ if (ec == std::errc::invalid_argument)
+ return "invalid_argument";
+ if (ec == std::errc::result_out_of_range)
+ return "result_out_of_range";
+ if (ec == std::errc::value_too_large)
+ return "value_too_large";
+ return "other";
+}
+
+template
+std::string_view written_view(const std::array& buffer, const char* end)
+{
+ return { buffer.data(), static_cast(end - buffer.data()) };
+}
+
+int main()
+{
+ constexpr f256 value = std::numbers::pi_v + 1_qd / 7_qd;
+
+ std::array buffer{};
+ const auto out = bl::to_chars(
+ buffer.data(),
+ buffer.data() + buffer.size(),
+ value,
+ std::chars_format::fixed,
+ 48);
+
+ std::cout << "to_chars fixed status: " << errc_name(out.ec) << "\n";
+ std::cout << "text: " << written_view(buffer, out.ptr) << "\n\n";
+
+ f256 parsed{};
+ const auto in = bl::from_chars(
+ buffer.data(),
+ out.ptr,
+ parsed,
+ std::chars_format::fixed);
+
+ std::cout << "from_chars status: " << errc_name(in.ec) << "\n";
+ std::cout << std::setprecision(std::numeric_limits::digits10);
+ std::cout << "parsed: " << parsed << "\n\n";
+
+ constexpr std::string_view packet = "6.02214076e23 mol";
+ f256 scanned{};
+ const auto scan = bl::from_chars(packet.data(), packet.data() + packet.size(), scanned);
+
+ std::cout << "scanned prefix: " << scanned << "\n";
+ std::cout << "consumed chars: " << (scan.ptr - packet.data()) << "\n";
+ std::cout << "remaining text: "
+ << std::string_view{ scan.ptr, static_cast(packet.data() + packet.size() - scan.ptr) }
+ << "\n\n";
+
+ const auto strict = bl::try_parse(packet);
+ std::cout << "try_parse whole packet status: " << errc_name(strict.ec) << "\n";
+ std::cout << "try_parse consumed chars: " << strict.consumed << "\n";
+}
diff --git a/examples/example_consteval_coefficients.cpp b/examples/example_consteval_coefficients.cpp
new file mode 100644
index 0000000..5a227df
--- /dev/null
+++ b/examples/example_consteval_coefficients.cpp
@@ -0,0 +1,59 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace bl;
+
+template
+consteval std::array gaussian_kernel(T sigma)
+{
+ std::array weights{};
+ T sum{};
+
+ for (std::size_t i = 0; i < weights.size(); ++i)
+ {
+ const int offset = static_cast(i) - static_cast(Radius);
+ const T x = T{ offset };
+ const T x2 = x * x;
+ const T denom = T{ 2 } * sigma * sigma;
+ const T exponent = -x2 / denom;
+ weights[i] = bl::exp(exponent);
+ sum += weights[i];
+ }
+
+ for (T& weight : weights)
+ weight /= sum;
+
+ return weights;
+}
+
+template
+constexpr T sum_values(const std::array& values)
+{
+ T sum{};
+ for (const T& value : values)
+ sum += value;
+ return sum;
+}
+
+int main()
+{
+ using T = f256;
+
+ constexpr auto kernel = gaussian_kernel<4>( bl::parse("1.25") );
+ constexpr T kernel_sum = sum_values(kernel);
+
+ std::cout << std::setprecision(std::numeric_limits::digits10);
+
+ for (std::size_t i = 0; i < kernel.size(); ++i)
+ {
+ const int offset = static_cast(i) - 4;
+ std::cout << "w[" << offset << "] = " << kernel[i] << "\n";
+ }
+
+ std::cout << "\nsum = " << kernel_sum << "\n";
+}
diff --git a/examples/example_consteval_math.cpp b/examples/example_consteval_library_sweep.cpp
similarity index 89%
rename from examples/example_consteval_math.cpp
rename to examples/example_consteval_library_sweep.cpp
index f2c9a4b..01a5a8b 100644
--- a/examples/example_consteval_math.cpp
+++ b/examples/example_consteval_library_sweep.cpp
@@ -19,10 +19,7 @@ consteval f32 f32_test()
constexpr f32 round_result = bl::round(2.5f);
constexpr long lround_result = bl::lround(2.5f);
constexpr llong llround_result = bl::llround(2.5f);
- constexpr f32 nearbyint_result = bl::nearbyint(2.5f);
- constexpr f32 rint_result = bl::rint(2.5f);
- constexpr long lrint_result = bl::lrint(2.5f);
- constexpr llong llrint_result = bl::llrint(2.5f);
+ constexpr f32 roundeven_result = bl::roundeven(2.5f);
constexpr f32 fmod_result = bl::fmod(5.5f, 2.0f);
constexpr f32 remainder_result = bl::remainder(5.5f, 2.0f);
@@ -38,6 +35,7 @@ consteval f32 f32_test()
constexpr f32 cbrt_result = bl::cbrt(0.123f);
constexpr f32 hypot_result = bl::hypot(0.3f, 0.4f);
constexpr f32 pow_result = bl::pow(0.123f, 4.56f);
+ constexpr f32 pow10_result = bl::ipow(10.0f, 2);
constexpr f32 exp_result = bl::exp(0.123f);
constexpr f32 exp2_result = bl::exp2(0.123f);
@@ -85,11 +83,10 @@ consteval f32 f32_test()
abs_result + fma_result
+ floor_result + ceil_result + trunc_result + round_result
+ static_cast(lround_result) + static_cast(llround_result)
- + nearbyint_result + rint_result
- + static_cast(lrint_result) + static_cast(llrint_result)
+ + roundeven_result
+ fmod_result + remainder_result + remquo_result1 + static_cast(remquo_result2)
+ fmin_result + fmax_result + fdim_result + copysign_result
- + sqrt_result + cbrt_result + hypot_result + pow_result
+ + sqrt_result + cbrt_result + hypot_result + pow_result + pow10_result
+ exp_result + exp2_result + expm1_result
+ log_result + log2_result + log10_result + log1p_result + logb_result + static_cast(ilogb_result)
+ sin_result + cos_result + tan_result + asin_result + acos_result + atan_result + atan2_result
@@ -113,10 +110,7 @@ consteval f64 f64_test()
constexpr f64 round_result = bl::round(2.5);
constexpr long lround_result = bl::lround(2.5);
constexpr llong llround_result = bl::llround(2.5);
- constexpr f64 nearbyint_result = bl::nearbyint(2.5);
- constexpr f64 rint_result = bl::rint(2.5);
- constexpr long lrint_result = bl::lrint(2.5);
- constexpr llong llrint_result = bl::llrint(2.5);
+ constexpr f64 roundeven_result = bl::roundeven(2.5);
constexpr f64 fmod_result = bl::fmod(5.5, 2.0);
constexpr f64 remainder_result = bl::remainder(5.5, 2.0);
@@ -132,6 +126,7 @@ consteval f64 f64_test()
constexpr f64 cbrt_result = bl::cbrt(0.123);
constexpr f64 hypot_result = bl::hypot(0.3, 0.4);
constexpr f64 pow_result = bl::pow(0.123, 4.56);
+ constexpr f64 pow10_result = bl::pow(10.0, 2);
constexpr f64 exp_result = bl::exp(0.123);
constexpr f64 exp2_result = bl::exp2(0.123);
@@ -179,11 +174,10 @@ consteval f64 f64_test()
abs_result + fma_result
+ floor_result + ceil_result + trunc_result + round_result
+ static_cast(lround_result) + static_cast(llround_result)
- + nearbyint_result + rint_result
- + static_cast(lrint_result) + static_cast(llrint_result)
+ + roundeven_result
+ fmod_result + remainder_result + remquo_result1 + static_cast(remquo_result2)
+ fmin_result + fmax_result + fdim_result + copysign_result
- + sqrt_result + cbrt_result + hypot_result + pow_result
+ + sqrt_result + cbrt_result + hypot_result + pow_result + pow10_result
+ exp_result + exp2_result + expm1_result
+ log_result + log2_result + log10_result + log1p_result + logb_result + static_cast(ilogb_result)
+ sin_result + cos_result + tan_result + asin_result + acos_result + atan_result + atan2_result
@@ -207,10 +201,7 @@ consteval f128 f128_test()
constexpr f128 round_result = bl::round(2.5_dd);
constexpr long lround_result = bl::lround(2.5_dd);
constexpr llong llround_result = bl::llround(2.5_dd);
- constexpr f128 nearbyint_result = bl::nearbyint(2.5_dd);
- constexpr f128 rint_result = bl::rint(2.5_dd);
- constexpr long lrint_result = bl::lrint(2.5_dd);
- constexpr llong llrint_result = bl::llrint(2.5_dd);
+ constexpr f128 roundeven_result = bl::roundeven(2.5_dd);
constexpr f128 fmod_result = bl::fmod(5.5_dd, 2.0_dd);
constexpr f128 remainder_result = bl::remainder(5.5_dd, 2.0_dd);
@@ -222,11 +213,11 @@ consteval f128 f128_test()
constexpr f128 fdim_result = bl::fdim(1.25_dd, 0.75_dd);
constexpr f128 copysign_result = bl::copysign(0.125_dd, -1.0_dd);
- constexpr f128 sqrt_result = bl::sqrt(0.123_dd);
- constexpr f128 cbrt_result = bl::cbrt(0.123_dd);
- constexpr f128 hypot_result = bl::hypot(0.3_dd, 0.4_dd);
- constexpr f128 pow_result = bl::pow(0.123_dd, 4.56_dd);
- constexpr f128 pow_double_result = bl::pow(0.123_dd, 4.56);
+ constexpr f128 sqrt_result = bl::sqrt(0.123_dd);
+ constexpr f128 cbrt_result = bl::cbrt(0.123_dd);
+ constexpr f128 hypot_result = bl::hypot(0.3_dd, 0.4_dd);
+ constexpr f128 pow_result = bl::pow(0.123_dd, 4.56_dd);
+ constexpr f128 pow10_result = bl::pow(10.0_dd, 2);
constexpr f128 exp_result = bl::exp(0.123_dd);
constexpr f128 exp2_result = bl::exp2(0.123_dd);
@@ -274,11 +265,10 @@ consteval f128 f128_test()
abs_result + fma_result
+ floor_result + ceil_result + trunc_result + round_result
+ f128{ static_cast(lround_result) } + f128{ static_cast(llround_result) }
- + nearbyint_result + rint_result
- + f128{ static_cast(lrint_result) } + f128{ static_cast(llrint_result) }
+ + roundeven_result
+ fmod_result + remainder_result + remquo_result1 + f128{ static_cast(remquo_result2) }
+ fmin_result + fmax_result + fdim_result + copysign_result
- + sqrt_result + cbrt_result + hypot_result + pow_result + pow_double_result
+ + sqrt_result + cbrt_result + hypot_result + pow_result + pow10_result
+ exp_result + exp2_result + expm1_result
+ log_result + log2_result + log10_result + log1p_result + logb_result + f128{ static_cast(ilogb_result) }
+ sin_result + cos_result + tan_result + asin_result + acos_result + atan_result + atan2_result
@@ -302,10 +292,7 @@ consteval f256 f256_test()
constexpr f256 round_result = bl::round(2.5_qd);
constexpr long lround_result = bl::lround(2.5_qd);
constexpr llong llround_result = bl::llround(2.5_qd);
- constexpr f256 nearbyint_result = bl::nearbyint(2.5_qd);
- constexpr f256 rint_result = bl::rint(2.5_qd);
- constexpr long lrint_result = bl::lrint(2.5_qd);
- constexpr llong llrint_result = bl::llrint(2.5_qd);
+ constexpr f256 roundeven_result = bl::roundeven(2.5_qd);
constexpr f256 fmod_result = bl::fmod(5.5_qd, 2.0_qd);
constexpr f256 remainder_result = bl::remainder(5.5_qd, 2.0_qd);
@@ -317,11 +304,11 @@ consteval f256 f256_test()
constexpr f256 fdim_result = bl::fdim(1.25_qd, 0.75_qd);
constexpr f256 copysign_result = bl::copysign(0.125_qd, -1.0_qd);
- constexpr f256 sqrt_result = bl::sqrt(0.123_qd);
- constexpr f256 cbrt_result = bl::cbrt(0.123_qd);
- constexpr f256 hypot_result = bl::hypot(0.3_qd, 0.4_qd);
- constexpr f256 pow_result = bl::pow(0.123_qd, 4.56_qd);
- constexpr f256 pow_double_result = bl::pow(0.123_qd, 4.56);
+ constexpr f256 sqrt_result = bl::sqrt(0.123_qd);
+ constexpr f256 cbrt_result = bl::cbrt(0.123_qd);
+ constexpr f256 hypot_result = bl::hypot(0.3_qd, 0.4_qd);
+ constexpr f256 pow_result = bl::pow(0.123_qd, 4.56_qd);
+ constexpr f256 pow10_result = bl::pow(10.0_qd, 2);
constexpr f256 exp_result = bl::exp(0.123_qd);
constexpr f256 exp2_result = bl::exp2(0.123_qd);
@@ -369,11 +356,10 @@ consteval f256 f256_test()
abs_result + fma_result
+ floor_result + ceil_result + trunc_result + round_result
+ f256{ static_cast(lround_result) } + f256{ static_cast(llround_result) }
- + nearbyint_result + rint_result
- + f256{ static_cast(lrint_result) } + f256{ static_cast(llrint_result) }
+ + roundeven_result
+ fmod_result + remainder_result + remquo_result1 + f256{ static_cast(remquo_result2) }
+ fmin_result + fmax_result + fdim_result + copysign_result
- + sqrt_result + cbrt_result + hypot_result + pow_result + pow_double_result
+ + sqrt_result + cbrt_result + hypot_result + pow_result + pow10_result
+ exp_result + exp2_result + expm1_result
+ log_result + log2_result + log10_result + log1p_result + logb_result + f256{ static_cast(ilogb_result) }
+ sin_result + cos_result + tan_result + asin_result + acos_result + atan_result + atan2_result
@@ -386,6 +372,20 @@ consteval f256 f256_test()
return scalar_sum;
}
+template
+consteval bool approx_match(T lhs, T rhs)
+{
+ const T diff = bl::abs(lhs - rhs);
+ const T lhs_abs = bl::abs(lhs);
+ const T rhs_abs = bl::abs(rhs);
+ const T scale = (lhs_abs > rhs_abs) ? lhs_abs : rhs_abs;
+ const T floor = T{ 1 };
+ const T reference = (scale > floor) ? scale : floor;
+ const T tolerance = std::numeric_limits::epsilon() * T{ 16 };
+
+ return diff <= reference * tolerance;
+}
+
int main()
{
constexpr f32 result_f32 = f32_test();
@@ -393,6 +393,11 @@ int main()
constexpr f128 result_f128 = f128_test();
constexpr f256 result_f256 = f256_test();
+ constexpr bool match =
+ approx_match(result_f32, static_cast(result_f64)) &&
+ approx_match(result_f64, static_cast(result_f128)) &&
+ approx_match(result_f128, static_cast(result_f256));
+
std::cout
<< std::fixed
<< std::setprecision(std::numeric_limits::digits10)
@@ -401,5 +406,5 @@ int main()
<< result_f128 << "\n"
<< result_f256;
- return 0;
+ return match ? 0 : 1;
}
diff --git a/examples/example_constexpr_io.cpp b/examples/example_constexpr_io.cpp
index b09a371..4a96bc7 100644
--- a/examples/example_constexpr_io.cpp
+++ b/examples/example_constexpr_io.cpp
@@ -1,25 +1,118 @@
#include
#include
+#include
#include
+#include
+#include
using namespace bl;
-using namespace bl::literals;
int main()
{
- // calculate pi * 0.5:
- // = 1.57079632679489661923132169163975144209858469968755291048747230
- constexpr f256 pio2_f256 = std::numbers::pi_v * 0.5_qd;
-
- // generate static_string<512> at compile-time
- // = "1.57079632679489661923132169163975144209858469968755291048747230"
- constexpr auto str_f256 = bl::to_static_string(pio2_f256);
- std::cout << "str_f256: " << str_f256 << "\n";
-
- // parse back to f128 value
- constexpr f128 parsed_f128 = bl::to_f128(str_f256);
- std::cout << std::setprecision(bl::numeric_limits::digits10);
- std::cout << "parsed_f128: " << parsed_f128;
+ // ----- try switching format mode -----
+ constexpr auto mode = std::ios_base::fixed;
+ // constexpr auto mode = std::ios_base::scientific; // scientific
+ // constexpr auto mode = std::ios_base::fmtflags{}; // defaultfloat
+ // constexpr auto mode = std::ios_base::fixed | std::ios_base::scientific; // hexfloat
+
+ // ----- try switching base flags -----
+ constexpr auto base_flags =
+ std::ios_base::showpoint |
+ std::ios_base::showpos |
+ std::ios_base::uppercase;
+
+ // sets cout stream flags
+ constexpr auto flags = mode | base_flags;
+ std::cout.setf(flags & std::ios_base::floatfield, std::ios_base::floatfield);
+ std::cout.setf(flags & base_flags, base_flags);
+
+ // use enough decimal digits for an exact f256 text round-trip
+ constexpr int digits = std::numeric_limits::max_digits10;
+ std::cout << std::setprecision(digits);
+
+ // calculate 10+pi/2
+ constexpr f256 value = 10 + std::numbers::pi_v / 2;
+ std::cout << "value: " << value << "\n\n";
+
+ // compile-time text round-trip
+ {
+ constexpr auto txt_static = bl::to_static_string(value, digits, flags);
+ std::cout << "txt (static): " << txt_static << "\n";
+
+ // parse back to f256 value
+ constexpr f256 parsed_static = bl::parse(txt_static);
+ std::cout << "parsed (static): " << parsed_static << "\n\n";
+
+ if (parsed_static != value)
+ return 1;
+ }
+
+ // runtime text round-trip
+ {
+ auto txt_runtime = bl::to_string(value, digits, flags);
+ std::cout << "txt (runtime): " << txt_runtime << "\n";
+
+ // parse back to f256 value
+ f256 parsed_runtime = bl::parse(txt_runtime);
+ std::cout << "parsed (runtime): " << parsed_runtime << "\n\n";
+
+ if (parsed_runtime != value)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// ----- Verify every viable stream flag combination round-trips at max_digits10 -----
+
+template
+consteval bool static_roundtrip_ok()
+{
+ constexpr int digits = std::numeric_limits::max_digits10;
+ constexpr f256 value = 10 + std::numbers::pi_v / 2;
+ constexpr auto txt_static = bl::to_static_string(value, digits, flags);
+ constexpr f256 parsed_static = bl::parse(txt_static);
+ return parsed_static == value;
+}
+
+template
+consteval bool all_static_roundtrips_ok()
+{
+ return (static_roundtrip_ok() && ...);
}
+static_assert(all_static_roundtrips_ok <
+ std::ios_base::fmtflags{},
+ std::ios_base::showpoint,
+ std::ios_base::showpos,
+ std::ios_base::uppercase,
+ std::ios_base::showpoint | std::ios_base::showpos,
+ std::ios_base::showpoint | std::ios_base::uppercase,
+ std::ios_base::showpos | std::ios_base::uppercase,
+ std::ios_base::showpoint | std::ios_base::showpos | std::ios_base::uppercase,
+ std::ios_base::fixed,
+ std::ios_base::fixed | std::ios_base::showpoint,
+ std::ios_base::fixed | std::ios_base::showpos,
+ std::ios_base::fixed | std::ios_base::uppercase,
+ std::ios_base::fixed | std::ios_base::showpoint | std::ios_base::showpos,
+ std::ios_base::fixed | std::ios_base::showpoint | std::ios_base::uppercase,
+ std::ios_base::fixed | std::ios_base::showpos | std::ios_base::uppercase,
+ std::ios_base::fixed | std::ios_base::showpoint | std::ios_base::showpos | std::ios_base::uppercase,
+ std::ios_base::scientific,
+ std::ios_base::scientific | std::ios_base::showpoint,
+ std::ios_base::scientific | std::ios_base::showpos,
+ std::ios_base::scientific | std::ios_base::uppercase,
+ std::ios_base::scientific | std::ios_base::showpoint | std::ios_base::showpos,
+ std::ios_base::scientific | std::ios_base::showpoint | std::ios_base::uppercase,
+ std::ios_base::scientific | std::ios_base::showpos | std::ios_base::uppercase,
+ std::ios_base::scientific | std::ios_base::showpoint | std::ios_base::showpos | std::ios_base::uppercase,
+ std::ios_base::fixed | std::ios_base::scientific,
+ std::ios_base::fixed | std::ios_base::scientific | std::ios_base::showpoint,
+ std::ios_base::fixed | std::ios_base::scientific | std::ios_base::showpos,
+ std::ios_base::fixed | std::ios_base::scientific | std::ios_base::uppercase,
+ std::ios_base::fixed | std::ios_base::scientific | std::ios_base::showpoint | std::ios_base::showpos,
+ std::ios_base::fixed | std::ios_base::scientific | std::ios_base::showpoint | std::ios_base::uppercase,
+ std::ios_base::fixed | std::ios_base::scientific | std::ios_base::showpos | std::ios_base::uppercase,
+ std::ios_base::fixed | std::ios_base::scientific | std::ios_base::showpoint | std::ios_base::showpos | std::ios_base::uppercase > ());
\ No newline at end of file
diff --git a/examples/example_dispatch.cpp b/examples/example_dispatch.cpp
index 88848c4..a1ba01f 100644
--- a/examples/example_dispatch.cpp
+++ b/examples/example_dispatch.cpp
@@ -1,5 +1,6 @@
#include
#include
+#include
#include
#include
@@ -7,7 +8,6 @@
#include
using namespace bl;
-//using namespace bl::literals;
/* Notes:
@@ -31,36 +31,36 @@ using namespace bl;
*/
template
-void print_math(const char* name)
+void print_math(std::string_view name)
{
- // T1 = f32/f64/f128/f256
+ // T1 can be: f32 / f64 / f128 / f256
constexpr T1 pi_v = std::numbers::pi_v;
constexpr T1 min_v = std::numeric_limits::min();
constexpr T1 max_v = std::numeric_limits::max();
- constexpr T1 sin_pi_6 = bl::sin(pi_v / T1{ 6.0 });
- constexpr T1 sin_pi_5 = bl::sin(pi_v / T1{ 5.0 });
+ constexpr T1 sin_pi_6 = bl::sin(pi_v / 6);
+ constexpr T1 sin_pi_5 = bl::sin(pi_v / 5);
std::cout << "print_math<" << name << ">():\n";
std::cout << std::setprecision(std::numeric_limits::digits10);
- std::cout << " " << name << " min = " << min_v << "\n";
- std::cout << " " << name << " max = " << max_v << "\n";
- std::cout << " " << name << " PI = " << pi_v << "\n";
- std::cout << " " << "bl::sin(pi_v / 6.0) = " << sin_pi_6 << "\n";
- std::cout << " " << "bl::sin(pi_v / 5.0) = " << sin_pi_5 << "\n";
+ std::cout << " " << name << " min\t\t= " << min_v << "\n";
+ std::cout << " " << name << " max\t\t= " << max_v << "\n";
+ std::cout << " " << name << " pi\t\t= " << pi_v << "\n";
+ std::cout << " " << "bl::sin(pi / 6)\t= " << sin_pi_6 << "\n";
+ std::cout << " " << "bl::sin(pi / 5)\t= " << sin_pi_5 << "\n";
// T2 is fixed to f32 by the dispatch argument below in this example
T2 c = static_cast(sin_pi_5);
std::cout << std::setprecision(std::numeric_limits::digits10);
- std::cout << " c: " << c << "\n";
+ std::cout << " c\t\t\t= " << c << "\n";
// test_bool is a runtime value at the call site, but dispatch selects a specialization
// where Test_Bool is a compile-time bool
if constexpr (Test_Bool)
{
- std::cout << " Test_Bool is TRUE\n";
+ std::cout << " Test_Bool\t\t= TRUE\n";
}
std::cout << "\n";
@@ -68,72 +68,102 @@ void print_math(const char* name)
int main()
{
- // print_math("F32");
-
-
+ // loop over each FloatType value
for (int i = 0; i < (int)FloatType::COUNT; i++)
{
- const char* name = FloatTypeNames[i];
+ // set runtime args
FloatType float_type = (FloatType)i;
- bool test_bool = (i % 2) == 0;
+ auto name = bl::to_string(float_type);
+ bool test_bool = (i % 2) == 0;
- // invokes print_math(name), selected from runtime dispatch values
+ // invokes matching print_math(name) selected from runtime values
bl_table_invoke(
- bl_dispatch_table(print_math, name), // bind runtime args passed to print_math(...)
+ bl_dispatch_table(print_math, name), // bind runtime args to print_math(...)
bl_enum_type(float_type), // map runtime enum value to T1
bl_enum_type(FloatType::F32), // map runtime enum value to T2
test_bool // map runtime bool to Test_Bool
);
}
- // print dispatch table info by providing example args (4 * 4 * 2 = 32 variants)
- bl::dispatch_table_info::print_from_args("print_math",
- bl_enum_type(FloatType::F64), // domain size = 4
- bl_enum_type(FloatType::F32), // domain size = 4
- true // domain size = 2
- );
+ // generate an automatic dispatch table report:
+ {
+ std::cout << bl_dispatch_table_report("print_math", // print_math: 32 variants
+ bl_enum_type(FloatType::F64), // arg[0] domain = 4
+ bl_enum_type(FloatType::F32), // arg[1] domain = 4
+ true // arg[2] domain = 2
+ );
+ }
+
+ /*
+ // manually calculate domain size of a single mapped type
+ {
+ constexpr std::size_t template_arg_domain_size = bl_enum_type_domain_size(FloatType::F64);
+ std::cout << "FloatType template arg domain: " << template_arg_domain_size << "\n\n";
+ }
+
+
+ // manually calculate and print dispatch table domain info
+ {
+ constexpr std::size_t table_variant_count = bl_table_variants_count(
+ bl_enum_type(FloatType::F64),
+ bl_enum_type(FloatType::F32),
+ true
+ );
+ constexpr auto table_domain_sizes = bl_dispatch_table_domain_sizes(
+ bl_enum_type(FloatType::F64),
+ bl_enum_type(FloatType::F32),
+ true
+ );
+
+ std::cout << "print_math: " << table_variant_count << " variants\n";
+ for (std::size_t i = 0; i < table_domain_sizes.size(); i++)
+ std::cout << " arg[" << i << "] domain = " << table_domain_sizes[i] << "\n";
+
+ std::cout << "\n";
+ }
+ */
}
/* Output:
-print_math():
- F32 min = 1.17549e-38
- F32 max = 3.40282e+38
- F32 PI = 3.14159
+print_math():
+ f32 min = 1.17549e-38
+ f32 max = 3.40282e+38
+ f32 PI = 3.14159
bl::sin(pi_v / 6.0) = 0.5
bl::sin(pi_v / 5.0) = 0.587785
c: 0.587785
Test_Bool is TRUE
-print_math():
- F64 min = 2.2250738585072e-308
- F64 max = 1.79769313486232e+308
- F64 PI = 3.14159265358979
+print_math():
+ f64 min = 2.2250738585072e-308
+ f64 max = 1.79769313486232e+308
+ f64 PI = 3.14159265358979
bl::sin(pi_v / 6.0) = 0.5
bl::sin(pi_v / 5.0) = 0.587785252292473
c: 0.587785
-print_math():
- F128 min = 2.225073858507201383090232717332e-308
- F128 max = 1.797693134862315708145274237317e+308
- F128 PI = 3.14159265358979323846264338328
+print_math():
+ f128 min = 2.225073858507201383090232717332e-308
+ f128 max = 1.797693134862315708145274237317e+308
+ f128 PI = 3.14159265358979323846264338328
bl::sin(pi_v / 6.0) = 0.5
bl::sin(pi_v / 5.0) = 0.5877852522924731291687059546391
c: 0.587785
Test_Bool is TRUE
-print_math():
- F256 min = 2.22507385850720138309023271733240406421921598046233183055332742e-308
- F256 max = 1.79769313486231570814527423731704356798070567525844996598917477e+308
- F256 PI = 3.14159265358979323846264338327950288419716939937510582097494459
+print_math():
+ f256 min = 2.22507385850720138309023271733240406421921598046233183055332742e-308
+ f256 max = 1.79769313486231570814527423731704356798070567525844996598917477e+308
+ f256 PI = 3.14159265358979323846264338327950288419716939937510582097494459
bl::sin(pi_v / 6.0) = 0.5
bl::sin(pi_v / 5.0) = 0.587785252292473129168705954639072768597652437643145991072272481
c: 0.587785
print_math: 32 variants
- arg[0] domain=4
- arg[1] domain=4
- arg[2] domain=2
+ arg[0] domain = 4
+ arg[1] domain = 4
+ arg[2] domain = 2
*/
diff --git a/examples/example_mandelbrot.cpp b/examples/example_mandelbrot.cpp
index 16166c5..70da9f6 100644
--- a/examples/example_mandelbrot.cpp
+++ b/examples/example_mandelbrot.cpp
@@ -3,27 +3,15 @@
#include
#include
#include
-#include
#include
#include
#include
+#include "helpers/bmp_writer.h"
using namespace bl;
using namespace bl::literals;
-static void write_u16(std::ofstream& out, std::uint16_t value)
-{
- out.put((unsigned char)(value & 0xff));
- out.put((unsigned char)((value >> 8) & 0xff));
-}
-
-static void write_u32(std::ofstream& out, std::uint32_t value)
-{
- write_u16(out, (std::uint16_t)(value));
- write_u16(out, (std::uint16_t)(value >> 16));
-}
-
static unsigned char to_byte(double x)
{
if (x < 0.0) x = 0.0;
@@ -33,45 +21,45 @@ static unsigned char to_byte(double x)
int main()
{
+ // switch which underlying type is used
using flt = f128;
+ // mandelbrot iteration limit / coordinate / zoom
constexpr int max_iter = 20000;
- constexpr flt center_x = flt("-1.73200006480238126967529761198455");
- constexpr flt center_y = flt("0.00000019235376499049335337716270");
- constexpr flt zoom = flt("2.0e+28");
+ constexpr flt center_x = bl::parse("-1.73200006480238126967529761198455");
+ constexpr flt center_y = bl::parse("0.00000019235376499049335337716270");
+ constexpr flt zoom = bl::parse("2.0e+28");
+ // target bitmap width / height
constexpr int width = 1024;
constexpr int height = 1024;
- constexpr int row_stride = (width * 3 + 3) & ~3;
- constexpr int pixel_bytes = row_stride * height;
- constexpr int file_size = 54 + pixel_bytes;
-
- std::vector pixels(pixel_bytes);
+ bmp_writer image(width, height);
std::atomic rows_done = 0;
+ // determine Mandelbrot-plane distance per pixel and image-centre offset
const flt scale_x = 4.0 / (zoom * width);
const flt scale_y = 4.0 / (zoom * height);
const flt half_w = width * 0.5;
const flt half_h = height * 0.5;
+ // determine ideal thread count to split up work
std::size_t thread_count = std::thread::hardware_concurrency();
if (thread_count == 0) thread_count = 1;
if (thread_count > (std::size_t)height) thread_count = height;
+ // calculate how many rows to assign to each thread
const int rows_per_thread = (height + (int)thread_count - 1) / (int)thread_count;
+ // mandelbrot kernel to process rows [row_begin, row_end)
auto render_rows = [&](int row_begin, int row_end)
{
for (int row = row_begin; row < row_end; ++row)
{
- int py = height - 1 - row;
- unsigned char* out_row = pixels.data() + row * row_stride;
-
for (int px = 0; px < width; ++px)
{
flt cx = center_x + (flt(px) - half_w) * scale_x;
- flt cy = center_y + (flt(py) - half_h) * scale_y;
+ flt cy = center_y + (flt(row) - half_h) * scale_y;
flt x = 0, y = 0;
int iter = 0;
@@ -82,12 +70,12 @@ int main()
x = xx;
++iter;
- f64 _x = (f64)x;
- f64 _y = (f64)y;
- if (_x*_x + _y*_y > 4.0)
+ // cheap low-precision escape check
+ if (bl::sqr((f64)x) + bl::sqr((f64)y) > 4.0)
break;
}
+ // choose a nice colour
unsigned char r, g, b;
if (iter == max_iter)
{
@@ -96,28 +84,26 @@ int main()
else
{
constexpr f64 tau = 6.28318530717958647692;
- f64 t = iter * 0.005;
+ f64 t = iter * 0.005;
r = to_byte(127.5 + 127.5 * std::cos(tau * (t + 0.00)));
g = to_byte(127.5 + 127.5 * std::cos(tau * (t + 0.15)));
b = to_byte(127.5 + 127.5 * std::cos(tau * (t + 0.32)));
}
- int i = px * 3;
- out_row[i + 0] = b;
- out_row[i + 1] = g;
- out_row[i + 2] = r;
+ image.setPixel(px, row, { r, g, b });
}
+ // update progress
rows_done.fetch_add(1, std::memory_order_relaxed);
}
};
+ // create threads, assign rows
std::vector threads;
threads.reserve(thread_count);
const auto render_start = std::chrono::steady_clock::now();
-
for (std::size_t i = 0; i < thread_count; ++i)
{
int row_begin = (int)i * rows_per_thread;
@@ -130,10 +116,11 @@ int main()
threads.emplace_back(render_rows, row_begin, row_end);
}
+ // track percentage complete
int last_percent = -1;
while (true)
{
- int done = rows_done.load(std::memory_order_relaxed);
+ int done = rows_done.load(std::memory_order_relaxed);
int percent = (done * 100) / height;
if (percent != last_percent)
@@ -148,37 +135,18 @@ int main()
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
+ // wait for all threads to complete
for (std::thread& thread : threads)
thread.join();
- const auto render_end = std::chrono::steady_clock::now();
+ const auto render_end = std::chrono::steady_clock::now();
const std::chrono::duration render_time = render_end - render_start;
std::cout << "\r100% complete\n";
std::cout << "generation took " << render_time.count() << " seconds\n";
+ // save image (and print path)
const std::filesystem::path output_path = std::filesystem::absolute("mandelbrot.bmp");
- std::ofstream out(output_path, std::ios::binary);
-
- out.put('B');
- out.put('M');
- write_u32(out, file_size);
- write_u16(out, 0);
- write_u16(out, 0);
- write_u32(out, 54);
-
- write_u32(out, 40);
- write_u32(out, width);
- write_u32(out, height);
- write_u16(out, 1);
- write_u16(out, 24);
- write_u32(out, 0);
- write_u32(out, pixel_bytes);
- write_u32(out, 2835);
- write_u32(out, 2835);
- write_u32(out, 0);
- write_u32(out, 0);
-
- out.write(reinterpret_cast(pixels.data()), pixels.size());
+ image.save(output_path);
std::cout << "wrote: " << output_path.string() << "\n";
}
diff --git a/examples/example_newton_solver.cpp b/examples/example_newton_solver.cpp
new file mode 100644
index 0000000..a7c7565
--- /dev/null
+++ b/examples/example_newton_solver.cpp
@@ -0,0 +1,70 @@
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace bl;
+
+template
+struct solve_result
+{
+ T root{};
+ T residual{};
+ int iterations{};
+};
+
+template
+constexpr T equation(T x)
+{
+ return bl::cos(x) - x;
+}
+
+template
+constexpr T derivative(T x)
+{
+ return -bl::sin(x) - T{ 1 };
+}
+
+template
+constexpr solve_result solve_newton(T initial)
+{
+ T x = initial;
+ const T tolerance = std::numeric_limits::epsilon() * T{ 32 };
+
+ for (int i = 0; i < 20; ++i)
+ {
+ const T step = equation(x) / derivative(x);
+ x -= step;
+
+ T scale = bl::abs(x);
+ if (scale < T{ 1 })
+ scale = T{ 1 };
+
+ if (bl::abs(step) <= tolerance * scale)
+ return { x, equation(x), i + 1 };
+ }
+
+ return { x, equation(x), 20 };
+}
+
+template
+void print_result(std::string_view name)
+{
+ const auto result = solve_newton(1);
+
+ std::cout
+ << name << ":\n"
+ << " iterations = " << result.iterations << "\n"
+ << std::setprecision(std::numeric_limits::max_digits10)
+ << " root = " << result.root << "\n"
+ << " residual = " << result.residual << "\n\n";
+}
+
+int main()
+{
+ print_result("f64");
+ print_result("f128");
+ print_result("f256");
+}
diff --git a/examples/example_polynomial.cpp b/examples/example_polynomial.cpp
new file mode 100644
index 0000000..da7ea0b
--- /dev/null
+++ b/examples/example_polynomial.cpp
@@ -0,0 +1,54 @@
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace bl;
+
+template
+constexpr T expanded_shifted_power(T x)
+{
+ T y = T{ 1 };
+ y = bl::fma(y, x, T{ -8 });
+ y = bl::fma(y, x, T{ 28 });
+ y = bl::fma(y, x, T{ -56 });
+ y = bl::fma(y, x, T{ 70 });
+ y = bl::fma(y, x, T{ -56 });
+ y = bl::fma(y, x, T{ 28 });
+ y = bl::fma(y, x, T{ -8 });
+ y = bl::fma(y, x, T{ 1 });
+ return y;
+}
+
+template
+constexpr T factored_shifted_power(T x)
+{
+ const T delta = x - T{ 1 };
+ return bl::ipow(delta, 8);
+}
+
+template
+void print_case(std::string_view name)
+{
+ const T x = bl::parse("1.0001");
+ const T expanded = expanded_shifted_power(x);
+ const T factored = factored_shifted_power(x);
+ const T diff = expanded - factored;
+ const T error = bl::abs(diff) / bl::abs(factored);
+
+ std::cout
+ << name << ":\n"
+ << std::setprecision(std::numeric_limits::max_digits10)
+ << " expanded Horner = " << expanded << "\n"
+ << " factored form = " << factored << "\n"
+ << " relative error = " << error << "\n\n";
+}
+
+int main()
+{
+ print_case("f64");
+ print_case("f128");
+ print_case("f256");
+}
diff --git a/examples/example_pow.cpp b/examples/example_pow.cpp
new file mode 100644
index 0000000..d76443f
--- /dev/null
+++ b/examples/example_pow.cpp
@@ -0,0 +1,227 @@
+#include
+#include
+#include
+#include
+
+#include
+using namespace bl;
+using namespace bl::literals;
+using namespace bl::int_literals;
+
+/*
+
+bl::pow precision policy:
+
+ Follows std::pow rules: integer arguments count as f64 for overload selection, so these return f64:
+ - bl::pow(10, 10)
+ - bl::pow(10.0f, 3)
+ - bl::pow(10, 3.0f)
+
+ f128/f256 act as wider floating types:
+ - f128 wins over f64
+ - f256 wins over f128
+
+
+bl::ipow precision policy:
+
+ - Keeps the base type as the result type.
+ - Debug builds assert when result overflows base type
+ - Release builds silently wrap
+
+*/
+
+
+#define assert_value_type(value, type) \
+ static_assert(std::same_as, type>)
+
+consteval void consteval_pow_tests()
+{
+ // pow(integer, ...): integers count as f64 for overload ranking.
+ constexpr auto a0 = bl::pow(4_u8, 2_u8); assert_value_type(a0, f64);
+ constexpr auto a1 = bl::pow(10_u8, 10_i16); assert_value_type(a1, f64);
+ constexpr auto a2 = bl::pow(2_u8, 7_i32); assert_value_type(a2, f64);
+ constexpr auto a3 = bl::pow(2_u8, 7); assert_value_type(a3, f64);
+ constexpr auto a4 = bl::pow(4_u8, 3.0f); assert_value_type(a4, f64);
+ constexpr auto a5 = bl::pow(10_u8, 12.0); assert_value_type(a5, f64);
+ constexpr auto a6 = bl::pow(10_u8, 12.0_dd); assert_value_type(a6, f128);
+ constexpr auto a7 = bl::pow(10_u8, 12.0_qd); assert_value_type(a7, f256);
+
+ constexpr auto b0 = bl::pow(10_i16, 4_u8); assert_value_type(b0, f64);
+ constexpr auto b1 = bl::pow(10_i16, 4_i16); assert_value_type(b1, f64);
+ constexpr auto b2 = bl::pow(10_i16, 4_i32); assert_value_type(b2, f64);
+ constexpr auto b3 = bl::pow(10_i16, 4); assert_value_type(b3, f64);
+ constexpr auto b4 = bl::pow(10_i16, 12.0f); assert_value_type(b4, f64);
+ constexpr auto b5 = bl::pow(10_i16, 12.0); assert_value_type(b5, f64);
+ constexpr auto b6 = bl::pow(10_i16, 12.0_dd); assert_value_type(b6, f128);
+ constexpr auto b7 = bl::pow(10_i16, 12.0_qd); assert_value_type(b7, f256);
+
+ constexpr auto c0 = bl::pow(10, 9_u8); assert_value_type(c0, f64);
+ constexpr auto c1 = bl::pow(10, 10); assert_value_type(c1, f64);
+ constexpr auto c2 = bl::pow(10, 12.0f); assert_value_type(c2, f64);
+ constexpr auto c3 = bl::pow(10, 12.0); assert_value_type(c3, f64);
+ constexpr auto c4 = bl::pow(10, 12.0_dd); assert_value_type(c4, f128);
+ constexpr auto c5 = bl::pow(10, 12.0_qd); assert_value_type(c5, f256);
+
+ // pow(floating, ...): f32 + integer returns f64, matching std::pow.
+ constexpr auto d0 = bl::pow(10.0f, 12.0f); assert_value_type(d0, f32);
+ constexpr auto d1 = bl::pow(10.0f, 12_u8); assert_value_type(d1, f64);
+ constexpr auto d2 = bl::pow(10.0f, 12); assert_value_type(d2, f64);
+ constexpr auto d3 = bl::pow(10.0f, 12_u64); assert_value_type(d3, f64);
+ constexpr auto d4 = bl::pow(10.0f, 12.0); assert_value_type(d4, f64);
+ constexpr auto d5 = bl::pow(10.0f, 12.0_dd); assert_value_type(d5, f128);
+ constexpr auto d6 = bl::pow(10.0f, 12.0_qd); assert_value_type(d6, f256);
+
+ constexpr auto e0 = bl::pow(10.0, 12.0f); assert_value_type(e0, f64);
+ constexpr auto e1 = bl::pow(10.0, 12_u8); assert_value_type(e1, f64);
+ constexpr auto e2 = bl::pow(10.0, 12); assert_value_type(e2, f64);
+ constexpr auto e3 = bl::pow(10.0, 12_u64); assert_value_type(e3, f64);
+ constexpr auto e4 = bl::pow(10.0, 12.0); assert_value_type(e4, f64);
+ constexpr auto e5 = bl::pow(10.0, 12.0_dd); assert_value_type(e5, f128);
+ constexpr auto e6 = bl::pow(10.0, 12.0_qd); assert_value_type(e6, f256);
+
+ constexpr auto f0 = bl::pow(10.0_dd, 12.0f); assert_value_type(f0, f128);
+ constexpr auto f1 = bl::pow(10.0_dd, 12_u8); assert_value_type(f1, f128);
+ constexpr auto f2 = bl::pow(10.0_dd, 12); assert_value_type(f2, f128);
+ constexpr auto f3 = bl::pow(10.0_dd, 12_u64); assert_value_type(f3, f128);
+ constexpr auto f4 = bl::pow(10.0_dd, 12.0); assert_value_type(f4, f128);
+ constexpr auto f5 = bl::pow(10.0_dd, 12.0_dd); assert_value_type(f5, f128);
+ constexpr auto f6 = bl::pow(10.0_dd, 12.0_qd); assert_value_type(f6, f256);
+
+ constexpr auto g0 = bl::pow(10.0_qd, 12.0f); assert_value_type(g0, f256);
+ constexpr auto g1 = bl::pow(10.0_qd, 12_u8); assert_value_type(g1, f256);
+ constexpr auto g2 = bl::pow(10.0_qd, 12); assert_value_type(g2, f256);
+ constexpr auto g3 = bl::pow(10.0_qd, 12_u64); assert_value_type(g3, f256);
+ constexpr auto g4 = bl::pow(10.0_qd, 12.0); assert_value_type(g4, f256);
+ constexpr auto g5 = bl::pow(10.0_qd, 12.0_dd); assert_value_type(g5, f256);
+ constexpr auto g6 = bl::pow(10.0_qd, 12.0_qd); assert_value_type(g6, f256);
+
+ static_assert(a1 == 10000000000.0);
+ static_assert(c1 == 10000000000.0);
+
+ // negative_exponent_overloads
+ {
+ constexpr auto a0 = bl::pow(2_u8, -3_i8); assert_value_type(a0, f64);
+ constexpr auto a1 = bl::pow(2_u8, -5_i16); assert_value_type(a1, f64);
+ constexpr auto a2 = bl::pow(2_i16, -7_i32); assert_value_type(a2, f64);
+ constexpr auto a3 = bl::pow(2, -9_i64); assert_value_type(a3, f64);
+ constexpr auto a4 = bl::pow(2, -3.0f); assert_value_type(a4, f64);
+ constexpr auto a5 = bl::pow(2, -3.0); assert_value_type(a5, f64);
+ constexpr auto a6 = bl::pow(2, -3.0_dd); assert_value_type(a6, f128);
+ constexpr auto a7 = bl::pow(2, -3.0_qd); assert_value_type(a7, f256);
+
+ constexpr auto b0 = bl::pow(2.0f, -3_i8); assert_value_type(b0, f64);
+ constexpr auto b1 = bl::pow(2.0f, -7_i32); assert_value_type(b1, f64);
+ constexpr auto b2 = bl::pow(2.0f, -3.0f); assert_value_type(b2, f32);
+ constexpr auto b3 = bl::pow(2.0f, -3.0); assert_value_type(b3, f64);
+ constexpr auto b4 = bl::pow(2.0f, -3.0_dd); assert_value_type(b4, f128);
+ constexpr auto b5 = bl::pow(2.0f, -3.0_qd); assert_value_type(b5, f256);
+
+ constexpr auto c0 = bl::pow(2.0, -3_i8); assert_value_type(c0, f64);
+ constexpr auto c1 = bl::pow(2.0, -7_i32); assert_value_type(c1, f64);
+ constexpr auto c2 = bl::pow(2.0, -3.0f); assert_value_type(c2, f64);
+ constexpr auto c3 = bl::pow(2.0, -3.0); assert_value_type(c3, f64);
+ constexpr auto c4 = bl::pow(2.0, -3.0_dd); assert_value_type(c4, f128);
+ constexpr auto c5 = bl::pow(2.0, -3.0_qd); assert_value_type(c5, f256);
+
+ constexpr auto d0 = bl::pow(2.0_dd, -3_i8); assert_value_type(d0, f128);
+ constexpr auto d1 = bl::pow(2.0_dd, -5_i16); assert_value_type(d1, f128);
+ constexpr auto d2 = bl::pow(2.0_dd, -7_i32); assert_value_type(d2, f128);
+ constexpr auto d3 = bl::pow(2.0_dd, -9_i64); assert_value_type(d3, f128);
+ constexpr auto d4 = bl::pow(2.0_dd, -3.0f); assert_value_type(d4, f128);
+ constexpr auto d5 = bl::pow(2.0_dd, -3.0); assert_value_type(d5, f128);
+ constexpr auto d6 = bl::pow(2.0_dd, -3.0_dd); assert_value_type(d6, f128);
+ constexpr auto d7 = bl::pow(2.0_dd, -3.0_qd); assert_value_type(d7, f256);
+
+ constexpr auto e0 = bl::pow(2.0_qd, -3_i8); assert_value_type(e0, f256);
+ constexpr auto e1 = bl::pow(2.0_qd, -5_i16); assert_value_type(e1, f256);
+ constexpr auto e2 = bl::pow(2.0_qd, -7_i32); assert_value_type(e2, f256);
+ constexpr auto e3 = bl::pow(2.0_qd, -9_i64); assert_value_type(e3, f256);
+ constexpr auto e4 = bl::pow(2.0_qd, -3.0f); assert_value_type(e4, f256);
+ constexpr auto e5 = bl::pow(2.0_qd, -3.0); assert_value_type(e5, f256);
+ constexpr auto e6 = bl::pow(2.0_qd, -3.0_dd); assert_value_type(e6, f256);
+ constexpr auto e7 = bl::pow(2.0_qd, -3.0_qd); assert_value_type(e7, f256);
+
+ constexpr auto f0 = bl::pow(-2_i16, -3_i8); assert_value_type(f0, f64);
+ constexpr auto f1 = bl::pow(-2.0_dd, -5_i16); assert_value_type(f1, f128);
+ constexpr auto f2 = bl::pow(-2.0_qd, -7_i32); assert_value_type(f2, f256);
+
+ static_assert(a0 == 0.125);
+ static_assert(a1 == 0.03125);
+ static_assert(a2 == 0.0078125);
+ static_assert(a3 == 0.001953125);
+ static_assert(d0 == 0.125_dd);
+ static_assert(e0 == 0.125_qd);
+ static_assert(f0 == -0.125);
+ }
+
+ // exponent_value_ranges
+ {
+ // These use deliberately different exponent storage types with values chosen
+ // to exercise the small-exp switch, its boundary, and the generic fallback.
+ constexpr auto a0 = bl::pow(1.25, 12_u8); assert_value_type(a0, f64);
+ constexpr auto a1 = bl::pow(1.25, 12_u64); assert_value_type(a1, f64);
+ constexpr auto a2 = bl::pow(1.001, 255_u64); assert_value_type(a2, f64);
+ constexpr auto a3 = bl::pow(1.001, 256_u64); assert_value_type(a3, f64);
+ constexpr auto a4 = bl::pow(1.0001, 4096_u64); assert_value_type(a4, f64);
+ constexpr auto a5 = bl::pow(1.00001, 65536_u64); assert_value_type(a5, f64);
+ constexpr auto a6 = bl::pow(1.0000001_qd, 12345678_u64); assert_value_type(a6, f256);
+
+ constexpr auto b0 = bl::pow(2.0_dd, -12_i8); assert_value_type(b0, f128);
+ constexpr auto b1 = bl::pow(2.0_dd, -12_i64); assert_value_type(b1, f128);
+
+ constexpr auto c0 = bl::pow(2.0f, 120_i32); assert_value_type(c0, f64);
+ constexpr auto c1 = bl::pow(2.0, 512_i32); assert_value_type(c1, f64);
+ constexpr auto c2 = bl::pow(2.0_dd, 512_i32); assert_value_type(c2, f128);
+ constexpr auto c3 = bl::pow(2.0_qd, 512_i32); assert_value_type(c3, f256);
+
+ constexpr auto d0 = bl::pow(10.0f, 30_i32); assert_value_type(d0, f64);
+ constexpr auto d1 = bl::pow(10.0, 30_i32); assert_value_type(d1, f64);
+ constexpr auto d2 = bl::pow(10.0_dd, 30_i32); assert_value_type(d2, f128);
+ constexpr auto d3 = bl::pow(10.0_qd, 30_i32); assert_value_type(d3, f256);
+ constexpr auto d4 = bl::pow(20.0_dd, 30_i32); assert_value_type(d4, f128);
+ constexpr auto d5 = bl::pow(125.0_qd, 24_i32); assert_value_type(d5, f256);
+
+ constexpr auto e0 = bl::pow(10.0_dd, -30_i32); assert_value_type(e0, f128);
+ constexpr auto e1 = bl::pow(10.0_qd, -30_i32); assert_value_type(e1, f256);
+ constexpr auto e2 = bl::pow(-20.0_dd, -30_i32); assert_value_type(e2, f128);
+
+ static_assert(d4 == bl::pow(f128{ 20 }, 30_i32));
+ static_assert(d5 == bl::pow(f256{ 125 }, 24_i32));
+ }
+}
+
+
+consteval void consteval_ipow_tests()
+{
+ // ipow(int-like, int-like): default return is the base type.
+ constexpr auto a0 = bl::ipow(4_u8, 2_u8); assert_value_type(a0, u8);
+ constexpr auto a1 = bl::ipow(4_u8, 3_i16); assert_value_type(a1, u8);
+ constexpr auto a2 = bl::ipow(2_u8, 7_i32); assert_value_type(a2, u8);
+ constexpr auto a3 = bl::ipow(2_u8, 7); assert_value_type(a3, u8);
+
+ constexpr auto b0 = bl::ipow(10_i16, 4_u8); assert_value_type(b0, i16);
+ constexpr auto b1 = bl::ipow(10_i16, 4_i16); assert_value_type(b1, i16);
+ constexpr auto b2 = bl::ipow(10_i16, 4_i32); assert_value_type(b2, i16);
+ constexpr auto b3 = bl::ipow(10_i16, 4); assert_value_type(b3, i16);
+
+ constexpr auto c0 = bl::ipow(10, 9_u8); assert_value_type(c0, int);
+ constexpr auto c1 = bl::ipow(10, 9); assert_value_type(c1, int);
+
+ #if 0 // Expected failures
+ {
+ // Integral ipow keeps the base type as the result type.
+ // Overflow is diagnosed during constant evaluation in all builds.
+ // Runtime overflow is checked only in Debug; Release runtime calls wrap silently.
+ constexpr auto a0 = bl::ipow(10_i16, 5_i16);
+ constexpr auto a1 = bl::ipow(10, 10);
+ }
+ #endif
+}
+
+int main()
+{
+ consteval_pow_tests();
+ consteval_ipow_tests();
+
+ return 0;
+}
diff --git a/examples/example_random.cpp b/examples/example_random.cpp
index e920c97..f45c17e 100644
--- a/examples/example_random.cpp
+++ b/examples/example_random.cpp
@@ -1,31 +1,49 @@
#include
#include
+#include
+#include
#include
using namespace bl;
using namespace bl::literals;
-template
-consteval f256 sum(std::array values)
+template
+consteval T avg(const std::array& values)
{
- f256 total = 0;
+ T total = 0;
for (const auto& v : values)
total += v;
- return total;
+ return total / N;
}
int main()
{
- constexpr auto constexpr_values = bl::random_array<4>(
- bl::mt19937_64{ 0x1020304050607080ull },
- bl::uniform_real_distribution{ -0_qd, 10_qd }
- );
+ // f256: random uniform arrays (min=0, max=100)
+ constexpr auto a = bl::uniform_real_array<100>(0_qd, 100_qd, bl::mt19937_64{ 0x123ull }); // explicit engine / custom seed
+ constexpr auto b = bl::uniform_real_array<100>(0_qd, 100_qd, 0x123ull); // default engine / custom seed
+ constexpr auto c = bl::uniform_real_array<100>(0_qd, 100_qd); // default engine / default seed
- constexpr f256 total = sum(constexpr_values);
+ // f128: random normal array (mean=50, stddev=50)
+ constexpr auto d = bl::normal_array<100>(50_dd, 50_dd, bl::mt19937_64{ 0x123ull }); // explicit engine / custom seed
+ constexpr auto e = bl::normal_array<100>(50_dd, 50_dd, 0x123ull); // default engine / custom seed
+ constexpr auto f = bl::normal_array<100>(50_dd, 50_dd); // default engine / default seed
+
+ constexpr f256 a_avg = avg(a);
+ constexpr f256 b_avg = avg(b);
+ constexpr f256 c_avg = avg(c);
+
+ constexpr f128 d_avg = avg(d);
+ constexpr f128 e_avg = avg(e);
+ constexpr f128 f_avg = avg(f);
std::cout
<< std::fixed
<< std::setprecision(std::numeric_limits::digits10)
- << "sum: " << total;
+ << "a_avg: " << a_avg << "\n"
+ << "b_avg: " << b_avg << "\n"
+ << "c_avg: " << c_avg << "\n\n"
+ << "d_avg: " << d_avg << "\n"
+ << "e_avg: " << e_avg << "\n"
+ << "f_avg: " << f_avg << "\n";
}
diff --git a/examples/example_special_values.cpp b/examples/example_special_values.cpp
new file mode 100644
index 0000000..c069267
--- /dev/null
+++ b/examples/example_special_values.cpp
@@ -0,0 +1,79 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace bl;
+using namespace bl::literals;
+
+const char* class_name(int cls)
+{
+ switch (cls)
+ {
+ case FP_INFINITE: return "infinite";
+ case FP_NAN: return "nan";
+ case FP_NORMAL: return "normal";
+ case FP_SUBNORMAL: return "subnormal";
+ case FP_ZERO: return "zero";
+ default: return "unknown";
+ }
+}
+
+template
+void describe(std::string_view name, T value)
+{
+ std::cout
+ << std::boolalpha
+ << name << ":\n"
+ << " class = " << class_name(bl::fpclassify(value)) << "\n"
+ << " finite = " << bl::isfinite(value) << "\n"
+ << " infinite = " << bl::isinf(value) << "\n"
+ << " nan = " << bl::isnan(value) << "\n"
+ << " signbit = " << bl::signbit(value) << "\n\n";
+}
+
+int main()
+{
+ using T = f256;
+
+ describe("positive zero", T{ 0.0 });
+ describe("negative zero", T{ -0.0 });
+ describe("denorm_min", std::numeric_limits::denorm_min());
+ describe("infinity", std::numeric_limits::infinity());
+ describe("quiet_NaN", std::numeric_limits::quiet_NaN());
+
+ const T value = bl::parse("-12.75");
+
+ int exponent{};
+ const T significand = bl::frexp(value, &exponent);
+ const T rebuilt = bl::ldexp(significand, exponent);
+
+ T integer_part{};
+ const T fraction = bl::modf(value, &integer_part);
+
+ std::cout
+ << std::setprecision(std::numeric_limits::max_digits10)
+ << "frexp(-12.75): significand = " << significand << ", exponent = " << exponent << "\n"
+ << "ldexp(significand, exponent): " << rebuilt << "\n"
+ << "modf(-12.75): integer = " << integer_part << ", fraction = " << fraction << "\n"
+ << "nextafter(0, 1): " << bl::nextafter(T{ 0 }, T{ 1 }) << "\n\n";
+
+
+ constexpr int counter_width = 3;
+ constexpr int zeros_before_counter =
+ std::numeric_limits::digits10 - counter_width; // 60 for f256
+
+ constexpr int places = zeros_before_counter + counter_width;
+ const T step = bl::pow(10_qd, -places);
+
+ std::cout << std::fixed << std::setprecision(places);
+
+ for (int i = 1; i <= 4; ++i)
+ {
+ const T v = T{ i } * step;
+ std::cout << v << "\n";
+ }
+}
diff --git a/examples/example_std_integration.cpp b/examples/example_std_integration.cpp
new file mode 100644
index 0000000..c8b21e4
--- /dev/null
+++ b/examples/example_std_integration.cpp
@@ -0,0 +1,49 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace bl;
+using namespace bl::literals;
+
+int main()
+{
+ const f256 pi = std::numbers::pi_v;
+
+ std::cout
+ << "sizeof(f128): " << sizeof(f128) << "\n"
+ << "sizeof(f256): " << sizeof(f256) << "\n"
+ << "f128 digits10: " << std::numeric_limits::digits10 << "\n"
+ << "f256 digits10: " << std::numeric_limits::digits10 << "\n\n";
+
+ std::cout
+ << std::setprecision(std::numeric_limits::digits10)
+ << "std::numbers::pi_v: " << pi << "\n\n";
+
+ std::unordered_map labels;
+ labels.emplace(0.1_dd, "one tenth");
+ labels.emplace(0.25_dd, "one quarter");
+
+ const auto found = labels.find(0.1_dd);
+ if (found != labels.end())
+ std::cout << "unordered_map lookup for 0.1: " << found->second << "\n\n";
+
+ const f256 nan = std::numeric_limits::quiet_NaN();
+ const auto ordering = nan <=> pi;
+ std::cout << std::boolalpha
+ << "NaN comparison is unordered: "
+ << (ordering == std::partial_ordering::unordered) << "\n\n";
+
+#if FLTX_HAS_STD_FORMAT
+ std::cout << std::format("std::format fixed: {:.40f}\n", pi);
+ std::cout << std::format("std::format padded: {:>72.60g}\n", pi);
+#else
+ std::cout << "std::format is not available in this standard library.\n";
+#endif
+}
diff --git a/examples/example_storage_interop.cpp b/examples/example_storage_interop.cpp
new file mode 100644
index 0000000..88dc1ca
--- /dev/null
+++ b/examples/example_storage_interop.cpp
@@ -0,0 +1,79 @@
+#include
+#include
+#include
+#include
+#include
+#include