Repository navigation
formula-cpp 0.4.0
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.
Rationalstores a newformula::Int128, so realistic laboratory statistics that
overflowed 64 bits now answer. The logarithm and exponential take every argument aRationalholds. - A unit on every number. Every value a trace, a rendered formula,
number_textorstd::formatwrites 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
clandclang-cl(Windows); - Clang and GCC 14 (Linux), including an UndefinedBehaviorSanitizer build;
- Clang on macOS.
- MSVC
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, andclandclang-cluse portableconstexprcode. It converts to no
built-in integer, implicitly or explicitly.to_int64()andto_uint64()say when a value does not fit, and
std::formatwrites it in decimal. -
Rationalstores its numerator and denominator inInt128. 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
Rationalholds.- The kernels behind
rounded_ln,rounded_log10androunded_expused to refuse arguments wider than 64 bits.
ln 2^70is now 48.5203 at 4 places. rounded_expanswers up to 88.7, beyond which no result fits aRational: e^45 to 18 places, e^88 to whole
units.- The exponential computes with 192 fraction bits, so a result as wide as a
Rationalis still decided.
- The kernels behind
-
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/500000is now1239/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.
- Borrowed units. A computed value reads in its operand's unit where that is safe: the outlier-rejection limit
-
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 readround(#1, to 2 dp); numeric(x, in 1/1000 kg)names the unit the same way;render(),number_textandstd::formatwrite a value declared in such a unit in the coherent unit, as a
trace does:3/1000 kg, not a bare3. 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}. - a rounding reads
-
Diagnostics.
explainandchecked_explaingiven a series refuse it in the library's words, and point at
explain_seriesinstead 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::IntisInt128, and aRationalis 32 bytes.- Code that stored
numerator()ordenominator()in a built-in integer must narrow withto_int64(), which
returnsstd::optional. Rational's converting constructor takes every built-in integer of at most 64 bits exceptbool, and refuses
a wider one.band(Rational, Rational)andbreakpoint(Rational)refuse a value that does not fit their 64-bit fields.
- Code that stored
- Trace texts changed. Text that a test pins changes wherever a trace showed a dimensioned value bare, an
inverse unit as1/kg, or a rounding in a unit with no symbol.Step::unitnow holds the unit a computed step borrowed.StepgainslookupKeyHigh: a sample-size lookup's count can exceed 2^64 - 1.
- Number text in a unit with no symbol.
number_textandstd::formatof aMeasuredvalue in a dimensioned
unit with no symbol write it in the coherent unit,3/1000 kgor0.003 kg, at that unit's places, where they
wrote a bare3.