name: new-number-type
description: 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.
user-invocable: true
argument-hint: " [template-params...]"
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
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 likeposit<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.)
- How many template parameters? (1-param like
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/
- 1-param:
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;NOTuint8_t _bits{ 0 }; ReportTrivialityOfType<T>()willstatic_assertfail if the type isn't trivial- Default constructor must have empty body or be
= default
Constexpr
- Do NOT mark constructors/assignment operators
constexprif they callstd::frexp,std::ldexp,std::log2, etc. - Only use
constexprif the conversion path uses onlystd::memcpyor integer arithmetic
Exception Hierarchy
- Number systems inherit from
universal_arithmetic_exception/universal_internal_exception - Internal building blocks inherit from
std::runtime_error(NEVER fromuniversal_*) - Dependencies flow strictly downward: number system -> internal block -> never upward
- Each type has its OWN exception guard macro (e.g.,
TYPENAME_THROW_ARITHMETIC_EXCEPTION) - The umbrella header forwards its exception config to building blocks:
#if !defined(TYPENAME_THROW_ARITHMETIC_EXCEPTION) #define TYPENAME_THROW_ARITHMETIC_EXCEPTION 0 #if !defined(BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION) #define BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION 0 #endif #else #if !defined(BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION) #define BLOCKBINARY_THROW_ARITHMETIC_EXCEPTION TYPENAME_THROW_ARITHMETIC_EXCEPTION #endif #endif
Portability
- Never use
long doublemanual bit-shift division — usestd::ldexp(1.0l, exponent)instead - Always initialize
blockbinarytemporaries (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_assertfailures) - 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 |