Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
641f5e2
docs(plan): power, energy and Fahrenheit units
Sep 29, 2026
bd483a8
feat(unit): watt, kilowatt, watt-hour, kilowatt-hour and degrees Fahr…
Sep 29, 2026
708a5e0
docs(unit): say only what the affine unit comments can back
Sep 29, 2026
06987d7
docs(dimensions): power, energy and Fahrenheit units
Sep 29, 2026
1341078
docs: write decimals with a point in English prose
Sep 29, 2026
2bfc8e9
docs: name the feature, not the stage it was built in
Sep 29, 2026
c849e96
docs(unit): say what the affine unit comment and the example claim
Sep 29, 2026
281395d
test: write decimals with a point in comments
Sep 29, 2026
b427568
docs(plan): base dimensions an application declares, such as money
Sep 29, 2026
2ef802c
refactor(dimension): the fixed-size symbol type moves next to Dimension
Sep 29, 2026
cff69ba
feat(dimension): base dimensions an application declares, such as money
Sep 29, 2026
75b15c6
test(dimension): pin power, nth_root and the quotient guard
Sep 29, 2026
f77dca1
feat(trace): named base dimensions in coherent units
Sep 29, 2026
51da020
docs(trace): say when a quotient of named bases does not fit
Sep 29, 2026
b26c9f0
docs(trace): say exactly which named bases a unit quotient counts
Sep 29, 2026
4aee8a2
test(unit): money as a dimension, end to end
Sep 29, 2026
7034ca1
test(unit): make the tariff's identity across translation units catch…
Sep 29, 2026
6accc3f
docs(examples): money as its own dimension
Sep 29, 2026
e6c8334
docs(trace): say when an exponent overflow reaches unit_quotient's guard
Sep 29, 2026
84e6d70
docs(dimensions): base dimensions the SI does not have
Sep 29, 2026
a42a2e0
docs: say "coherent unit" where a dimension may carry a named base
Sep 29, 2026
dc743aa
docs: say named bases come in the next release, and correct five word…
Sep 29, 2026
2681293
docs(plan): calculations and worksheets
Sep 29, 2026
facedb1
refactor(type_list): quantity lists shared beyond overlays
Sep 29, 2026
cb65846
feat(evaluate): an environment can report a read's source and failure…
Sep 29, 2026
a5a8613
test(evaluate): pin the source hooks' constraints, and say what they …
Sep 29, 2026
523edf8
feat(calculation): define<Q>(expression), and the quantities it reads…
Sep 29, 2026
195aada
test(calculation): pin the walk's refusal gates, and say what the reg…
Sep 29, 2026
c23c87c
feat(calculation): a dependency graph checked at compile time, and qu…
Sep 29, 2026
84c59d2
feat(calculation): worksheets that recalculate only what a change rea…
Sep 29, 2026
8488c40
fix(calculation): refuse is_overridden of an input, and pin what the …
Sep 29, 2026
ee1a37f
feat(trace): a worksheet's derivation, one block per named value
Sep 29, 2026
be9c187
feat(render): describe a calculation's graph, as text and as Graphviz…
Sep 29, 2026
3b768aa
docs(trace): say what a derivation's overlay step reports, and what t…
Sep 29, 2026
7e81908
test(render): pin the order of a line's reads, and a vocabulary in a …
Sep 29, 2026
02dfca7
feat(document): document a calculation, each calculated value with it…
Sep 29, 2026
6bccb32
docs(render): state the order of a value's arrows, and say plainly wh…
Sep 29, 2026
c0d5e6e
docs(calculation): say coherent unit where a dimension may carry a na…
Sep 29, 2026
e89b469
test(document): pin a citation met twice and a derived row beside its…
Sep 29, 2026
1556c8a
docs(plan): decimal display and std::format
Sep 29, 2026
a2c63ce
feat(number_text): exact decimals, rounded decimals and fractions, sp…
Sep 29, 2026
e7179fb
test(number_text): pin the overflow-free long division and both bound…
Sep 29, 2026
263a984
refactor(render): spell fractions through number_text
Sep 29, 2026
33bda30
refactor(trace_render): every number a trace line states goes through…
Sep 29, 2026
fe4d0e7
feat(trace_render): decimals in traces, exact unless an approximation…
Sep 29, 2026
93bc2b6
fix(trace_render): a typed value is passed on only by the step that h…
Sep 29, 2026
18bd6b2
fix(trace_render): a precision limit's second pass passes its limit's…
Sep 29, 2026
e7bcc69
feat(format): std::format for Rational and Measured
Sep 29, 2026
a21cca6
feat(render): decimal constants in rendered formulas and documentation
Sep 29, 2026
9e0843f
test(document): divide the derivation fixture by a plainly invented d…
Sep 29, 2026
be03cc0
fix(format): say when writing a value throws, and take only a scalar …
Sep 29, 2026
0c3fe18
test(render): a typed number stays exact and unpadded at every table …
Sep 29, 2026
3167346
docs(display): a guide to decimals, and a std::format reference
Sep 29, 2026
8a908c1
docs(format): only a value that must round can overflow; pin the fill…
Sep 29, 2026
9671184
feat(render): typed_number_style, the style a formula's typed number …
Sep 29, 2026
466895c
docs(display): every refusal mapped to its cause, and the typed-numbe…
Sep 29, 2026
6199535
test(docs): check the std::format table, README's output and a quoted…
Sep 29, 2026
51624a2
test(format): ~Mode refuses a unit declaring fewer than -18 decimals
Sep 29, 2026
e5c0197
docs(display): say coherent unit where a dimension may carry a named …
Sep 29, 2026
c5d2180
feat(trace_render): a derivation spells its numbers in the trace's style
Sep 29, 2026
be1749a
docs(display): close two gaps in the guide's checks, and say what to …
Sep 29, 2026
e4dfbb0
fix(trace_render): a typed value another block reads stays exact there
Sep 29, 2026
8886c57
fix(render): a rounding in a unit nobody declared shows its first sig…
Sep 29, 2026
9ea9409
test(docs): check the dimensions guide's output and code against its …
Sep 29, 2026
87f8d59
refactor(number_text): exact_decimal of a Rational is exact_decimal_text
Sep 29, 2026
5f5ac77
refactor(number_text): name the detail decimal helper apart from exac…
Sep 29, 2026
d4790cb
test(calculation): check the read each derivation step notes, over ev…
Sep 29, 2026
0c0e709
docs: say what checks the dimensions guide, and pin more of the first…
Sep 29, 2026
4d91c52
docs(calculations): an electricity bill as a worksheet, and a guide t…
Sep 29, 2026
106ae28
test(calculation): write 43200 s plainly, and say what the bill fixtu…
Sep 29, 2026
cc03b71
docs(calculations): keep the bill to its fifteen values, and show a f…
Sep 29, 2026
55d9f49
docs(calculations): the bill in decimals, and its output quoted and p…
Sep 29, 2026
301331e
docs: list power, energy and Fahrenheit units for the next release
Sep 29, 2026
eb332d8
docs(calculations): explain a reused value, and show what the guide o…
Sep 29, 2026
1ed9eb2
feat(calculation): clear_override on a worksheet about to be discarded
Sep 29, 2026
ab90f86
test(hygiene): reach the styled render and document, and money in a w…
Sep 29, 2026
d3c931a
test(calculation): money of its own through a worksheet and every tex…
Sep 29, 2026
19a7e2d
docs(calculations): convert the shared total, and show what was only …
Sep 29, 2026
7e36e62
docs(changelog): record the lookup change under Changed, and name eve…
Sep 29, 2026
1cabd1c
docs(display): link the calculations guide where worksheets are named
Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ target_sources(formula-cpp INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/detail/type_list.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/band.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/binning.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/calculation.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/citation.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/conditional.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/conformity.hpp"
Expand All @@ -64,13 +65,15 @@ target_sources(formula-cpp INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/escape.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/evaluate.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/expression.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/format.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/formula.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/function.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/least_squares.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/lineage.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/lookup.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/measured.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/method.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/number_text.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/opaque.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/outcome.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/formula-cpp/overlay.hpp"
Expand Down
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,28 @@ last two lines are the reason that matters: the same number rounds to 1.2 or
1.1 depending on the rule the method specifies, and the library makes you say
which.

### A decimal only where it is exact, a rounding only where you ask

```cpp
#include <formula-cpp/format.hpp>
```

```text
std::format("{}", Rational { 3, 5 }) 0.6
std::format("{}", Rational { 1, 3 }) 1/3
std::format("{:/}", Rational { 3, 5 }) 3/5
std::format("{:.2HalfEven}", Rational { 23653, 200 }) 118.26
std::format("{:.2HalfAwayFromZero}", Rational { 23653, 200 }) 118.27
std::format("{:.2HalfEven}", Rational { 4 }) 4.00
std::format("{:~.3HalfEven}", Rational { 1, 3 }) ≈0.333
std::format("{:~.3HalfEven}", Rational { 3, 5 }) 0.6
```

`1/3` has no decimal, so it stays `1/3`: `0.333` would be a different number.
A rounding names its mode — there is no default — and `~` marks it `≈`.
Traces and rendered formulas take the same choice; see
[Displaying numbers](docs/display.md).

### The library documents itself

`document()` walks a formula for its rendered text, its citations and its
Expand All @@ -174,7 +196,7 @@ std::string const trace = formula::render_trace(explained.trace, { .maxSteps = 1
4. #3 = 3/5 [Water/cement ratio, Example Standard 1:2020, 5.4.2, (3)]
```

Every value is shown in the unit it was declared in, not the coherent SI unit
Every value is shown in the unit it was declared in, not the coherent unit
the arithmetic actually ran on — that is `9/50` cubic metres above, and nobody
typed cubic metres. When the environment overrides the result instead of
letting the formula derive it, `explained.trace` comes back empty — nothing
Expand Down Expand Up @@ -232,11 +254,13 @@ because all of it came from the same line of code.
| Guide | What it covers |
|---|---|
| [Exact numbers](docs/numbers.md) | `Rational`, the rounding modes, why exactness is the default |
| [Dimensions and units](docs/dimensions.md) | Compile-time dimensional analysis, exact unit conversion |
| [Dimensions and units](docs/dimensions.md) | Compile-time dimensional analysis, exact unit conversion, and base dimensions the SI does not have, such as money |
| [Quantities](docs/quantities.md) | Declaring a quantity, `Describe`, measurements that may be absent |
| [Writing formulas](docs/expressions.md) | Operators, evaluation, environments, overrides |
| [Citations and rendering](docs/citations.md) | `documented()`, the three dialects, generated documentation |
| [Tracing and audit trails](docs/tracing.md) | `explain()`, `render_trace()`, sinks, and the zero-cost untraced path |
| [Displaying numbers](docs/display.md) | Decimals in traces and rendered formulas, exact unless an approximation is asked for, and std::format for Rational and Measured |
| [Calculations and worksheets](docs/calculations.md) | Named values defined by expressions, a dependency graph checked at compile time, a worksheet that recalculates only what a change reaches, what-if copies, overrides, and a derivation per named value |
| [Rounding and conditionals](docs/rounding-and-conditionals.md) | Rounding as a node, `when()`, and the traced `numeric_value_of` escape hatch |
| [Constraints and verdicts](docs/constraints.md) | Validating a result with `constraint()` and `check()`, the four-state outcome, and checking a set without short-circuit |
| [Lookup tables](docs/lookup-tables.md) | The three table kinds, validation that refuses a gap, and why a miss is not a number |
Expand Down Expand Up @@ -273,6 +297,10 @@ and still growing. The public API may change until 1.0.
| Statistics, precision limits, outlier rejection | shipped |
| Other samples and other tests: records, context, lineage | shipped |
| Opaque operations (least squares), bounded retry | shipped |
| Power, energy and Fahrenheit units | next release |
| Named base dimensions such as money | next release |
| Decimals in traces, rendered formulas and `std::format` | next release |
| Calculations: definitions, dependency graph, incremental worksheets | next release |

## Requirements

Expand Down
17 changes: 16 additions & 1 deletion cmake/CheckDocumentedDiagnostics.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@
# where a quote carries one, the header's line 219 must really be `<text>`.
# This is the strongest check here -- it verifies the number *means* what
# the quote says, not merely that something is there.
# 3. **Every other quoted line must be neither blank nor comment-only.** The
# 3. **A quoted MSVC `note: see usage of '<name>'` must name a line that
# calls `<name>`** -- the last `::` segment, followed by `(`, on a line
# that is not a comment. MSVC points that note at the use, so this is as
# exact as the `static_assert` rule: it pins a guide's quote of a refusing
# guard to the guard's call.
# 4. **Every other quoted line must be neither blank nor comment-only.** The
# `note: see reference to ...` cascade points at instantiation sites, and
# no invariant this script can state says which line one of those must be.
# But drift lands them on blank lines and in the middle of prose comments,
Expand Down Expand Up @@ -292,6 +297,16 @@ foreach(document ${documents})
list(APPEND problems
"${documentName}:${lineNumber} quotes a static-assertion error at ${headerName}:${quotedNumber}, but that line is not a static_assert -- it is '${headerLine}'")
endif()
elseif(line MATCHES "see usage of '([A-Za-z_][A-Za-z0-9_:]*)'")
# MSVC's `note: see usage of 'formula::detail::name'` points at the
# call of that function, so line N must call it: `name(`, in code
# -- a comment that names the call is not one.
string(REGEX REPLACE "^.*::" "" usedName "${CMAKE_MATCH_1}")
string(FIND "${headerLine}" "${usedName}(" usedAt)
if(usedAt EQUAL -1 OR headerLine MATCHES "^(///|//|\\*)")
list(APPEND problems
"${documentName}:${lineNumber} quotes a use of ${usedName} at ${headerName}:${quotedNumber}, but that line does not call it -- it is '${headerLine}'")
endif()
elseif(headerLine STREQUAL "")
list(APPEND problems
"${documentName}:${lineNumber} quotes ${headerName}:${quotedNumber}, which is a blank line")
Expand Down
57 changes: 56 additions & 1 deletion cmake/CheckGuideOutput.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,65 @@ if(blocks EQUAL 0 OR lines EQUAL 0)
"examines nothing is a check that lies.")
endif()

# With CALL_TABLES, a table row whose last two cells are code spans, the first
# of them a `std::format(` call, states what the example prints for that call:
# the output must hold a line that starts with the call and a space, and ends,
# after the column's padding, with the row's last cell. docs/display.md's spec
# table is such a table; its rows are prose around real output, and a row's
# Output cell could otherwise drift from the checked block beside it. A table
# row that names `std::format(` in any other form -- an Output cell that lost
# its backticks, an Example cell that is no longer a code span -- is refused
# rather than skipped, so that no row drops out of the check unseen.
set(tableRows 0)
if(CALL_TABLES)
set(rest "${guide}")
while(TRUE)
string(FIND "${rest}" "\n" newline)
if(newline EQUAL -1)
break()
endif()
math(EXPR next "${newline} + 1")
string(SUBSTRING "${rest}" ${next} -1 rest)
string(FIND "${rest}" "\n" rowEnd)
string(SUBSTRING "${rest}" 0 ${rowEnd} row)
if(NOT row MATCHES "\\| `(std::format\\([^`]*\\))` \\| `([^`]*)` \\|$")
string(FIND "${row}" "std::format(" namesFormat)
if(row MATCHES "^\\|" AND NOT namesFormat EQUAL -1)
string(APPEND offenders
"\n a table row names std::format( but not as a call and its output, each a code span: ${row}")
endif()
continue()
endif()
set(call "${CMAKE_MATCH_1}")
set(written "${CMAKE_MATCH_2}")
math(EXPR tableRows "${tableRows} + 1")
string(FIND "${haystack}" "\n${call} " callFound)
if(callFound EQUAL -1)
string(APPEND offenders "\n a table row whose call the example never prints: ${call}")
continue()
endif()
string(LENGTH "\n${call} " callLength)
math(EXPR printedStart "${callFound} + ${callLength}")
string(SUBSTRING "${haystack}" ${printedStart} -1 printed)
string(FIND "${printed}" "\n" printedEnd)
string(SUBSTRING "${printed}" 0 ${printedEnd} printed)
string(STRIP "${printed}" printed)
if(NOT printed STREQUAL written)
string(APPEND offenders "\n a table row states ${call} as ${written}, but the example prints ${printed}")
endif()
endwhile()
if(tableRows EQUAL 0)
message(FATAL_ERROR
"guide output check: CALL_TABLES is set, but ${GUIDE} has no table row whose last two cells are a "
"`std::format(` call and its output. A check that examines nothing is a check that lies.")
endif()
endif()

if(NOT offenders STREQUAL "")
message(FATAL_ERROR
"${GUIDE} quotes output the example does not print as quoted. Re-run the example and copy its output:"
"${offenders}")
endif()

message(STATUS "guide output check: ${blocks} blocks of ${lines} quoted lines, each printed by the example as quoted")
message(STATUS "guide output check: ${blocks} blocks of ${lines} quoted lines, and ${tableRows} table rows, "
"each printed by the example as quoted")
6 changes: 5 additions & 1 deletion cmake/CheckPublicHeaderIncludes.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
# recorded derivation into text -- separately optional from it.
# detail/latex_math.hpp qualifies as a part of render.hpp: it builds the
# `std::string` render.hpp puts inside a LaTeX `\mathrm{...}`, and render.hpp
# is its only includer, which check 2 keeps true.
# is its only includer, which check 2 keeps true. format.hpp qualifies too --
# it specialises std::formatter, which needs <format>, and writes its output
# with a plain loop, so that it includes nothing else from that list itself.
#
# Being named here does not, by itself, permit anything: check 2 below
# enforces that no other header may reach one of these, which is what stops
Expand All @@ -25,6 +27,7 @@ set(exemptHeaders
"${SOURCE_DIR}/include/formula-cpp/trace.hpp"
"${SOURCE_DIR}/include/formula-cpp/trace_render.hpp"
"${SOURCE_DIR}/include/formula-cpp/detail/latex_math.hpp"
"${SOURCE_DIR}/include/formula-cpp/format.hpp"
)

file(GLOB_RECURSE headers "${SOURCE_DIR}/include/*.hpp")
Expand Down Expand Up @@ -128,6 +131,7 @@ set(exemptAllowances
"trace.hpp=vector"
"trace_render.hpp=string"
"latex_math.hpp=string"
"format.hpp=format"
)

set(overreaches "")
Expand Down
3 changes: 2 additions & 1 deletion cmake/CheckVocabularyReach.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# through `render`, `document` and the trace under a crossed-over vocabulary,
# and `render.hpp` refuses a one-argument `render_node` of this library's at
# compile time. What this script refuses, outside a whole-line comment, in
# the four headers below:
# the five headers below:
#
# - `::symbol` as a whole name, which is how `Describe<Q>::symbol` (at any
# nesting of template arguments), `N::quantity::symbol` (a CRTP quantity's
Expand All @@ -31,6 +31,7 @@
# the guarantee.

set(surfaces
"${SOURCE_DIR}/include/formula-cpp/calculation.hpp"
"${SOURCE_DIR}/include/formula-cpp/render.hpp"
"${SOURCE_DIR}/include/formula-cpp/document.hpp"
"${SOURCE_DIR}/include/formula-cpp/trace.hpp"
Expand Down
Loading
Loading