Skip to content

Float Cascade to model multi-component floats

Let’s walk through how this FloatCascade<N> building block architecture naturally extends to support adaptive precision priest. The key insight is that priest uses a variable-length cascade that can grow and shrink as needed.

Here’s how the FloatCascade<N> building block architecture supports adaptive precision priest:

1. VariableCascade - Dynamic Version of FloatCascade

Section titled “1. VariableCascade - Dynamic Version of FloatCascade”
  • Uses std::vector<double> instead of std::array<double, N>

  • Can grow/shrink during computation

  • Same component ordering principles as fixed-size cascades

  • Provides seamless conversion to/from FloatCascade<N>

2. Bidirectional Conversion Between Fixed and Adaptive

Section titled “2. Bidirectional Conversion Between Fixed and Adaptive”
// Fixed → Adaptive (promotion)
dd d1(1.0);
priest p1(d1); // Seamless conversion
// Adaptive → Fixed (truncation/extraction)
priest result = some.complex_calculation();
dd final.dd = result.to_dd(); // Extract to double-double
td final.td = result.to_td(); // Extract to triple-double

The priest class uses AdaptivePrecision config to control:

  • Termination criteria: When to stop iterating (1/3 problem solved!)
  • Component limits: Max/min cascade length
  • Tolerance: Absolute vs relative error bounds
  • Performance: Early termination, auto-compression
struct AdaptivePrecision {
double relative_tolerance;
unsigned max_components;
unsigned max_iterations;
};
AdaptivePrecision high_precision{
.relative_tolerance = 1e-25, // Very tight tolerance
.max_components = 20, // Allow up to 20 components
.max_iterations = 50 // Limit iterations
};
priest one(1.0, high.precision);
priest three(3.0, high.precision);
priest one.third = one / three; // Adapts precision automatically!

The division algorithm:

  1. Starts with hardware precision estimate
  2. Iterates using Newton-Raphson refinement
  3. Terminates when error tolerance is met OR max iterations reached
  4. Controls component growth to prevent explosion
  5. Compresses result to remove unnecessary precision
// Start with fixed precision (fast)
dd quick.calc = dd(1.0) / dd(3.0); // Limited precision
// Need more precision? Promote seamlessly
priest precise.calc = priest(quick_calc);
precise.calc = priest(1.0) / priest(3.0); // Now adaptive!
// Back to fixed for performance-critical code
td final.result = precise_calc.to_td();

All types (dd, td, qd, priest) use the same core algorithms:

  • two_sum, fast_two_sum for basic operations
  • Expansion addition/multiplication algorithms
  • Same mathematical foundations, different precision strategies
  1. No Precision Walls: Smooth upgrade path from ddtdqdpriest
  2. Performance Flexibility: Use fixed precision when speed matters, adaptive when accuracy matters
  3. Building Block Reuse: Same FloatCascade principles throughout
  4. Controlled Iteration: Solves the “infinite 1/3” problem with configurable termination
  5. Universal Compatibility: All types can interchange via FloatCascade extraction

This design gives us what Priest intended: adaptive precision that does only as much work as necessary to guarantee a correct result, while maintaining Universal’s principle of independent, interoperable number systems!