Skip to content

Releases

formula-cpp 0.5.0

Choose a tag to compare

@christianparpart christianparpart released this 07 Oct 05:10
c18dbd3

formula-cpp 0.5.0

The fifth release of formula-cpp, a header-only C++23 library for traceable formulas. You write a formula once
in C++. Dimensions are checked at compile time, units are converted exactly, and evaluation runs in exact rational
arithmetic. The same formula renders as documentation and traces step by step into an audit trail.

This release makes the library easier to learn and lets it take units, limits and numbers that arrive at run time:

  • A tutorial. Nine chapters build one compressive-strength test step by step, and six more cover the rest of the
    library. Every snippet and every output on its pages comes from a program the test suite builds and runs.
  • Run-time units, limits and numbers. Unit symbols and ASCII keys from run-time text, one-sided bounds, limits
    checked at run time, a quantity's own decimal places, and decimal text parsed into its exact value.

Documentation, with the tutorial, the guides, a gallery and the API reference:
https://lastrada-software.github.io/formula-cpp/

Requirements

  • C++23, CMake 3.23 or newer, and GCC 14 or newer.
  • CI builds and tests every change on:
    • MSVC cl and clang-cl (Windows);
    • Clang and GCC 14 (Linux), including an UndefinedBehaviorSanitizer build;
    • Clang on macOS.

Installation

CPMAddPackage("gh:LASTRADA-Software/formula-cpp@0.5.0")
target_link_libraries(your_target PRIVATE formula-cpp::formula-cpp)

find_package(formula-cpp CONFIG REQUIRED) from an install tree, or copying include/, work as well.

What is new

  • Documentation.
    • A tutorial in two tracks: nine chapters that grow one compressive-strength test, from a first formula
      through units, exact numbers, missing and entered values, rounding, constraints, documentation, tracing and
      worksheets, and six independent chapters on lookup tables, methods and overlays, series, statistics, records,
      and opaque operations with bounded retry.
    • The README is a short landing page: a minimal example with its output, and installation with CPM,
      find_package or the headers alone.
    • No drift between pages and code. Tutorial and README code is included from compiled programs, and each
      program's whole output is compared with a checked-in file. The documentation describes the library as it is;
      the changelog alone records history, and a test enforces it.
    • examples/cycling_speed.cpp, a cyclist's steady-state speed from power, calculated on a worksheet.
  • Units from run-time text.
    • checked_symbol() builds a unit Symbol from run-time text byte for byte, returning
      std::expected<Symbol, SymbolError> (TooLong, EmbeddedNull). It never truncates and never aborts.
    • A stable ASCII key per unit. Unit gains asciiText, a key for serialising the unit that stays the same
      when its display symbol is restyled. view_ascii, has_ascii_key and checked_ascii_symbol() read and build
      it. The built-in units with non-ASCII symbols declare keys: permille, um, degC, degF.
  • Limits.
    • One-sided bounds. at_least() and at_most() declare a minimum or a maximum alone, beside bounds().
    • Limits held at run time. checked_within(value, lowEnd, highEnd) checks a Rational or a Measured
      against optional ends from a specification row or a catalogue entry, with no Unit built to carry them.
  • Numbers.
    • A quantity's own decimal places. Quantity takes an optional fifth parameter, DecimalPlaces, so two
      quantities in one unit can read to different precision. same_unit says whether two units are the same unit,
      whatever their declared decimals and bounds.
    • parse_decimal_text reads decimal text from a CSV import, a form field or a configuration value into its
      exact value and the places it was typed to: "2.400" is 12/5 at 3 places. _r literals take a 128-bit
      mantissa and scale from 10^-38 to 10^38.
    • checked_transform and checked_combine apply a callback that can fail to measured values, and stay
      noexcept when the callback is.
  • A bound formula is an operand. A formula bound with yields<Q>(...) can be used inside another formula, where
    it stands for the formula it holds: yields<Strength>(var<Load> / loadedArea). That covers the arithmetic and
    comparison operators and every function that builds a formula. .expression still compiles and means the same.
  • Coverage reporting. A workflow measures the test suite's coverage of include/ for Codecov.

Upgrading from 0.4.0

A minor release may break the public API before 1.0, and this one does. The CHANGELOG lists every change under
Changed; these four are marked Breaking:

  • symbol() is consteval. Its spelling must be a constant expression. Build a symbol from run-time text with
    checked_symbol().
  • SymbolCapacity is 32 bytes, up from 16. Symbol, Dimension, Unit and a trace's Step are larger, so a
    trace takes more memory.
  • A unit whose symbol is not ASCII must declare an ASCII key, .asciiText = formula::symbol("ug/L") for µg/L,
    or its use as a quantity's, constant's, rounding's or table's unit no longer compiles:
    formula: a unit whose symbol is not ASCII must declare an ASCII key.
    • Unit has a new member after symbolText, so a Unit built with positional initialisers must move to
      designated initialisers.
    • Unit equality compares asciiText too. Use same_unit to ask whether two units are the same unit.
  • Bounds::present is replaced by lowPresent and highPresent. A Bounds built with positional initialisers
    in the old order no longer compiles; { true } alone now declares a minimum of 0 and no maximum. Use bounds(),
    at_least(), at_most() or designated initialisers. Code that read present reads lowPresent || highPresent.

Full changelog: CHANGELOG.md

formula-cpp 0.4.0

Choose a tag to compare

@christianparpart christianparpart released this 05 Oct 05:29
6ef3b19

formula-cpp 0.4.0

The fourth release of formula-cpp, a header-only C++23 library for traceable formulas. You write a formula once
in C++. Dimensions are checked at compile time, units are converted exactly, and evaluation runs in exact rational
arithmetic. The same formula renders as documentation and traces step by step into an audit trail.

This release widens exact numbers to 128 bits and makes every number the library writes name its unit:

  • Wider exact numbers. Rational stores a new formula::Int128, so realistic laboratory statistics that
    overflowed 64 bits now answer. The logarithm and exponential take every argument a Rational holds.
  • A unit on every number. Every value a trace, a rendered formula, number_text or std::format writes now says
    which unit its number is in.

Documentation, with the guides, a gallery and the API reference:
https://lastrada-software.github.io/formula-cpp/

Requirements

  • C++23, CMake 3.23 or newer, and GCC 14 or newer.
  • CI builds and tests every change on:
    • MSVC cl and clang-cl (Windows);
    • Clang and GCC 14 (Linux), including an UndefinedBehaviorSanitizer build;
    • Clang on macOS.

What is new

  • 128-bit exact numbers.

    • formula::Int128: a signed 128-bit integer with one API on every compiler. GCC and Clang use the
      compiler's own 128-bit integer, and cl and clang-cl use portable constexpr code. It converts to no
      built-in integer, implicitly or explicitly. to_int64() and to_uint64() say when a value does not fit, and
      std::format writes it in decimal.

    • Rational stores its numerator and denominator in Int128. These now answer where they overflowed:

      • the sample variance of masses read to 6 decimal places of a gram;
      • rejection by standard deviations at that resolution;
      • a cylinder's strength at every diameter measured.

      The numeric headroom page measures how much room each fixture leaves.

  • Logarithms and exponentials take every argument a Rational holds.

    • The kernels behind rounded_ln, rounded_log10 and rounded_exp used to refuse arguments wider than 64 bits.
      ln 2^70 is now 48.5203 at 4 places.
    • rounded_exp answers up to 88.7, beyond which no result fits a Rational: e^45 to 18 places, e^88 to whole
      units.
    • The exponential computes with 192 fraction bits, so a result as wide as a Rational is still decided.
  • A unit on every value a trace writes.

    • Borrowed units. A computed value reads in its operand's unit where that is safe: the outlier-rejection limit
      #2 * #3 = 1239/500000 is now 1239/500 g. Otherwise it reads in the coherent unit, spelt from its base units:
      427/125000000 kg^2, 60000000 kg/(m s^2).
    • Inverse units. A unit with nothing above the slash is written with negative exponents, so 20000/413 kg^-1
      cannot be read as a fraction divided again.
    • Precision limits. A precision limit's first pass reads in its level's unit, as its second pass already
      did.
  • Units with no symbol. A dimensioned unit with no symbol is named by its size wherever a number refers to it:

    • a rounding reads round(#1, to 2 dp of 1/1000 kg), where it read round(#1, to 2 dp);
    • numeric(x, in 1/1000 kg) names the unit the same way;
    • render(), number_text and std::format write a value declared in such a unit in the coherent unit, as a
      trace does: 3/1000 kg, not a bare 3. This covers constants, table rows and bands, and limits too.

    In LaTeX the size is grouped, \operatorname{round}_{2\,(1/1000\,\mathrm{kg})}, and powers are raised:
    \mathrm{kg}^{-1}.

  • Diagnostics. explain and checked_explain given a series refuse it in the library's words, and point at
    explain_series instead of failing with "no matching function".

Upgrading from 0.3.0

A minor release may break the public API before 1.0, and this one does. The CHANGELOG lists every change under
Changed. These are the ones most likely to need an edit:

  • A dimensionless unit with a scale or an offset and no symbol no longer compiles. That covers a quantity's
    unit, a constant's, a rounding's, and a table's key or result. Such a unit printed one half in hundredths as a bare
    50. Give the unit a symbol (%, ppm, or your own), or declare the quantity in scale 1. The refusal reads
    formula: a dimensionless unit with a scale must have a symbol.
  • Rational::Int is Int128, and a Rational is 32 bytes.
    • Code that stored numerator() or denominator() in a built-in integer must narrow with to_int64(), which
      returns std::optional.
    • Rational's converting constructor takes every built-in integer of at most 64 bits except bool, and refuses
      a wider one.
    • band(Rational, Rational) and breakpoint(Rational) refuse a value that does not fit their 64-bit fields.
  • Trace texts changed. Text that a test pins changes wherever a trace showed a dimensioned value bare, an
    inverse unit as 1/kg, or a rounding in a unit with no symbol.
    • Step::unit now holds the unit a computed step borrowed.
    • Step gains lookupKeyHigh: a sample-size lookup's count can exceed 2^64 - 1.
  • Number text in a unit with no symbol. number_text and std::format of a Measured value in a dimensioned
    unit with no symbol write it in the coherent unit, 3/1000 kg or 0.003 kg, at that unit's places, where they
    wrote a bare 3.

formula-cpp 0.3.0

Choose a tag to compare

@christianparpart christianparpart released this 30 Sep 23:07
7d720b5

formula-cpp 0.3.0

The third release of formula-cpp, a header-only C++23 library for traceable formulas: written
once in C++, with dimensions checked at compile time and units converted exactly, evaluated in exact
rational arithmetic, rendered as documentation, and traced step by step into an audit trail.

This release gives a shorter spelling to everything the examples repeated, and takes no spelling
away: exact decimal literals, plain numbers in series, one call that reads a result's number or
returns a trace, std::format for results, a rounding named once, and a formula bound to its result
quantity. Every example is rewritten with them.

Documentation, with the guides, a gallery and the API reference:
https://lastrada-software.github.io/formula-cpp/

Requirements

  • C++23, CMake 3.23 or newer, and GCC 14 or newer.
  • CI builds and tests every change on MSVC cl and clang-cl (Windows), Clang and GCC 14
    (Linux, with an UndefinedBehaviorSanitizer build) and Clang on macOS.

What is new

  • Literals and inputs: 27.3_r is the exact Rational 273/10, where 27.3 is the double
    nearest it. A series takes plain numbers and not_measured beside measured values:
    measured_series<Determination>(41.3_r, 43.9_r, formula::not_measured, 45.7_r).
    breakpoint(12.7_r) and band(83.7_r, 97.3_r) take exact numbers too.
  • Reading results: number_of(x) is the number a result holds, or nothing, as a
    std::optional<Rational>, for a Measured, an Outcome, a checked_evaluate result and the
    other result types: number_of(checked_evaluate<Q>(...)) == 0.5_r. A Measured also has throwing
    twins, convert_to<R>, round_to_declared and within_bounds, for callers who would only
    rethrow.
  • Formatting: with <formula-cpp/format.hpp>, std::format("{}", outcome) writes an Outcome,
    a Unit, a Dimension and every enumeration that has a describe(). describe now covers
    ConstraintOutcomeKind, RetryEnd, ValueSource, OutcomeKind and FailureSite.
    symbol_of<Q>() needs no vocabulary, and render(x, options) no placeholder one:
    std::println("{} = {} ({})", formula::symbol_of<WaterCementRatio>(), *result, result->source()).
  • Traces: traced(evaluation) runs any evaluation that takes a sink and returns the outcome with
    its trace. explain_method<Tag>, explain_check_method, explain_check, explain_check_all,
    explain_conformity, explain_curve and explain_rejection are the traced twins of their verbs, and
    trace_of<Q>(expression, env) is the trace of an evaluation in one call, whether it succeeds or
    fails: render_trace(trace_of(mixCost, inputs), { .maxSteps = 20 }).
  • A rounding named once: DecimalRounding and SignificantRounding hold a unit, the places or
    digits, and a mode, where the three were repeated at every use.
    declared_rounding(unit, mode) takes the places the unit declares:
    constexpr formula::DecimalRounding wholeCents = formula::declared_rounding(EuroCent, formula::RoundingMode::HalfAwayFromZero);
    once, then rounded<wholeCents>(var<Subtotal> + var<Vat>).
  • Bound formulas: yields<Q>(expression) names a formula's result quantity once, where the
    formula is written: constexpr auto circularArea = yields<Area>(pi * pow<2>(var<Diameter>) / 4);
    then evaluate(circularArea, environment). Q is still checked against the dimension the
    expression computes, never deduced. Misusing a bound formula draws one formula: message, not an
    overload list. That covers a bound series or rejection handed to a verb that answers with one value,
    and a bound formula used as an operand or compared.
  • The examples themselves: 18 example programs, 4705 → 4402 lines; uses of a private rat()
    helper 155 → 0; printf 361 → 0 (now std::println); measurement().value() chains 60 → 0;
    RecordingSink in hand-built traces 23 → 0.

Upgrading from 0.2.0

A minor release may break the public API before 1.0, and this one does in a few places. Each change
is listed under Changed in the CHANGELOG. The ones most likely to need an edit:

  • Argument-dependent lookup. An unqualified call with this library's types as arguments now also
    finds the functions this release adds, among them describe, number_of, convert_to,
    round_to_declared, within_bounds, traced, trace_of, the explain_* twins, yields and
    declared_rounding. A consumer's function template of the same name and shape, such as a
    template <typename R, typename Q> Measured<R> convert_to(Measured<Q>) helper, is displaced
    silently: the library's is more constrained and is chosen, so the helper no longer runs, and
    where it returned an absent value on failure the library's convert_to throws
    ArithmeticException. A function of another shape, such as a non-template
    describe(ConstraintOutcomeKind) or a template taking its argument by value, makes the call
    ambiguous. A non-template taking exactly a library template's parameter types is preferred and
    keeps working. Rename the helper, remove it, or call it by a qualified name such as ::convert_to.
    And since _r is in an inline namespace of formula, using namespace formula; brings it into
    scope, where a consumer's own _r is ambiguous with it.
  • checked_convert_to across dimensions is now a compile error. Converting, say, a volume into
    a mass, or euros into yen, used to compile and return ArithmeticError::DomainError at run time.
    The message names the two quantities. A conversion within one dimension is unchanged.
  • A floating-point breakpoint or band argument is refused. breakpoint(1.5) used to be
    truncated silently to breakpoint(1). It now fails to compile, with a message that names the exact
    spelling for that call, such as 12.7_r, Rational { 127, 10 } or band(12.7_r, 17.3_r).
  • std::formatter specialisations may collide with yours. <formula-cpp/format.hpp> now
    specialises it for Outcome<Q>, Unit, Dimension and every enumeration that has a
    describe(). A program that defines its own for one of these now defines it twice, and a generic
    formatter for every enumeration is ambiguous for them; remove it and use the library's.
  • GCC 14 is required. Older GCC is not supported.
  • The with_rounding refusal text changed. With no citation it now opens
    formula: with_rounding<...>() was given no citation, for either spelling.

Using it

Install it with CMake, then consume it with find_package:

cmake -S . -B build -DFORMULA_INSTALL=ON -DCMAKE_INSTALL_PREFIX=/your/prefix
cmake --install build
find_package(formula-cpp 0.3 CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE formula-cpp::formula-cpp)

The package's compatibility is SameMinorVersion, so a request for 0.3 is not satisfied by 0.2 or
by 0.4. Because the library is header-only, copying include/ into your tree also works.

Known limits

  • No stable API before 1.0. A minor release may change the public API. Every change is recorded
    in the CHANGELOG.
  • 64-bit rational headroom (#1):
    the exact route of some realistic laboratory statistics still overflows 64 bits. It reports an
    overflow; it never gives a wrong number. The least-squares fits answer at a declared precision
    where their exact route overflows, within the width of their wider integers. The guide
    "Numeric headroom" measures where each computation stops.
  • Units of single-value arithmetic in a trace
    (#2): a computed single value, such
    as a product or a quotient, is still shown in its coherent SI value with no unit symbol.

The full list of changes is in CHANGELOG.md.

formula-cpp 0.2.0

Choose a tag to compare

@christianparpart christianparpart released this 30 Sep 13:40
eb5eed8

formula-cpp 0.2.0

The second release of formula-cpp, a header-only C++23 library for traceable formulas: written
once in C++, with dimensions checked at compile time and units converted exactly, evaluated in exact
rational arithmetic, rendered as documentation, and traced step by step into an audit trail.

This release adds calculations whose values depend on each other, worksheets that recalculate only
what a change reaches, and decimals in traces and std::format. It also adds the first formulas
whose true values the exact layer cannot hold: logarithms, exponentials, and least-squares fits over
raw observations. Each such value is written only as the correctly rounded decimal its formula
declares, proved with integer arithmetic, so a trace still never shows a number that is not exact.

Documentation, with the guides, a gallery and the API reference:
https://lastrada-software.github.io/formula-cpp/

Requirements

  • C++23 and CMake 3.23 or newer.
  • CI builds and tests every change on MSVC cl and clang-cl (Windows), Clang and GCC 14
    (Linux, with an UndefinedBehaviorSanitizer build) and Clang on macOS.
  • Minimum compiler versions are not settled yet.

What is new

  • Calculations and worksheets: define<Q>(expression) binds a quantity to its formula, and a
    calculation's dependency graph is known at compile time. A mistake such as a cycle or a dimension
    mismatch is one compile error. A worksheet computes each value only when asked, and recalculates
    only what a change reaches. A derivation is re-run against the worksheet's current values, so it
    is never stale.
  • Decimals and std::format: traces, rendered formulas and derivations can show exact decimals,
    or approximations that name their rounding mode and always carry ≈. An opt-in std::formatter
    handles Rational and measured values; a bad format spec in a literal is a compile error.
  • Base dimensions an application declares: base_dimension("EUR") declares a dimension the SI
    does not have, so adding euros to yen, or to a bare ratio, fails to compile. No currencies are
    shipped, and an exchange rate is data.
  • Units: watt, kilowatt, watt-hour and kilowatt-hour, with dim::Power, and Fahrenheit.
  • Values the exact layer cannot hold: rounded_output<"slope", Unit, Places, Mode>(fit) reports
    an opaque operation's output at the precision the method declares. The rounded decimal is the
    step's exact value, and its line says it was rounded. A rounding that cannot be decided is
    Overflow, never a guess. The least-squares fits compute in wider integers, so, within those
    integers' width, they answer this way where their exact route overflows 64 bits.
  • Logarithms and exponentials: ln, log10 and exp take a dimensionless argument, checked at
    compile time. They are exact where the value is rational (ln 1, exp 0, log10 of a power of ten),
    and otherwise Inexact. rounded_ln, rounded_log10 and rounded_exp give the correctly
    rounded decimal at declared places, from an integer kernel.
  • A line through raw observations: linear_least_squares over two sets of observations, whose
    number is data, gives the intercept, the slope, R² and the number of points. A fit of values that
    are all equal is its own domain error.
  • Multiple regression: multiple_least_squares(regressors(x1, ..., xK), y, citation), for one to
    eight regressors, gives a constant, one coefficient per regressor in its own unit, R² and the
    number of points. The exact route refuses a singular design exactly, by a zero pivot, not by a
    tolerance.

Upgrading from 0.1.0

A minor release may break the public API before 1.0, and this one does in a few places. Each change
is listed under Changed in the CHANGELOG. The ones most likely to need an edit:

  • New enumerators and members. StepKind gains seven enumerators, appended. Dimension,
    OpaqueCallInfo, OpaqueStepData and OpaqueCallFailure gain members, so a structured binding
    over one needs more names. A switch over StepKind that names every enumerator and has no
    default now misses seven.
  • Argument-dependent lookup. An unqualified call of ln, log10, exp, fraction_text,
    number_text and a few others now also finds the library's function. A consumer's own function of
    the same name for the same arguments makes such a call ambiguous.
  • Includes. statistics.hpp no longer brings in binning.hpp, band.hpp or lookup.hpp;
    include them where they are used.

Using it

Install it with CMake, then consume it with find_package:

cmake -S . -B build -DFORMULA_INSTALL=ON -DCMAKE_INSTALL_PREFIX=/your/prefix
cmake --install build
find_package(formula-cpp 0.2 CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE formula-cpp::formula-cpp)

The package's compatibility is SameMinorVersion, so a request for 0.2 is not satisfied by 0.1 or
by 0.3. Because the library is header-only, copying include/ into your tree also works.

Known limits

  • No stable API before 1.0. A minor release may change the public API. Every change is recorded
    in the CHANGELOG.
  • 64-bit rational headroom (#1):
    the exact route of some realistic laboratory statistics still overflows 64 bits. It reports an
    overflow; it never gives a wrong number. The least-squares fits now answer at a declared
    precision where their exact route overflows, within the width of their wider integers. The guide
    "Numeric headroom" measures where each computation stops.
  • Units of single-value arithmetic in a trace
    (#2): a computed single value, such
    as a product or a quotient, is still shown in its coherent SI value with no unit symbol.

The full list of changes is in CHANGELOG.md.

formula-cpp 0.1.0

Choose a tag to compare

@christianparpart christianparpart released this 29 Sep 04:41

formula-cpp 0.1.0

The first release of formula-cpp, a header-only C++23 library for traceable formulas. You write
a formula once, in C++. The library checks its dimensions at compile time, converts units exactly,
and evaluates it in exact rational arithmetic, so a number is never silently rounded. It renders
the same formula as documentation. It also records every step of an evaluation, so the result
comes with an audit trail that states each number with its unit and where it came from.

The library is licensed under Apache-2.0.

Documentation, with the guides, a gallery and the API reference:
https://lastrada-software.github.io/formula-cpp/

Requirements

  • C++23 and CMake 3.23 or newer.
  • CI builds and tests every change on MSVC cl and clang-cl (Windows), Clang and GCC 14
    (Linux, with an UndefinedBehaviorSanitizer build) and Clang on macOS.
  • Minimum compiler versions are not settled yet.

What is in it

  • Exact numbers and rounding: a checked rational type that reports overflow instead of
    wrapping, with rounding to decimal places, significant digits or multiples under named modes.
  • Dimensions and units: dimensions with rational exponents, checked at compile time, and units
    with exact conversion, including affine ones such as degrees Celsius.
  • Quantities: declared once with a symbol, a description and a unit, either as an alias or as a
    struct. Measurements may be absent without that being an error.
  • Expressions and evaluation: arithmetic, powers and roots over environments of measured or
    hand-entered values. Evaluation yields an outcome that is a value, a verdict, an invalid result
    or nothing, never a bare number that hides which.
  • Citations and documentation: documented() citations on any part of a formula. Formulas
    render as plain text, Markdown or LaTeX, and the library generates documentation with a symbol
    table.
  • Tracing and audit reports: composable sinks, a trace of every step, and a bounded rendering
    of it that a person can check line by line.
  • Rounding and conditionals: rounding as a node of a formula, when() conditionals, and a
    justified escape hatch from units.
  • Constraints: constraints as peers of formulas. Their verdicts are recorded in the trace, and
    a set of constraints is checked without short-circuiting.
  • Lookup tables: exact, banded and interpolating lookups over tables validated at compile time.
    A value outside the table gives no number at all.
  • Methods and jurisdiction overlays: a method holds variants chosen by tag, together with
    rounding rules and constraints. A jurisdiction's overlay can fix constants, derive quantities,
    replace formulas, pin or prune variants, and use its own vocabulary of symbols. Every change is
    cited in the trace.
  • Series and grading curves: a quantity at every point of a method's domain, elementwise
    arithmetic, running totals, sums, conformity against a limit envelope, snapping, curves,
    interpolation, splicing, and binning of raw observations.
  • Statistics and outlier rejection: mean, variance and range of a sample, precision limits,
    critical-value tables read at the sample size, and iterative outlier rejection with its own
    abort rules.
  • Cross-record context and lineage: a formula can read from other samples and tests, with each
    value's origin stated in the trace, and can check that records share a lineage.
  • Opaque operations and least squares: named computations traced by their inputs and outputs,
    with the inside marked as not shown. The first of them is a least-squares line.
  • Bounded retry: a step repeated until it is accepted, at most a fixed number of times. It ends
    in exactly one of six stated ways, and ends in the method's verdict when the attempts run out.

Using it

Install it with CMake, then consume it with find_package:

cmake -S . -B build -DFORMULA_INSTALL=ON -DCMAKE_INSTALL_PREFIX=/your/prefix
cmake --install build
find_package(formula-cpp 0.1 CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE formula-cpp::formula-cpp)

The installed package exports the header-only INTERFACE target formula-cpp::formula-cpp and a
version file. Its compatibility is SameMinorVersion, so a request for 0.1 is not satisfied by
0.2. Because the library is header-only, copying include/ into your tree also works.

Known limits

  • No stable API before 1.0. A minor release may change the public API. Every change is
    recorded in the CHANGELOG.
  • 64-bit rational headroom (#1):
    some realistic laboratory statistics exceed the 64-bit numerator and denominator. Such a
    computation reports an overflow; it never gives a wrong number. The guide "Numeric headroom"
    describes the limits in detail.
  • Units of single-value arithmetic in a trace
    (#2): a computed single value,
    such as a product or a quotient, is shown in its coherent SI value with no unit symbol. The
    number is right, but the reader has to know which unit it is in.

The full list of changes is in CHANGELOG.md.