Add logarithms, fits over observations with R², and multiple regression - #7
Merged
Merged
Conversation
added 30 commits
September 29, 2026 22:30
A value that is irrational, or a fraction too wide for 64 bits, is written only as the correctly rounded decimal its formula declares, proved with integer arithmetic: wide unsigned integers, exact rounding of a wide fraction and of an enclosure, and rounded_output for an opaque operation's output. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
ln, log10 and exp as function nodes that are exact where the value is rational and Inexact elsewhere, and rounded forms that give the correctly rounded decimal from an integer kernel; a real power stays out of scope. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Observations become an opaque input; a least-squares line over them reports intercept, slope, r squared and points, and a multiple regression refuses a singular design, decided exactly in wide integers. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Seven tasks: wide unsigned integers; exact rounding of a wide fraction and of an enclosure; rounded_output and its refusals; the page, overlays and walks; the trace; the curve fit's compute_exact with its census rows; the display rule and the guides. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Seven tasks: the plain ln, log10 and exp nodes; the walks; the plain nodes on the page and in the trace; the integer kernel with its reference table; the rounded forms; the rounded forms on the page and in the trace; the guide, gallery and CHANGELOG. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Seven tasks: observations in a header of their own; observations as an opaque input; the regression kernel, exact in wide integers and approximate in double; the line through observations with r squared and points; its example, guide and census; multiple regression refusing a singular design; its example, guide and census. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…time smoke test A whole rounding through the kernel costs 240,000 to 285,000 constant-evaluation steps on cl 19.51 and about 298,000 on clang-cl 22.1.3, too close to either default to repeat in every value check. Every check that reaches the kernel is now a run-time CHECK, as for the other wide kernels; special points and early exits stay compile-time, and one deliberate compile-time rounding (log10 2 at 3 dp) keeps the compile-time claim true. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… 64 bits A value the 64-bit Rational cannot hold -- a least-squares slope through many readings -- still has an exact decimal it rounds to, and finding it needs integers wider than 64 bits. WideUnsigned holds 32-bit limbs and forms every product in std::uint64_t, since cl has no 128-bit integer; every operation that can leave the width answers an empty std::optional instead of wrapping, named *_checked_or_none as the 64-bit helpers beside it are, and no shift is by 32 or more. Short division by a 32-bit divisor goes limb by limb, and a product skips its left operand's zero limbs, which keeps both cheap in a constant evaluation. Signed values and fractions are a sign beside magnitudes. Internal to the library. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…a Rational The decimal a value rounds to can be decided in integers even when the value itself needs more than 64 bits. round_wide_ratio reduces the fraction, scales it by a power of ten and decides the last digit by comparing the remainder with its complement, in every mode, agreeing with checked_round wherever that answers. rounded_in_unit rounds in a declared unit as a rounding node does, and decide_rounding answers for a value known between two bounds only when both round alike -- Overflow otherwise, never a guess. Internal to the library. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
An opaque operation's output can be a fraction too wide for Rational -- a line fitted through many readings -- and the exact route can only say Overflow. rounded_output states the unit, places and mode the output is reported at and answers the decimal the true output rounds to, exactly: through an operation's own exact hook in wide integers when it declares one, through compute<Rational> otherwise. rounded<>(opaque_output<>()) keeps its meaning. A sink hearing the call is told it holds no values, since none exists until the output is rounded. Refused in the library's words for an unknown output, a unit of another dimension and a unit with an offset, and under double. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…on plainly
rounded_output answers the rounded decimal of an output too wide for Rational only for an operation that states its outputs in wider integers; any other operation's output is computed in Rational and fails with Overflow where opaque_output would. The node's documentation and the changelog promised the first without the condition. The node's comment also gave its reason for leaving call without a {} initialiser as a label a reader cannot look up; it now states the reason: such an initialiser turns a default-constructibility probe, as std::tuple's default constructor makes, into a hard error.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A rounded output is read everywhere an opaque output is: it renders as the rounding it states, the page lists its call once whichever outputs are used and however, an overlay's constant or derived quantity reaches the call's inputs, and the precision, calculation, rejection and retry walks see those inputs. Each registry treats it as the output it rounds, so the two cannot drift apart. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ded value A value the exact layer cannot hold must not appear in a trace, even as a fraction of a call's outputs. A call evaluated for a rounded output names its outputs without values, and the output's own step holds the rounded decimal -- exact, so no number style marks it approximate -- with its unit, places and mode, as a rounding step does. A failure the call carried reads as the call's on the output's line. The new step kind is appended to StepKind. The empty value array a hearing sink is handed is now a namespace-scope constant: cl value-initialised it inside a function through a helper that declares a local i, which hid a consumer's global of that name. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The overlay case compared only the method's value, and the rewritten fit's slope rounds to the same 41.9 mm/min under the method's rule whether or not the rewrite keeps the 4 dp rounding, so a rewrite that dropped it passed every test. The case now checks the rewritten node's unit, places and mode, and that the rewritten variant still renders as a rounding. The exact route's call row is checked to say it answered, as the rounded route's is. Also a stray blank line and a comment wrapped past its paragraph. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…e answers where the exact one overflows The exact least-squares route overflows from 34 readings at three decimals: compute's intermediates leave 64 bits there, although the fitted slope itself, reduced, still fits. compute_exact is the same line as compute's, from uncentred integer sums on a common denominator, in 256 bits, and rounded_output rounds the one coefficient a method reports: on those readings it answers at every size to 128 points, and agrees with the exact route in every mode wherever that answers. The census page pins where it stops: 58 points on a different denominator for every point. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…uides The display rule gains its last clause: a value the exact layer cannot hold is written only as the rounding its formula declares. The display guide says what that means for a trace line -- exact, no approximation marker, the true value written nowhere -- and which values answer that way: a root, and an output of an opaque operation that computes in wider integers, as linear_least_squares does; any other operation's output is computed in Rational and overflows where the exact output would. The opaque-operations guide shows the rounded slope's trace and the fit it answers where the exact one overflows, and the expressions guide and the entry points' comments point there instead of at double. The opaque header's file comment no longer calls a fit's output too wide for Rational: computing it exactly is what leaves the range, and only an operation that computes in wider integers answers there. Four lines of trace_render.hpp over the 125-column limit are rewrapped, layout only. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… a dimensionless formula ln, log10 and exp are nodes, so a formula that takes them is still a formula: dimension-checked where it is written, with a refusal that names the argument and the two ways to make it a bare number. Exactly evaluated they answer only where the value is rational, and are Inexact elsewhere, as sqrt(2) is; in double they call the standard library. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…e a logarithm A jurisdiction's fixed constant reaches the argument of ln, log10 and exp; the whole-method rule sees a substitution made there; a precision_level hidden in one is still found; and a definition lists what its logarithm reads. The substitution entry is needed for correctness: its primary template answers that nothing was substituted. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A logarithm or an exponential reads as the call it is in every dialect -- \exp in LaTeX, since a
power renders its base as an atom and e^{x}^{2} is no formula -- and a derivation writes it as a
step on its argument's, whose value is exact or whose error says there is none. Three StepKind
enumerators are appended.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…nentials ln, log10 and exp of a rational computed in 384-bit fixed point with 128 fraction bits, every step truncating, so each answer is an enclosure whose width the header derives: under 2^-120 absolute for the logarithms and 2^-119 relative for the exponential. No floating point: the same inputs give the same bits on every compiler, at compile time and at run time. The stored ln 2 and log10(e) are checked against their published digits and re-derived from the kernel's own series, and 39 inputs against a 40-digit reference decide as the reference does in every mode. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…keep its names clear of consumer globals The kernel's exponential entry point named a local `scaled`, which is one of the consumer globals every public header must not hide: cl reports it as C4459 and g++ -Wshadow as a warning as soon as a header that includes the kernel is compiled beside them. It and `halved` are renamed for what they hold. The header's argument for the atanh series' deficit added to more than it claimed: the tight count is at most 41 terms, so it now reads 41 + 2 (1 + ... + 1/81) + 2.25/83 < 47, checked with exact fractions along with the other inequalities of the derivation. The determinism claim is stated as what it is -- only integer operations the language defines exactly, so no floating-point mode enters -- and names the two toolchains the suite has passed on, instead of every compiler. An internal label and a stand-in step count from planning are replaced by the figure measured on this code. The tests pin the logarithms' width at 2^-120 as derived, so a fivefold loss of tightness fails them. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ctly to declared places A method that reports ln, log10 or exp to so many decimals states an exact number: the decimal the irrational value rounds to. rounded_ln, rounded_log10 and rounded_exp compute it -- the special points through checked_round, which alone can see a tie, everything else through the integer kernel's enclosure -- and answer Overflow where the result does not fit or the rounding cannot be decided, never a guess. Overlays, precision limits and calculations see inside them. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…the tiny-exponential rule The test meant to show that places outside -18...18 are answered before the tiny-exponential rule used Ceiling, whose one-unit answer overflows on its own at 19 places, so the places check could be deleted with every test still passing. It now also pins the modes only that check decides: at 19 and -19 places, exp of -50 is Overflow under HalfEven and Floor, where the tiny rule alone would answer 0. Also tidies the double-refusal negative (indentation, and a local named for the exponential it holds), reflows a comment, renames a test alias that read like the public Outcome, and drops two unused includes. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…n every dialect The page states the rounding and the call, round(ln(x), to 4 dp); the trace writes one step whose value is the rounded decimal and whose bracket names the mode, so the irrational value appears nowhere. Three more StepKind enumerators are appended. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
How to write ln, log10 and exp of a bare number, where each is exact, how to declare the precision an irrational value is reported at, what goes wrong and how it reads on the page; the display guide names the logarithm and the exponential among the values the exact layer cannot hold, with the trace line of a rounded logarithm; the census page says the kernel's wide words are outside its tally; and the gallery shows a logarithmic reduction rounded exactly to 0.01. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Observations were declared in binning.hpp, whose comment said a binning was the only reader. The sample statistics read them too, and an opaque operation is about to: including a binning's classes, lookups and bands for that is weight nobody needs. The declarations move unchanged, with the stand-in for refused observations, and binning.hpp includes the new header, so every existing include keeps compiling. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…h its double counterpart A fit through fifty readings at four decimals is an exact fraction far wider than 64 bits, and a fit of several regressors must decide exactly when its design is singular. The kernel brings each column to a common denominator so that every sum is an integer, solves the centred normal equations by fraction-free elimination, where a zero pivot is an exact verdict of singularity, and returns wide fractions for rounding. Its widths come from a bound on realistic data: 12 limbs for a line, up to 73 for eight regressors. The double route runs the same shape with square-root-free Cholesky and refuses a pivot below 1e-9 of its diagonal, since rounded data cannot decide singularity. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…of internal labels Two comments referred to things a reader cannot look up: a signed type by a label, and a rule by a name. They now name WideSigned, and say in plain words why the exact kernel's tests there run at run time. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A method fits "every determination that meets the condition": how many there are is data. Observations modelled that for statistics but could not reach an opaque operation. They now arrive as one span over the observations made, in any representation, and their counts are not a framework rule, since two independent samples differ in size. An observation that fails to convert is relayed with its position, marked as an observation, so the trace names it "at observation k". Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… number as a placeholder With raw observations as an input, an opaque call's failure position can count an observation as well as an element, but the docs of OpaqueCallFailure::element and Step::failureSite still described series and curves only. Both now name both, and point to the site for what the position counts. The changelog wrote the step number of a relayed failure as a literal #1 where it is whichever step the observations have, and said a switch over InputShape warns without naming the flag that makes it so. One overlong message line is rewrapped, its words unchanged. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
added 25 commits
September 30, 2026 11:16
…ample and the census The guide shows the fit over observations exactly and rounded where used, its R² as an acceptance, the flat-response refusal, and fifty readings that overflow exactly and answer rounded. The census measures where each route stops over 2 to 128 points: the exact route early, since a call's outputs fail together, the rounded route not at all on realistic data. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… the census The guide shows a two-regressor fit, what its outputs mean when a regressor is read in degrees Celsius or in percent, and a singular design refused, with the tolerance the double route decides by. The census adds a two-regressor row: its exact route stops where the line's does, and its rounded route does not stop on realistic data. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…unwrapped regressor, and rewrap a comment A call with one unwrapped regressor and a Citation object is viable for both refusing overloads; a negative case now pins that it draws exactly one library message. The no-regressor case lists the strings it rejects and says that the two naming the constraint are g++'s wording and can only fire there. One comment line in least_squares.hpp is rewrapped. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…n's reach Several sentences claimed more than the census and the tests showed: that a rounded fit answers wherever the exact one overflows, when a different denominator on every point outgrows the kernel from 62 points; that a nearly singular design has few trustworthy digits in double, which no run measured; that the double tolerance can refuse a line, when for one regressor it cannot; and that one message wording can only occur on g++, when clang++ 22 words it the same way. Each now says what was measured, and the census's line rows fail on any error other than Overflow, as the two-regressor row already did. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…e time No test instantiated a regression of more than three regressors, so the 37- to 73-limb solve that multiple_least_squares promises for four to eight was never compiled by the suite. A fit of eight regressors over ten rows of small invented integers now runs exactly -- every output against Python's fractions -- rounded where used, and in double; a fit of four exactly. Ten rows of eight regressors exceed the constant-evaluation limit of cl 19.51 (1048576 steps) and of g++ 14.2 (33554432 operations), as measured, so both run at run time. The four-point line through observations and the six-row fit of two regressors do evaluate in a constant expression on both compilers, so their outputs are now checked at compile time as well, exact and rounded where used, and the three comments that said an exact fit was too long for a constant evaluation say instead which fixtures run at run time. A logarithm of the line's exact R², rounded to six places in two modes, pins the one composition of the logarithms with a fit. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The tie of 87.85 g rounds to 87.8 under HalfEven and under HalfTowardZero alike, so swapping those two modes passed every test; a span of 87.75 g, whose kept digit is odd, tells them apart. round_wide_ratio was pinned at -2^63 but not one past it: loosening its bound by one answered -2^63 - 1 as IntMax, a wrong sign, and no test failed. narrow_wide_ratio's numerator and denominator limits were not pinned either; without the denominator's, 2^64 - 1 would reach Rational as -1. All three now answer Overflow under test, and a denominator of IntMax still fits. The header's claim that round_wide_ratio answers values checked_round refuses had no case behind it: 10/3 at 18 places is one. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
opaque_observations_where_series_declared rejected the dimension check's and the compute check's messages, but its operation accepted every dimension and its compute took the span the observations give, so neither could appear whatever the shape check did: deleting the shape gate left the test passing. The operation now accepts no dimension, so the gate is what keeps the dimension check's message away, and deleting it fails the test. The compute check stays behind the dimension check, so its REJECT could never fire and is dropped. transcendental_refused_operand and transcendental_then_addition had no EXPECT_COUNT: each now pins its one message. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…leased changes statistics.hpp includes observations.hpp now instead of binning.hpp, and with it no longer brings in band.hpp and lookup.hpp: a translation unit that used a binning, a band table or a lookup through statistics.hpp alone compiled on 0.1.0 and does not now. The changelog promises to record every such change, and did not record this one. The unreleased section read as two halves: the regression's bullets opened Added and named rounded_output twenty bullets before introducing it, while the declared precision's and the logarithms' closed it, and Changed was split three and three. The three features' bullets are now one run at the end of each, in the order they build on each other -- declared precision, logarithms, regression -- and the regression's guide sections, example and census rows have a bullet as the other two features' docs do. Two sentences are corrected: "which the library's refuses" had no clear antecedent, and OpaqueCallFailure's bullet now states its structured binding's member count, as OpaqueCallInfo's and OpaqueStepData's do. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Four doc comments -- checked_evaluate_si's, explain's, the trace's number styles' and render_trace's -- list rounded_sqrt and rounded_output as the way to report a value Rational cannot hold, and leave out rounded_ln, rounded_log10 and rounded_exp, which do the same for a logarithm or an exponential; the display guide lists all three. They now name them. The README's and the site's status tables gain a row each for values reported at a declared precision and for logarithms and exponentials, beside the regression's, and the guide rows of the two guides that gained a section say so. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… guides - detail/transcendental.hpp stated in an installed header which compilers the test suite had passed on, "only": a verification status that CI outdates, and inaccurate already. The design intent beside it stays. - The regression kernel's width: "an estimate, checked" read as checked against a model; every wide operation checks it and answers Overflow. The centred sum's bound is Cauchy–Schwarz's, 2^144, so that 288 = 2 * 144 follows for R², where the text said 2^145; d(K) is defined before it is used, and its ceiling has one reading. all_equal_to_first said nothing is not "all equal", where the code answers true for an empty span. - divmod and divmod_small shared one precondition but fail differently for a zero divisor: divmod answers every bit set up to the dividend's leading bit and the dividend as the remainder, divmod_small divides by zero. Each now says which. wide_rounding.hpp includes <compare> for the std::strong_ordering it uses. - RoundedOpaqueOutputNode's Origin read as naming its default, and its mode's comment said only an exactly rational output can tie, which is every opaque output. - ObservationsNode described anything that can be binned, where fits and statistics read observations too. - A test's logarithm checks sat between a comment and the precision limit it describes; the count of the wide rounding's agreement cases said who measured it rather than what it counts; the guide's tolerance paragraph was ragged. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…read back line_outputs took the row count as a wide integer and read it back for the number of points with to_u64().value_or(0): a fallback that cannot happen, since the count came from a std::size_t, and that would have reported 0 points silently had it ever run. It now takes the row count as solved_outputs already does, a std::size_t, and widens it once for the product it needs. Every output is the same. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
OpaqueStepData::answered was a bool, a new public data member that the
release would freeze. It is now answer, of a purpose-named enum,
OpaqueAnswer { Unanswered, Answered }, whose zero value keeps a row built
by hand reading as a call that did not answer.
One enum of answered, absent and failed would have made the row's
invalid combinations unrepresentable only by folding in failure, which
0.1.0 released beside it; a third value would instead have added a
second way to say the call failed. So the enum says what the bool said,
and failure keeps saying why a call gave nothing.
The trace renderer and the tests read it, and a test now pins
Unanswered, with no failure, for an absent call on the rounded route.
The changelog names the member and its type.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…its sum
signed_enclosure took a bool that both negated the ends and exchanged
them; it now takes a LogarithmSign, the enum LogarithmMagnitude records
beside its ends, so a call reads which side of zero the logarithm lies on.
accumulate reported success as a bool and grew its total in place; it
now answers the sum as a std::optional, as every other wide operation
does, and its callers keep a total only when it held.
The fifteen or so WideRatio and WideSigned initialisers that spelled the
sign as a bare true or false name it now, { .negative = false, ... }.
Every output is the same.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…and for them with_constant<Length> on a method whose fit reads observations<Length, 64> was told the method reads the quantity "as a series", which it does not. The refusal was written for series before raw observations could reach it, and every fit over observations reaches it now. RequireConstantNotSeries and RequireDerivationNotSeries say "as a series or as raw observations; one constant (one definition) cannot stand for many values", as the calculation's refusal of the same reads already names both. The five pinned negatives expect the new words; run first against the old header, all five failed, and pass with it. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Five comments in three test files pointed a reader to "the plan" for what a fixture is, without saying which plan or what it says. Each now says it: the fit's quantities, and the working behind its slope and intercept; the opaque tests' quantities; the rejection tests' two samples and critical-value tables, and that the expected values were computed with those tables as they stand. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
`round(<inner>, to <places> dp of <unit>)`, and its LaTeX
`\operatorname{round}_{...}(...)`, were spelt out in four render_node
overloads -- the rounding, the rounded root, the rounded logarithm or
exponential and the rounded opaque output -- and again in five places of
the trace. detail::rounding_call<D> in render.hpp is now the one spelling,
and the trace's rounding_call_text, used by the Round, RoundedRoot,
rounded-logarithm and RoundedOpaqueOutput steps and the rounded output's
line, writes it through the same function in the plain dialect. The
trace's transcendental_call was transcendental_text<Dialect::Plain> by
another name and is gone.
Every rendered formula and trace line is byte for byte as before: the
tests that pin them pass unchanged.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…uts, once rounded_opaque_output_line copied opaque_output_line's naming of the output from its call's row; opaque_output_label is now both lines' one naming. opaque_call_line listed the outputs twice -- with their values on the exact route, by name on the rounded one -- each with its own copy of the budget and the "... k more" ending; opaque_outputs_listed now spends the budget and ends a list cut short for both, told only how to spell one output and what to join them with. The exact route's cut-short list was pinned; the rounded route's was not, so a test now pins it at two budgets. It passes on the listing before this change and after it, and fails when the helper's ending is mutated. Every other trace line is byte for byte as before. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
opaque_rounding_answer repeated opaque_compute's three steps -- hold each evaluated input, turn the held values into compute's arguments, apply -- to call compute_exact or compute<Rational> instead of compute<Rep>. opaque_apply<Rep> now holds and applies for both, and each says only what it calls. The held values still live until the call returns. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ument The node knows at compile time which function it rounds, yet detail::rounded_transcendental took it at run time and switched over it twice: once in exact_transcendental, a run-time copy of the dispatch that transcendental_of<F, Rep> already makes at compile time, and once to pick the kernel's enclosure. It is now a template on F: the special points come from transcendental_of<F, Rational>, the enclosure is chosen by if constexpr, and exact_transcendental, with the unreachable DomainError after its switch, is gone. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…divides common_denominator ran a full lcm -- a binary gcd and a bit-serial long division over the wide integer -- for every element, even when its denominator repeated or already divided the running common denominator, as it does for every reading of one unit at one number of decimals. A denominator below 2^32 that divides it is now found by divmod_small, one step per limb, and leaves it as it is; the lcm of the two is the running denominator either way, so every common denominator is the same. all_equal_to_first says what it checks with std::ranges::all_of over every element after the first, compared with the first as before; it still evaluates at compile time on cl, which the line's compile-time checks prove. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ck and lcm LinearLeastSquares::compute and compute_exact each hand-rolled the loop that asks whether every point equals the first, and compute_exact its own lcm loop over both columns, beside the regression kernel's all_equal_to_first and common_denominator. Both now call the kernel's, after the same check that the spans are as long as each other. The curve fit keeps its own exact route, uncentred and 256 bits wide, where the line through observations is centred and 384 bits wide: exact_limbs now says why, in one place -- its outputs, lines and census figures stay as they were, and flat values, which the kernel refuses, still fit a line of slope zero. Every pinned output and census row is as before. One input answers differently, in double only: a curve of one point whose point is NaN was taken for a point distinct from itself and fitted to NaN outputs; like any other single point, it is now the fit's own DomainError. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
MultipleLeastSquares::compute and compute_exact each copied the column pack into an array and then the first K into a second one, for the regressors, before the values. detail::split_columns<K> does it once and answers both parts. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ur operations rep_add, rep_sub, rep_mul and rep_div were four copies of one body: pass on the first failure, else make one RepTraits call. rep_apply<Operation, Rep> is that body once, the operation named by a RepOperation enumerator rather than handed a RepTraits function, so that a representation's traits may overload theirs. The route still makes every operation its own call, in the same order, so a double answer is bit for bit as before. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Against master, the branch added 162 lines past the 125 columns the repository's .clang-format sets: 27 under include/ and 135 in tests. git clang-format master formats only the lines that differ from master, so untouched code keeps its layout. Afterwards no line the branch adds under include/ or in a test source exceeds 125 columns; the 10 left are negatives' one-line "// EXPECT:" headers, which state the expected message whole. detail/transcendental.hpp, new on the branch, loses the indentation its namespace formula::detail has nowhere else. What clang-format cannot settle was done by hand: the negatives were formatted without reflowing comments, since reflowing merged their EXPECT and REJECT headers into one paragraph, and two rows of the logarithm's reference table, in a clang-format off block, are wrapped. The opaque-and-retry guide quotes four blocks of its example verbatim, and quotes them as they are formatted now. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…n double LinearLeastSquares::compute<double> now decides "fewer than two distinct points" with the kernel's check, which compares each point after the first with the first. The loop it replaced compared the first point with itself too, and NaN equals nothing, itself included: one NaN point passed for two distinct points, and the fit answered NaN for the intercept and the slope. It is now the fit's own DomainError, as any single point is. That is the only input that answers differently: with two or more points a NaN point is fitted as before, and inputs without NaN are unchanged. The CHANGELOG records it, and a run-time test pins it; the test fails with the old loop put back and passes with the kernel's check. Comments this branch left imprecise now say what holds. The run-time fits in least_squares_tests.cpp start at five points, not fifteen. Most of the kernel tests' fixtures, not all, run at run time. rounding_call spells every rounding to one number of decimal places, and an element-wise rounding has its own spelling. The comments of the negative test for an overlay on a statistic's sample say what the refusal pins, since its message changed. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
This was referenced Sep 30, 2026
Merged
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.
Three kinds of formula the library could not express: a logarithm or an exponential (#3), a least-squares line
through a runtime number of observations with R² (#4), and a regression on several regressors (#5). Each needs a
value the exact layer cannot hold -- an irrational logarithm, a fit's fraction too wide for 64 bits -- and the library
shows every number exactly. So all three rest on one rule: such a value is written only as the correctly rounded
decimal its formula declares (unit, places, mode), proved with integer arithmetic alone. The rounded decimal is the
step's exact value, its trace line says it was rounded and carries no
≈, a rounding that cannot be decided isOverflow, and no floating point reaches a trace. The plain forms stay exact or refuse.Closes #3, closes #4, closes #5.
Changes
Values the exact layer cannot hold
detail/wide_int.hpp,detail/wide_rounding.hpp), whichagree with
checked_roundon every value it accepts.rounded_output<"name", Unit, Places, Mode>(call): an opaque operation's output at a declared precision -- on thepage, under overlays, in the walks and in the trace, with the call's line naming its outputs "rounded where used".
An operation that computes in wider integers answers where the exact output overflows;
linear_least_squaresover a curve now does, up to 128 points of readings at three decimals.
docs/display.mdgains Values the exact layer cannot hold; the opaque-operations and expressions guides, thecensus page and the example show the rounded route.
Logarithms and exponentials (#3)
ln,log10,expin every dialect and in the trace: exact where the value is rational (ln 1, exp 0,log10 10^k), otherwise
Inexact;DomainErrorat or below zero; a dimensionless argument is enforced at compiletime. In
doublethey answer directly.rounded_ln,rounded_log10,rounded_exp<Places, Mode>: correctly rounded from an integer kernel(
detail/transcendental.hpp, error below 2^-118), one fused step in the trace.A line through observations, with R² (#4)
("at observation k") in the trace.
linear_least_squares(observationsX, observationsY, citation): intercept, slope, R² and the number of points,exact through a wide regression kernel,
rounded_outputbeyondRational's range, anddoubleuntraced. Flatvalues are the fit's own
DomainError.Multiple regression (#5)
multiple_least_squares(regressors(x1, ..., xK), y, citation)for K from 1 to 8: a constant, one coefficient perregressor in its own unit, R² and the number of points. A singular design is refused exactly (fraction-free
elimination); the
doubleroute refuses a design within 10^-9 of singular.twice.
Compatibility (all recorded in the CHANGELOG):
StepKindgains seven enumerators, appended;OpaqueCallFailure,OpaqueCallInfoandOpaqueStepDatagain members, appended and defaulted;InputShapegainsObservations; an unqualifiedln,log10orexpon a formula node now finds the library's by argument-dependentlookup;
statistics.hppincludesobservations.hppinstead ofbinning.hpp; the curve fit'sdoubleroute refusesa lone NaN point.
The design documents and implementation plans are under
docs/superpowers/.