Unnamed and inverse units in traces and rendered formulas, 128-bit logarithm arguments, and the series refusal for explain - #28
Merged
Conversation
added 30 commits
October 4, 2026 22:48
Covers #11 and #13 to #21: trace text (a scaled dimensionless unit must have a symbol, an unnamed unit named by its size in a rounding clause, negative exponents for an inverse-only unit, a precision limit's first pass borrowing its unit, the snap's on-a-value compare), 128-bit arguments for the logarithm and exponential kernel, pinned bit widths in two guides, the library's refusal for explain of a series, two documentation corrections, and self-describing comments in place of development labels. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Ten tasks: four on trace text, two on numerics, then a merge, the series refusal with two documentation corrections, the replacement of development labels, and the finish. Planning corrected the design in two places. The exponential computes with 192 fraction bits, because at 128 a result near 2^127 could never be rounded with certainty and every such answer would be Overflow. The least-squares rows of the headroom page come from the 256-bit exact fit, so the "256-bit" figure there was right, and the new width column is generated from that fit's own constant. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The kernel narrowed an argument's numerator and denominator to 64 bits and refused wider ones with Overflow, though a Rational holds 128. It now reads both as 128-bit magnitudes: the scaled quotient is a long division over 128-bit words, a logarithm's reduction reaches 2^126, and the exponential's reaches 2^127 under a cap of 88.7, past which no value fits a Rational. The exponential computes with 192 fraction bits, so that a result as wide as a Rational is still enclosed within a fraction of its last kept unit: e^45 at 18 places and e^88 whole now answer. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A number in such a unit is in a scale nothing names: a trace showed one half in hundredths as 50, and no spelling of the unit could say so, since the coherent dimensionless unit is written as nothing. Every class that holds a unit as a template argument now refuses one, beside the checks it already makes: a quantity's description and the three nodes that read a quantity, a constant, every rounding, every table's key and result, a numeric value, a conformity check, a rounding rule and its override. A dimensioned unit with no symbol is still accepted and shown in the coherent unit. This breaks code that declares such a unit: give it a symbol, or declare the quantity in scale 1. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The series' product of a term and the reduced argument is below 2^384, the kernel word's width, and is what limits the exponential to 192 fraction bits; the comments now say so. Also pin the exponential of a negative argument with a 127-bit denominator, which rounds to 1, or one unit below it under Floor. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Reflow the exponential bullet's paragraph, name log10's widest product as log10's own, and wrap two rows of the reference table within the column limit. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A measurement names its quantity's description through RequireDescribed alone, with no variable node in sight, so that check is pinned by a case of its own. RequireDescribed's comments now name the three refusals it makes, the changelog entry leads with the break, and the new test files end in a newline. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A coherent unit with nothing above the slash was written 1/kg, so after a fraction a line read 20000/413 1/kg: one fraction divided again, at a glance. It is now kg^-1, m^-1 s^-1, JPY^-1, kg^(-1/2). A unit with a numerator keeps its slash: m/s, EUR s^2/(m^2 kg). Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The overflow census is told the bits every wide integer of LinearLeastSquares::compute_exact used, and the headroom page's least-squares table gains a generated column with the widest at the sizes that answer: 68 of 256 bits on readings at 3 decimal places, 249 on a different denominator for every point. A test pins the widths of the exact outputs of the fifty-readings fit the opaque guide quotes, and the guide cites it. The fifty readings now live once, in examples/fifty_readings.hpp, which the example and the least-squares tests both include. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…se-unit note A named base leads the SI units on its side of the slash, or among the negated factors when nothing stands above it, as in EUR^-1 s^-1. The tracing guide's note on negative exponents becomes short sentences. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The test of a named base dimension's place, and the paragraph on it in the dimensions guide, still spoke of the denominator for EUR^-1 s^-1, which has none. They now say that the name leads the negated factors when nothing stands above the slash, and the guide's list of examples reads as a sentence. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The SPDX and NOLINT hygiene checks globbed only the examples' sources and build files, so a header beside the examples, such as fifty_readings.hpp, escaped both. They now scan examples/*.hpp as well. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A rounding to places of a unit with no symbol wrote no unit clause, while the value after it read in the coherent unit: round(#1, to 2 dp) = 3/1000 kg let "2 dp" read as places of a kilogram. The clause now names the unit by its size in the coherent unit, round(#1, to 2 dp of 1/1000 kg), and an offset unit by its size and its zero, to 1 dp of 1 K from 5463/20 K, in render() and in a trace alike. numeric(x, in <unit>) names such a unit the same way, and render() writes a constant in it in the coherent unit, 3/1000 kg, where it wrote a bare 3. The coherent unit's spelling moves into render.hpp so that both can write it. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
In LaTeX a rounding's size clause read as a mixed number,
\operatorname{round}_{2\,\mathrm{1/1000\ kg}} as 2 1/1000 kg, and a
coherent unit's powers came out as an escaped caret. The coherent
spelling now takes a notation: plain text and a trace keep kg^-1, and
LaTeX sets each symbol upright with its power raised,
2000\,\mathrm{kg}^{-1}. A size clause is grouped in LaTeX,
round_{2\,(1/1000\,\mathrm{kg})}, and a numeric value's quotient too.
A constant that cannot be moved into the coherent unit now reads only
"(not shown: ...)", with no unit after it, as in a trace.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…unding clause's helper `verbatim_text` now says LaTeX escapes each factor as `LatexUnitNotation` sets it, and an element-wise rounding's comment names the clause's helper, `rounding_unit_in`, and the LaTeX grouping of a unit named by its size. `rounding_call_text`'s parameter is `unitText`, since it receives a size clause as well as a symbol. A test pins the LaTeX of a significant-digits rounding in a unit with no symbol. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… unit A number a formula declares in a dimensioned unit with no symbol is now moved exactly into the coherent unit and followed by that unit's spelling, in render() as in a trace, so that no number is shown in a scale the text after it does not name. A move that fails writes `(not shown: ...)`, never a number in the wrong scale. A unit with a symbol, and a dimensionless unit with no symbol, which is at scale 1, are never converted, and render as before. The rule now covers a constant, a per-element constant's values (`values(3/1000 kg, 1/200 kg)`), the bands, rows and values of a banded, an exact and an interpolating lookup, a binning's classes, a snap's permitted values, a domain's points and an envelope's limits (`from 1/4 to 1/2 kg`). One helper states the rule, `shown_number`, and `render()` and the trace share it: the bound helper `shown_bound_text`, the unit's spelling `shown_unit_spelling` and the envelope row `shown_limit_row` move from the trace into render.hpp, with the author-text spelling as a parameter. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A table's row is one run of upright words in LaTeX, escaped as a whole,
so a coherent unit with a power, such as kg^-1, was set as escaped
characters. A coherent unit is now set outside the row's words, after a
thin space, as a constant's unit is: `\mathrm{1000\ to\ under\
2000}\,\mathrm{kg}^{-1}`. A row in a unit with a symbol keeps its unit
among the words, byte for byte as before.
An envelope's row has one spelling, `limit_row_text`, for render() and
for a trace, with the author-text spelling as a parameter. A derivation's
header moves its value into the coherent unit through `shown_number`, the
one rule every other declared number follows.
Tests pin the LaTeX of a table keyed in a per-gram unit with no symbol,
and render()'s text for a point, a permitted value and a limit that the
coherent unit cannot hold.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A row that holds a coherent unit is set as words, a thin space and the unit's own factors and powers, not as one atom. The separator's comment said each row is one atom; it now says a row may be several, none of which is a break point in math, so its conclusion stands. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Pass 1 restates the level's value but took its unit from the types alone: the limit's quantity, else the level expression's, else the coherent unit. A level constant in grams read 40 g on its own line and 1/25 kg on the pass-1 line. Pass 1 now borrows the unit of the step it restates, under the rule pass 2 and a conditional use; the unit the types give stays the fallback. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A snap decides it sat on a permitted value by comparing two raw pairs, where a lookup's rows compare values. That is exact: an exact hit records one row twice, and the permitted set is strictly ascending by value. A comment now says so. A missed curve lookup also spells its low bound only on the path that writes it. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The check said the first pass reads in degrees Celsius as the constant's own line does, but pinned only the first pass. It now pins both lines, as the gram check does. The snap comment no longer says a lookup's rows compare by value because they can be typed as different pairs: they too are strictly ascending, and comparing by value simply does not depend on how a row was typed. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The logarithm and exponential kernel takes 128-bit arguments, and the bit widths two guides quote are measured by the census and by a test. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…he library's words A series handed to explain or checked_explain failed with the compiler's "no matching function": each had an overload for a single-value expression and one for a bound formula, and a series is neither. Each now has a series overload that refuses it as evaluate refuses one, pointing at explain_series, the verb that gives a series' derivation. trace_of and the bound forms refuse a series as before. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…dd as built The README's tracing paragraph linked the tracing guide twice; it now links it once, at the end. The 128-bit design document said the checked add and subtract use the compiler's overflow builtins. They compute on the words' bit patterns with the portable routines on every compiler, and detect overflow from the signs; only the multiply uses the native checked builtin. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…uiltin The 128-bit design document said the checked multiply uses __builtin_mul_overflow wherever the compiler has __int128. The native path is taken on GCC and Clang outside MSVC's ABI only: clang-cl has __int128 but multiplies in software, as cl does. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… history Comments, test names and section banners referred to stages of the project's development and to one-off experiments by labels a reader cannot look up. Each now says what it relies on: a measurement keeps its compilers and versions or points at the test that pins it, a banner names its feature, and a reference that added nothing is gone. Two census test cases are renamed after what they cover. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Thirteen comments cited a "defect class" by number, a classification
recorded nowhere a reader can look up. Each now says what it relies on:
a missing `{}` initialiser points at `Corrections` (lookup.hpp), which
gives the measurement, and a comment that already stated the property
simply drops the label. A negative test that leaned on "the other defect
classes here" names the three files it means.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… not do Comments in the lookup header and the tests still referred to tasks, task reports and reviewers, and to work owed by "a later task" that has since been done: a lookup's step now records its key, the interval its table covers and why it failed, and the lookup comments say so. A reference to a report or a reviewer is dropped. Two claims were untrue. describe(ArithmeticError) is what std::format and the trace's lines write, not an evaluation result's invalid arm, and snapping does not call checked_round_to_multiple. The fixture names in the record statistics tests now name the files that define them. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The released 0.1.0 entries were grouped under "Phase N:" headings that named development stages. Each heading now names only the feature it groups; every entry and the release structure are unchanged. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
added 6 commits
October 5, 2026 02:44
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Only a dimensionless unit at scale 1 drops the unit clause; a dimensioned unit with no symbol is written in its coherent unit instead. Both cases test `unit::One` alone, so their names now say so. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Wraps the test lines over 125 columns that came in with the unnamed-unit display work, following each file's own continuation style, and sets the note on a dimensionless unit with no symbol in `spells_coherent_unit`'s comment apart as its own paragraph. The header keeps its line count. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
`number_text` and `std::format` of a `Measured` value whose unit has a dimension but no symbol wrote the number on that unit's scale with nothing after it: 3 of a unit of 1/1000 kg read `3`. They now move it exactly into the coherent unit and write that unit's spelling after it, `3/1000 kg` or `0.003 kg`, as `render()` and a trace already do. A unit with a symbol, and a dimensionless unit with no symbol, are written as before. The coherent unit's order and shape are now written once, by a constexpr walker in `number_text.hpp` that both `coherent_unit_spelling` and a `NumberText` use, along with the plain spelling of a power; `spells_coherent_unit`, `shown_unit_of` and `shown_number` move there with it, and `coherent()` moves from `evaluate.hpp` to `unit.hpp`. A move into the coherent unit that fails returns its error, and a spelling too long for a `NumberText` returns `Overflow`. The gallery writes its worked value through `number_text`, and names a dimensioned unit with no symbol by its size instead of calling it dimensionless. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
`number_text` and `std::format` wrote a `Measured` value they had moved into the coherent unit at that unit's default 3 places, so a decimal style padded it and an approximating one could round a small value to `≈0 kg`: 1/3 of a unit of 1/1000 kg read `≈0 kg`. They now spell it through the trace's own `checked_shown_text`, which never pads those places and extends them to the value's first significant digit: `≈0.0003 kg`, as its trace line reads. `~.N` and `.N` still round at the places they name, and a value in any other unit keeps the decimals its unit declares. `checked_shown_text` and its helpers move from `render.hpp` to `number_text.hpp`, so the three surfaces share one definition. The gallery says why its unit cell uses the library's one spelling of a unit by its size, which has no public form. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A `Measured` value in a dimensioned unit with no symbol is shown in the coherent unit, at that unit's places, so the refusal for declared decimals outside -18 to 18 applies only to a `Rational` in the unit passed and to a `Measured` whose unit has a symbol or is dimensionless. Also wraps a test line that was over 125 columns. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
christianparpart
marked this pull request as ready for review
October 5, 2026 02:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Traces, rendered formulas and number text
an offset and no symbol traced as a bare number in a scale nothing named: one half in hundredths read
50. Such aunit is now refused at compile time wherever it is used: a quantity, a constant, a rounding, or a table's key or
result. The refusal reads
formula: a dimensionless unit with a scale must have a symbol. Give the unit a symbol(
%,ppm, …) or declare the quantity in scale 1.round(#1, to 2 dp of 1/1000 kg), where it readround(#1, to 2 dp)next to a value in kilograms. An offset unit is named by its size and its zero:
to 1 dp of 1 K from 5463/20 K.numeric(x, in <unit>)names the unit the same way.render()writes every number a formula declares in such a unit in the coherent unit, as its trace does:constants (
3/1000 kg, not3), per-element constants, lookup bands and rows, binning classes, snap values,domain points and conformity limits.
number_textandstd::formatwrite aMeasuredvalue in such a unit the same way, with the same spellingcode as the trace.
\operatorname{round}_{2\,(1/1000\,\mathrm{kg})}, and coherent powers aresuperscripts:
\mathrm{kg}^{-1}.written with negative exponents:
20000/413 kg^-1, not20000/413 1/kg. Units with a numerator keep the slash(
m/s,EUR/JPY).40 gonboth lines, where the first pass read
1/25 kg.its low bound only where it writes it.
Numerics
rounded_ln,rounded_log10androunded_exptake every argument aRationalholds. The kernel narrowedits argument to 64 bits and answered
Overflowbeyond, soln 2^70was refused; it now answers 48.5203 at4 places.
rounded_exp's cap moves from 44 to 887/10, past which no result fits aRational. Below the cap everyresult that fits the declared places is answered: e^45 to 18 places, e^88 to whole units.
Rationalis still decided.The 38 reference comparisons that were undecided are now 0.
decimal.a census hook in the exact fit.
readings (
examples/fifty_readings.hpp).Diagnostics and documentation
explainandchecked_explainhanded a series refuse it in the library's words, pointing atexplain_series,instead of failing with "no matching function".
stage of development produced them.
and multiply as built.
Closes #11
Closes #13
Closes #14
Closes #15
Closes #16
Closes #17
Closes #18
Closes #19
Closes #20
Closes #21