* functions: one work budget across every call of a lifted function
A function lifted over a computed array runs once per element, and only the
six lookups counted their repeated work against LIFTED_SCAN_BUDGET. Criteria
aggregates rescanned their ranges per criterion, CUMIPMT/CUMPRINC stepped
through up to 1e7 periods per element, and array-producing functions such as
MUNIT built a whole result per element before keeping its first cell.
The budget now covers all of it: COUNTIF/SUMIF/AVERAGEIF(S)/MAXIFS/MINIFS
charge their range cells per call, the period loops of every element are
added up before any runs, and array results charge their cells as they are
built. Past the budget the call is #NUM!, as lifted lookups already are.
* address review: charge numeric-text periods of lifted CUMIPMT/CUMPRINC to the work budget
* address review: charge only calls that reach the cumulative period loop
* address review: charge the numeric text a lifted period call parses
* address review: SUMIF/AVERAGEIF charge only the sum-range cells they read; weight cumulative period work
* calc: INDEX decides a multi-cell position from its shape before reading it
* calc: charge each formula's evaluation against one cumulative work budget
* address review: a database function whose computed criterion ran out of work is #CALC!
* address review: give the test's criteria cell its stored value
* address review: stop matching database records once a computed criterion exhausts the work budget
* address review: skip the criteria-row scan once work is exhausted
* address review: stop between conditions once the work budget is spent
* functions: TEXTJOIN builds its result piece by piece within the text cap
TEXTJOIN converted every input and delimiter into an owned String before
joining, so many references to one long shared text copied gigabytes before
the joined length was ever checked. Errors are now found first without
building text (so they still win in argument order), then each piece and
delimiter is converted only as it is appended, stopping at the cap.
* address review: check the cap before copying each text piece
* functions: stop WORKDAY, PIVOTBY, SORT and the D-functions rescanning their input
- WORKDAY/WORKDAY.INTL counted the holidays each step passed by scanning every
holiday, once per step; a run of consecutive holidays made that quadratic.
The working-day holidays are kept sorted and counted by binary search.
- PIVOTBY totals rescanned the whole source (rebuilding keys) once per total
row and column. The grouping pass now also indexes rows by row key and by
column key, and totals read from those indexes in the same order.
- SORT copied one key per sort_index entry for every row, so a long
sort_index with repeated columns multiplied the source. Repeats can't break
a tie their first use left, so only first uses are kept.
- DSUM and the other database functions tested every record against every
criteria row. A selection whose records x conditions exceed 5e8 is #NUM!,
and each criteria label's database column is found once, not per cell.
* address review: bounds-check SORT indices before narrowing; empty criteria rows are not charged
CEILING.MATH(6.7,VLOOKUP({"k","k"},A1:B1,2,FALSE)) with B1 empty now gives 0 per
element, as the scalar call does, instead of treating the significance as left out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FILTER(1,FALSE,VLOOKUP(...)) with a blank lookup result returned #CALC!
because util::has read the blank value as a left-out argument. Arguments
written in the formula that compute a blank are now marked as given.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 64-ulp spread threshold still classified distinct representable
predictors such as [1e15, 1e15 + 1, 1e15 + 2] as constant and dropped
them. A column is now constant only when every raw value is the same
double; the 1e15 case is added to the offset regression test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The small-k product also needs (1 - p)^(n - k) to stay a normal number;
otherwise it returned 0 for BINOM.DIST(15,1100,0.5,FALSE) (exact
2.1367646606291324e-298) and the cumulative form built on it. Those cases
now use the logarithmic formula. Reference grid gets k = 10..25 at n = 1100.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A centred column was dropped when its length fell under 1e-12 of the raw
column, which also removed real variation such as [1e13, 1e13+1, 1e13+2].
Constant columns are now recognised on the raw values, within a few ulps.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* functions: match TEXTSPLIT/TEXTBEFORE/TEXTAFTER delimiters through an index, within a work limit
The delimiter matcher compared every delimiter, duplicates included, at
every position of the text. Delimiters are now deduplicated and grouped
by the character a match is anchored on, and a search that would
compare more than 100 million characters returns #VALUE!.
* functions: pass float literals in the delimiter test
* functions: MODE counts through an index, FREQUENCY bins by binary search
MODE/MODE.SNGL/MODE.MULT searched the list of distinct numbers seen so far
for every input, and FREQUENCY scanned the sorted bins for every data value,
so both were quadratic in computed arrays. MODE now keys counts by value
(-0 folded into 0, NaN never merged) and keeps first-occurrence order;
FREQUENCY finds the first bin at or above each value with partition_point
over the order it already sorts. Results are unchanged.
* address review: FREQUENCY keeps NaN data in the overflow bin and NaN bins empty
CodeRabbit pointed out that an error or NaN result passed the accuracy test and
that a zero reference allowed any finite value (the bound was infinite). Errors
now count as infinite error and a zero reference allows 1e-300.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CodeRabbit pointed out that the drop test used the centred column length, so a
constant column such as [0.1; 3] (centred to about -1.4e-17) was scaled to a unit
vector and kept, giving a meaningless coefficient and wrong df. A centred column
shorter than 1e-12 of its uncentred length is now treated as collinear with the
intercept. The suggested 1e-6 threshold was not used: it would also drop columns
that sit far from zero, such as 1e9 + i.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The mass came from ln_gamma differences (relative error 1.1e-13 at n = 100,
1.5e-11 at n = 10,000) and the cumulative forms summed up to 10,000 of them.
The mass now comes from Loader's saddle-point formula, the cumulative sum
walks away from the mode by the ratio of neighbouring terms and stops when
terms stop mattering, and the incomplete beta / gamma branches take the same
mass as their prefactor.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The normal equations square the condition number, so a degree-5 polynomial on
x = 1..12 came back with errors up to 2.6e-6 and x, x^2 near 1e6 gave slope
2.99976 and intercept 251 for y = 3x + 7. LINEST, LOGEST, TREND and GROWTH now
factor the centred, column-scaled data with Householder QR. Collinear columns
are still dropped under the same relative tolerance.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3221, "Check the output size before
with_subtotals builds subtotal lines": the size limit was checked only
after every subtotal line existed. A row with 4000 key columns at total
depth 4000 nested with_subtotals 4000 levels deep and overflowed the
stack (the process aborted) before the late check could refuse it.
with_subtotals now works without recursion: each line gets an id for its
key prefix at every level, a stable sort by those ids keeps each group
together in first-appearance order, and one walk adds the subtotals.
The output size (with the header and total lines when present) is
checked while the groups are counted, before any subtotal line is built.
Test: groupby_subtotals_past_the_size_limit_are_refused_before_they_are_built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GROUPBY's total_depth only ever added one Total line. In Excel a depth
of N adds a subtotal line for each group of the first N - 1 key columns
(after the group, or before it when the depth is negative) and labels
the last line "Grand Total"; a depth greater than the number of key
columns, or above 1 with field_relationship 1 (a flat table), is
#VALUE!. The subtotals aggregate the group's source rows, so AVERAGE and
COUNT subtotals are right too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>