Skip to content

formula-cpp 0.2.0

Choose a tag to compare

@christianparpart christianparpart released this 30 Sep 13:40
· 241 commits to master since this release
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.