Skip to content

Add logarithms, fits over observations with R², and multiple regression - #7

Merged
christianparpart merged 57 commits into
masterfrom
next-features
Sep 30, 2026
Merged

christianparpart merged 57 commits into
masterfrom
next-features

Conversation

@christianparpart

Copy link
Copy Markdown
Member

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 is
Overflow, 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

  • Wide integers and exact rounding of a wide fraction (detail/wide_int.hpp, detail/wide_rounding.hpp), which
    agree with checked_round on every value it accepts.
  • rounded_output<"name", Unit, Places, Mode>(call): an opaque operation's output at a declared precision -- on the
    page, 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_squares
    over a curve now does, up to 128 points of readings at three decimals.
  • docs/display.md gains Values the exact layer cannot hold; the opaque-operations and expressions guides, the
    census page and the example show the rounded route.

Logarithms and exponentials (#3)

  • ln, log10, exp in every dialect and in the trace: exact where the value is rational (ln 1, exp 0,
    log10 10^k), otherwise Inexact; DomainError at or below zero; a dimensionless argument is enforced at compile
    time. In double they 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 guide section, a gallery entry and the census page.

A line through observations, with R² (#4)

  • Raw observations as an opaque operation's input: a span over the observations made, with a failure's position
    ("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_output beyond Rational's range, and double untraced. Flat
    values 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 per
    regressor in its own unit, R² and the number of points. A singular design is refused exactly (fraction-free
    elimination); the double route refuses a design within 10^-9 of singular.
  • Refusals in the library's words for an uncited fit, unwrapped regressors, a series regressor and a quantity read
    twice.
  • Guide sections, the example and census rows for both fits.

Compatibility (all recorded in the CHANGELOG): StepKind gains seven enumerators, appended;
OpaqueCallFailure, OpaqueCallInfo and OpaqueStepData gain members, appended and defaulted; InputShape gains
Observations; an unqualified ln, log10 or exp on a formula node now finds the library's by argument-dependent
lookup; statistics.hpp includes observations.hpp instead of binning.hpp; the curve fit's double route refuses
a lone NaN point.

The design documents and implementation plans are under docs/superpowers/.

Christian Parpart 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>
Christian Parpart 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant