Summary
A trace step computed from single values by arithmetic (*, /, +, -) prints its value in the coherent SI unit with no unit symbol, even when an operand is shown in a named unit such as grams. The number is correct, but it reads as dimensionless and on a different scale from the lines around it, so a reader cannot check it by eye.
Example
From the outlier-rejection example in docs/statistics.md (the same trace is pinned in test/rejection_tests.cpp and docs/gallery.md):
2. 3/50
3. pass mean = 413/10 g
4. #2 * #3 = 1239/500000
...
6. rejected element 4 of 6 (44 g) in pass 1: abs(x - mean) = 27/10 g > 1239/500 g (deviation from mean)
Line 4 is 3/50 × 41.3 g = 2.478 g. It is printed as 1239/500000, which is the value in kilograms, but with no unit. Two lines later the same value appears as 1239/500 g. Nothing on line 4 tells the reader it is a mass, or that it is in kg rather than g.
Current behaviour
- Series steps already carry a readable unit. A series sum, range or running total, and a series scaled by a pure number, borrow their operand's unit through
detail::operand_unit_or, guarded by detail::borrowable in include/formula-cpp/trace.hpp.
- Single-value binary steps do not. They keep the coherent unit, whose symbol is empty, so the value prints bare.
detail::borrowable already encodes the safety rules any fix must keep:
- never borrow a unit with an offset (°C), because a product, sum or difference of such readings is not a point on that scale and would be off by the offset;
- never borrow a unit without a symbol.
Proposed behaviour
Show a single-value arithmetic step in a named unit when that is unambiguous and safe:
- Scaling by a pure number (
x * k, k * x, x / k, where k is dimensionless): show the result in the other operand's unit, if borrowable. Line 4 above would read #2 * #3 = 1239/500 g.
- Sum or difference of like quantities (
x + y, x - y, both operands in the same borrowable unit): show the result in that unit. Offset units stay excluded. A difference of two °C readings is a temperature interval, not a reading.
- Otherwise (products or quotients of dimensioned quantities, mixed units, offset units): keep the coherent unit, and consider printing its derived symbol (e.g.
kg, m/s) instead of nothing, so no computed value prints without a unit.
The rule should be read off the operand steps, as operand_unit_or does, never off types, so that what an operand step shows is what carries over.
Scope and cost
- Mostly in
RecordingSink's binary-node recording in trace.hpp, reusing borrowable.
- Expect many pinned expectations to change. The bare coherent form appears in the gallery (
docs/gallery.md), the statistics guide (docs/statistics.md), test/rejection_tests.cpp, and numerous other trace and render tests and guides. The gallery must be regenerated (gallery.is-current), and every docs quote must still match its example's output.
Step's layout need not change: the step already has a unit field.
Acceptance criteria
- The example above prints
#2 * #3 = 1239/500 g.
- A sum of two gram values prints in grams; a difference of two °C readings does not print in °C.
- A product of two dimensioned quantities (e.g. mm × mm) prints either in the coherent unit with its symbol, or in a documented borrowed form, never as a bare number.
- No step ever prints a number in a unit different from the one it is shown with; add a test that re-derives each printed value from the step's SI value and shown unit.
- Tests pin each case; the gallery and docs are regenerated and pass their checks on all supported compilers.
Summary
A trace step computed from single values by arithmetic (
*,/,+,-) prints its value in the coherent SI unit with no unit symbol, even when an operand is shown in a named unit such as grams. The number is correct, but it reads as dimensionless and on a different scale from the lines around it, so a reader cannot check it by eye.Example
From the outlier-rejection example in
docs/statistics.md(the same trace is pinned intest/rejection_tests.cppanddocs/gallery.md):Line 4 is 3/50 × 41.3 g = 2.478 g. It is printed as
1239/500000, which is the value in kilograms, but with no unit. Two lines later the same value appears as1239/500 g. Nothing on line 4 tells the reader it is a mass, or that it is in kg rather than g.Current behaviour
detail::operand_unit_or, guarded bydetail::borrowableininclude/formula-cpp/trace.hpp.detail::borrowablealready encodes the safety rules any fix must keep:Proposed behaviour
Show a single-value arithmetic step in a named unit when that is unambiguous and safe:
x * k,k * x,x / k, wherekis dimensionless): show the result in the other operand's unit, ifborrowable. Line 4 above would read#2 * #3 = 1239/500 g.x + y,x - y, both operands in the same borrowable unit): show the result in that unit. Offset units stay excluded. A difference of two °C readings is a temperature interval, not a reading.kg,m/s) instead of nothing, so no computed value prints without a unit.The rule should be read off the operand steps, as
operand_unit_ordoes, never off types, so that what an operand step shows is what carries over.Scope and cost
RecordingSink's binary-node recording intrace.hpp, reusingborrowable.docs/gallery.md), the statistics guide (docs/statistics.md),test/rejection_tests.cpp, and numerous other trace and render tests and guides. The gallery must be regenerated (gallery.is-current), and every docs quote must still match its example's output.Step's layout need not change: the step already has aunitfield.Acceptance criteria
#2 * #3 = 1239/500 g.