Scaffold a New Number System Type
Create all required files, CMake wiring, and test structure for a new number type in the Universal library.
Arguments
$ARGUMENTS — the type name (e.g., takum) and optionally a description of the template parameters. If not provided, ask the user.
Before You Start
Ask the user:
- How many template parameters? (1-param like
integer<nbits> or 2-param like posit<nbits, es>)
- Is this a static (fixed-size) or elastic (adaptive) type?
- What category? (integer, fixed-point, float, tapered, logarithmic, block format)
- What internal building blocks does it use? (blockbinary, blocksignificand, blocktriple, etc.)
Read the matching skeleton template to use as the structural reference:
- 1-param:
include/sw/universal/number/skeleton_1param/
- 2-param:
include/sw/universal/number/skeleton_2params/
Read an existing similar type for behavioral reference (e.g., posit for tapered, cfloat for float, fixpnt for fixed-point).
CRITICAL Rules
These are hard-won lessons from past incidents. Violating them causes build failures or CI rejections.
Triviality
- Number types MUST be trivially constructible
- NO in-class member initializers: use
uint8_t _bits; NOT uint8_t _bits{ 0 };
ReportTrivialityOfType<T>() will static_assert fail if the type isn't trivial
- Default constructor must have empty body or be
= default
Constexpr
- Do NOT mark constructors/assignment operators
constexpr if they call std::frexp, std::ldexp, std::log2, etc.
- Only use
constexpr if the conversion path uses only std::memcpy or integer arithmetic
Exception Hierarchy
Portability
- Never use
long double manual bit-shift division — use std::ldexp(1.0l, exponent) instead
- Always initialize
blockbinary temporaries (clang doesn't zero stack like gcc)
- Test with BOTH gcc AND clang before committing
File Creation Order
Create files in this exact order (dependencies flow top-to-bottom):
Step 1: Header files in include/sw/universal/number/TYPE/
| File |
Purpose |
Key contents |
TYPE_fwd.hpp |
Forward declarations + type aliases |
template<params> class TYPE; + convenience aliases |
exceptions.hpp |
Exception hierarchy |
TYPE_arithmetic_exception, TYPE_divide_by_zero, TYPE_internal_exception |
TYPE_impl.hpp |
Main class implementation |
Full class with constructors, operators, conversions |
numeric_limits.hpp |
std::numeric_limits specialization |
All required constants and static functions |
manipulators.hpp |
type_tag(), to_binary(), color_print(), range() |
Use enable_if_t<is_TYPE<T>> pattern |
attributes.hpp |
Free functions for type properties |
sign(), scale(), TYPE_range() |
TYPE.hpp |
Umbrella header |
Includes everything in correct order (see below) |
Umbrella header include order (MUST follow this sequence):
1. Compiler directives (compiler.hpp, architecture.hpp, bit_cast.hpp, long_double.hpp)
2. Required stdlib (<iostream>, <iomanip>)
3. Behavioral compilation switches (TYPENAME_THROW_ARITHMETIC_EXCEPTION, etc.)
4. Exception config forwarding to building blocks
5. Trait function headers (number_traits.hpp, arithmetic_traits.hpp)
6. exceptions.hpp
7. TYPE_fwd.hpp
8. TYPE_impl.hpp
9. TYPE_traits.hpp (from traits/ directory)
10. numeric_limits.hpp
11. manipulators.hpp
12. attributes.hpp
13. mathlib.hpp (if applicable)
Step 2: Traits file in include/sw/universal/traits/
| File |
Purpose |
TYPE_traits.hpp |
is_TYPE trait, is_TYPE_trait struct, enable_if_TYPE alias |
The trait MUST match the exact template parameters of the class.
Step 3: Test directory structure
Create the test directory under the appropriate category. The repo uses
static/<CATEGORY>/<TYPE>/ where CATEGORY groups related types:
| Category |
Types |
tapered/ |
posit, takum, unum2 |
float/ |
cfloat, bfloat16, dfloat, hfloat, e8m0 |
logarithmic/ |
lns, dbns |
fixpnt/ |
binary, decimal |
integer/ |
binary, decimal, octal, hexadecimal |
block/ |
microfloat, mxblock, nvblock |
static/<CATEGORY>/TYPE/ (or elastic/TYPE/ for adaptive types)
CMakeLists.txt
api/
api.cpp # Primary API test — start here
conversion/
(empty initially)
logic/
(empty initially)
arithmetic/
(empty initially)
math/
(empty initially)
complex/
(empty initially, only populated when BUILD_COMPLEX=ON)
Step 4: Test CMakeLists.txt
Use the standard pattern. The compile_all label path follows:
"Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/<testdir>"
Check an existing sibling type's CMakeLists.txt for the exact label prefix.
| Number category |
Encoding |
Label prefix example |
floating-point |
binary |
cfloat, bfloat16, dd, microfloat |
floating-point |
decimal |
dfloat |
floating-point |
hexadecimal |
hfloat |
floating-point |
logarithmic |
lns, dbns |
floating-point |
tapered |
posit, takum |
fixed-point |
binary |
fixpnt |
fixed-point |
decimal |
dfixpnt |
integer |
binary |
integer |
integer |
decimal |
dint |
rational |
binary |
rational |
file(GLOB API_SRC "api/*.cpp")
file(GLOB CONVERSION_SRC "conversion/*.cpp")
file(GLOB LOGIC_SRC "logic/*.cpp")
file(GLOB ARITHMETIC_SRC "arithmetic/*.cpp")
file(GLOB MATH_SRC "math/*.cpp")
file(GLOB COMPLEX_SRC "complex/*.cpp")
# Example for lns: "Number Systems/static/floating-point/logarithmic/lns/api"
# Example for fixpnt: "Number Systems/static/fixed-point/binary/fixpnt/api"
# Example for dint: "Number Systems/static/integer/decimal/dint/api"
compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/api" "${API_SRC}")
compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/conversion" "${CONVERSION_SRC}")
compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/logic" "${LOGIC_SRC}")
compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/arithmetic" "${ARITHMETIC_SRC}")
compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/math" "${MATH_SRC}")
compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/complex" "${COMPLEX_SRC}")
Step 5: CMake wiring in root CMakeLists.txt
4 insertion points (find the right alphabetical position among existing types):
Option definition (~line 160):
option(UNIVERSAL_BUILD_NUMBER_TYPE "Set to ON to build TYPE tests" OFF)
UNIVERSAL_BUILD_NUMBER_STATICS cascade (~line 831):
set(UNIVERSAL_BUILD_NUMBER_TYPE ON)
add_subdirectory block (~line 974):
if(UNIVERSAL_BUILD_NUMBER_TYPE)
add_subdirectory("static/<CATEGORY>/TYPE")
endif(UNIVERSAL_BUILD_NUMBER_TYPE)
CI_LITE cascade (~line 756, optional — only if portability-critical):
set(UNIVERSAL_BUILD_NUMBER_TYPE ON)
Step 6: Initial api.cpp test
Create a minimal test that:
- Includes the umbrella header
- Sets
TYPENAME_THROW_ARITHMETIC_EXCEPTION 1
- Tests default construction
- Tests construction from native types (int, float, double)
- Tests
type_tag() output
- Tests
ReportTrivialityOfType<TYPE<config>>()
- Uses
ReportTestSuiteHeader() / ReportTestSuiteResults() pattern
- Has full exception catch blocks
Template Parameter Naming Conventions
| Parameter |
Name |
Notes |
| Total bits |
nbits |
NOT N or bits |
| Exponent bits |
es |
NOT E or exponent_bits |
| Fraction bits |
rbits or fbits |
Depends on type |
| Block type |
bt |
Default to uint8_t |
| In friend declarations |
nnbits, nes, nbt |
Prefix with n |
Verification Checklist
After creating all files:
- Build with gcc:
cmake --build --preset gcc-debug --target TYPE_api
- Run the test:
build/gcc-debug/static/TYPE/TYPE_api
- Build with clang:
cmake --build --preset clang-debug --target TYPE_api
- Run the clang test:
build/clang-debug/static/TYPE/TYPE_api
- Verify triviality passes (no
static_assert failures)
- Verify
type_tag() produces expected output
Reference Implementations
For behavioral reference, read an existing type that's similar:
| If your type is... |
Study this implementation |
| Tapered floating-point |
posit/posit_impl.hpp |
| Classic floating-point |
cfloat/cfloat_impl.hpp |
| Fixed-point |
fixpnt/fixpnt_impl.hpp |
| Logarithmic |
lns/lns_impl.hpp |
| Integer |
integer/integer_impl.hpp |
| Block format |
mxblock/mxblock_impl.hpp |
| Double-double |
dd/dd_impl.hpp |
1---2name: new-number-type3description: Scaffold a new number system type with all required files, CMake wiring, exception hierarchy, traits, tests, and numeric_limits. Use when adding a new arithmetic type to the Universal library.4---56# Scaffold a New Number System Type78Create all required files, CMake wiring, and test structure for a new number type in the Universal library.910## Arguments1112`$ARGUMENTS` — the type name (e.g., `takum`) and optionally a description of the template parameters. If not provided, ask the user.1314## Before You Start15161. Ask the user:17 - How many template parameters? (1-param like `integer<nbits>` or 2-param like `posit<nbits, es>`)18 - Is this a **static** (fixed-size) or **elastic** (adaptive) type?19 - What category? (integer, fixed-point, float, tapered, logarithmic, block format)20 - What internal building blocks does it use? (blockbinary, blocksignificand, blocktriple, etc.)21222. Read the matching skeleton template to use as the structural reference:23 - 1-param: `include/sw/universal/number/skeleton_1param/`24 - 2-param: `include/sw/universal/number/skeleton_2params/`25263. Read an existing similar type for behavioral reference (e.g., posit for tapered, cfloat for float, fixpnt for fixed-point).2728## CRITICAL Rules2930These are hard-won lessons from past incidents. Violating them causes build failures or CI rejections.3132### Triviality33- Number types MUST be trivially constructible34- **NO in-class member initializers**: use `uint8_t _bits;` NOT `uint8_t _bits{ 0 };`35- `ReportTrivialityOfType<T>()` will `static_assert` fail if the type isn't trivial36- Default constructor must have empty body or be `= default`3738### Constexpr39- Do NOT mark constructors/assignment operators `constexpr` if they call `std::frexp`, `std::ldexp`, `std::log2`, etc.40- Only use `constexpr` if the conversion path uses only `std::memcpy` or integer arithmetic4142### Exception Hierarchy43- Number systems inherit from `universal_arithmetic_exception` / `universal_internal_exception`44- Internal building blocks inherit from `std::runtime_error` (NEVER from `universal_*`)45- Dependencies flow strictly **downward**: number system -> internal block -> never upward46- Each type has its OWN exception guard macro (e.g., `TYPENAME_THROW_ARITHMETIC_EXCEPTION`)47- The umbrella header forwards its exception config to building blocks:48 ```cpp49 #if !defined(TYPENAME_THROW_ARITHMETIC_EXCEPTION)50 #define TYPENAME_THROW_ARITHMETIC_EXCEPTION 051 #if !defined(BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION)52 #define BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION 053 #endif54 #else55 #if !defined(BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION)56 #define BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION TYPENAME_THROW_ARITHMETIC_EXCEPTION57 #endif58 #endif59 ```6061### Portability62- Never use `long double` manual bit-shift division — use `std::ldexp(1.0l, exponent)` instead63- Always initialize `blockbinary` temporaries (clang doesn't zero stack like gcc)64- Test with BOTH gcc AND clang before committing6566## File Creation Order6768Create files in this exact order (dependencies flow top-to-bottom):6970### Step 1: Header files in `include/sw/universal/number/TYPE/`7172| File | Purpose | Key contents |73|------|---------|-------------|74| `TYPE_fwd.hpp` | Forward declarations + type aliases | `template<params> class TYPE;` + convenience aliases |75| `exceptions.hpp` | Exception hierarchy | `TYPE_arithmetic_exception`, `TYPE_divide_by_zero`, `TYPE_internal_exception` |76| `TYPE_impl.hpp` | Main class implementation | Full class with constructors, operators, conversions |77| `numeric_limits.hpp` | `std::numeric_limits` specialization | All required constants and static functions |78| `manipulators.hpp` | `type_tag()`, `to_binary()`, `color_print()`, `range()` | Use `enable_if_t<is_TYPE<T>>` pattern |79| `attributes.hpp` | Free functions for type properties | `sign()`, `scale()`, `TYPE_range()` |80| `TYPE.hpp` | Umbrella header | Includes everything in correct order (see below) |8182**Umbrella header include order** (MUST follow this sequence):83```text841. Compiler directives (compiler.hpp, architecture.hpp, bit_cast.hpp, long_double.hpp)852. Required stdlib (<iostream>, <iomanip>)863. Behavioral compilation switches (TYPENAME_THROW_ARITHMETIC_EXCEPTION, etc.)874. Exception config forwarding to building blocks885. Trait function headers (number_traits.hpp, arithmetic_traits.hpp)896. exceptions.hpp907. TYPE_fwd.hpp918. TYPE_impl.hpp929. TYPE_traits.hpp (from traits/ directory)9310. numeric_limits.hpp9411. manipulators.hpp9512. attributes.hpp9613. mathlib.hpp (if applicable)97```9899### Step 2: Traits file in `include/sw/universal/traits/`100101| File | Purpose |102|------|---------|103| `TYPE_traits.hpp` | `is_TYPE` trait, `is_TYPE_trait` struct, `enable_if_TYPE` alias |104105The trait MUST match the exact template parameters of the class.106107### Step 3: Test directory structure108109Create the test directory under the appropriate category. The repo uses110`static/<CATEGORY>/<TYPE>/` where CATEGORY groups related types:111112| Category | Types |113|----------|-------|114| `tapered/` | posit, takum, unum2 |115| `float/` | cfloat, bfloat16, dfloat, hfloat, e8m0 |116| `logarithmic/` | lns, dbns |117| `fixpnt/` | binary, decimal |118| `integer/` | binary, decimal, octal, hexadecimal |119| `block/` | microfloat, mxblock, nvblock |120121```text122static/<CATEGORY>/TYPE/ (or elastic/TYPE/ for adaptive types)123 CMakeLists.txt124 api/125 api.cpp # Primary API test — start here126 conversion/127 (empty initially)128 logic/129 (empty initially)130 arithmetic/131 (empty initially)132 math/133 (empty initially)134 complex/135 (empty initially, only populated when BUILD_COMPLEX=ON)136```137138### Step 4: Test CMakeLists.txt139140Use the standard pattern. The `compile_all` label path follows:141`"Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/<testdir>"`142143Check an existing sibling type's CMakeLists.txt for the exact label prefix.144145| Number category | Encoding | Label prefix example |146|----------------|----------|---------------------|147| `floating-point` | `binary` | `cfloat`, `bfloat16`, `dd`, `microfloat` |148| `floating-point` | `decimal` | `dfloat` |149| `floating-point` | `hexadecimal` | `hfloat` |150| `floating-point` | `logarithmic` | `lns`, `dbns` |151| `floating-point` | `tapered` | `posit`, `takum` |152| `fixed-point` | `binary` | `fixpnt` |153| `fixed-point` | `decimal` | `dfixpnt` |154| `integer` | `binary` | `integer` |155| `integer` | `decimal` | `dint` |156| `rational` | `binary` | `rational` |157158```cmake159file(GLOB API_SRC "api/*.cpp")160file(GLOB CONVERSION_SRC "conversion/*.cpp")161file(GLOB LOGIC_SRC "logic/*.cpp")162file(GLOB ARITHMETIC_SRC "arithmetic/*.cpp")163file(GLOB MATH_SRC "math/*.cpp")164file(GLOB COMPLEX_SRC "complex/*.cpp")165166# Example for lns: "Number Systems/static/floating-point/logarithmic/lns/api"167# Example for fixpnt: "Number Systems/static/fixed-point/binary/fixpnt/api"168# Example for dint: "Number Systems/static/integer/decimal/dint/api"169compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/api" "${API_SRC}")170compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/conversion" "${CONVERSION_SRC}")171compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/logic" "${LOGIC_SRC}")172compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/arithmetic" "${ARITHMETIC_SRC}")173compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/math" "${MATH_SRC}")174compile_all("true" "TYPE" "Number Systems/static/<NUMBER_CATEGORY>/<ENCODING>/TYPE/complex" "${COMPLEX_SRC}")175```176177### Step 5: CMake wiring in root CMakeLists.txt178179**4 insertion points** (find the right alphabetical position among existing types):1801811. **Option definition** (~line 160):182 ```cmake183 option(UNIVERSAL_BUILD_NUMBER_TYPE "Set to ON to build TYPE tests" OFF)184 ```1851862. **UNIVERSAL_BUILD_NUMBER_STATICS cascade** (~line 831):187 ```cmake188 set(UNIVERSAL_BUILD_NUMBER_TYPE ON)189 ```1901913. **add_subdirectory block** (~line 974):192 ```cmake193 if(UNIVERSAL_BUILD_NUMBER_TYPE)194 add_subdirectory("static/<CATEGORY>/TYPE")195 endif(UNIVERSAL_BUILD_NUMBER_TYPE)196 ```1971984. **CI_LITE cascade** (~line 756, optional — only if portability-critical):199 ```cmake200 set(UNIVERSAL_BUILD_NUMBER_TYPE ON)201 ```202203### Step 6: Initial api.cpp test204205Create a minimal test that:206- Includes the umbrella header207- Sets `TYPENAME_THROW_ARITHMETIC_EXCEPTION 1`208- Tests default construction209- Tests construction from native types (int, float, double)210- Tests `type_tag()` output211- Tests `ReportTrivialityOfType<TYPE<config>>()`212- Uses `ReportTestSuiteHeader()` / `ReportTestSuiteResults()` pattern213- Has full exception catch blocks214215## Template Parameter Naming Conventions216217| Parameter | Name | Notes |218|-----------|------|-------|219| Total bits | `nbits` | NOT `N` or `bits` |220| Exponent bits | `es` | NOT `E` or `exponent_bits` |221| Fraction bits | `rbits` or `fbits` | Depends on type |222| Block type | `bt` | Default to `uint8_t` |223| In friend declarations | `nnbits`, `nes`, `nbt` | Prefix with `n` |224225## Verification Checklist226227After creating all files:2282291. Build with gcc: `cmake --build --preset gcc-debug --target TYPE_api`2302. Run the test: `build/gcc-debug/static/TYPE/TYPE_api`2313. Build with clang: `cmake --build --preset clang-debug --target TYPE_api`2324. Run the clang test: `build/clang-debug/static/TYPE/TYPE_api`2335. Verify triviality passes (no `static_assert` failures)2346. Verify `type_tag()` produces expected output235236## Reference Implementations237238For behavioral reference, read an existing type that's similar:239240| If your type is... | Study this implementation |241|--------------------|--------------------------|242| Tapered floating-point | `posit/posit_impl.hpp` |243| Classic floating-point | `cfloat/cfloat_impl.hpp` |244| Fixed-point | `fixpnt/fixpnt_impl.hpp` |245| Logarithmic | `lns/lns_impl.hpp` |246| Integer | `integer/integer_impl.hpp` |247| Block format | `mxblock/mxblock_impl.hpp` |248| Double-double | `dd/dd_impl.hpp` |