Skip to content

Add FORECAST.LINEAR, INTERCEPT, TREND, GROWTH and LOGEST functions - #1802

Open
Tobiadefami wants to merge 12 commits into
developfrom
feature/HF-414
Open

Tobiadefami wants to merge 12 commits into
developfrom
feature/HF-414

Conversation

@Tobiadefami

@Tobiadefami Tobiadefami commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Context

This PR adds five regression functions: FORECAST.LINEAR (also available under its legacy name FORECAST),
INTERCEPT, TREND, GROWTH and LOGEST.

Important

Stacked on #1769 (LINEST). TREND, GROWTH and LOGEST reuse LINEST's least-squares fit and its input
helpers, so this branch is based on feature/hf-221-linest. Until #1769 merges, this diff also shows LINEST's
commits. Review only the commits from feat(HF-414): add INTERCEPT and FORECAST.LINEAR onward:
feat(HF-414): add INTERCEPT and FORECAST.LINEAR, feat(HF-414): add TREND, GROWTH and LOGEST,
docs(HF-414): link the pull request in the changelog, fix(HF-414): accept the text TRUE and FALSE as const and stats
and feat(HF-414): list the regression functions in the license catalog. Once #1769 merges, this branch will be
rebased onto develop and only these commits will remain.

Behavior, checked against Excel:

  • FORECAST.LINEAR and INTERCEPT pair known_y/known_x like SLOPE: a pair is skipped when either value is
    blank, text, numeric text or a boolean, and an error in a pair is returned. Ranges need the same number of cells
    (#N/A otherwise); fewer than two pairs, or x values that are all equal, return #DIV/0!.
  • TREND, GROWTH and LOGEST follow LINEST: every known value must be a number (#VALUE! otherwise), dimensions
    must be compatible (#REF!), and several x-variables are supported in column or row orientation. known_x
    omitted means 1, 2, 3, …; new_x omitted means known_x.
  • Collinear x-columns are handled as in LINEST: the later column is kept and the removed one gets a coefficient of
    0 (LOGEST shows its base as 1).
  • GROWTH and LOGEST require positive known_y values (#NUM! otherwise). GROWTH evaluates b·m^x as Excel
    does, so overflow and underflow give #NUM! in the affected cells.
  • LOGEST returns LINEST's layout: one row of bases and the constant, or five rows with stats set. The
    statistics are LINEST's statistics of the fit to ln y.
  • const and stats: a blank cell is FALSE, an empty argument is the default, numbers and booleans are coerced, the
    text "TRUE" or "FALSE" (in any letter case) is accepted as in LINEST, and other text is #VALUE!. An error written in the formula is returned as is; a cell holding an error gives #VALUE!, as in
    Excel.

Note: Microsoft's FORECAST.LINEAR and INTERCEPT pages say empty data returns #N/A. Excel returns #DIV/0!, and
this implementation follows Excel.

Implementation

  • INTERCEPT and FORECAST.LINEAR live in StatisticalAggregationPlugin, next to SLOPE, and reuse its pairing
    helper. A new private simpleLinearFit computes the line with plain two-pass sums, which reproduces Excel's last
    digits more closely than the jStat helpers SLOPE uses. SLOPE is unchanged. FORECAST is an alias of
    FORECAST.LINEAR.
  • TREND, GROWTH and LOGEST live in RegressionPlugin, next to LINEST, and reuse regressionShape,
    buildPredictorRows, regressionOutput and fitLinearRegression. LINEST's existing code is not modified.
  • TREND and GROWTH share one method and a new size method, trendArraySize, which predicts the result size from
    new_x (or known_y when new_x is omitted). LOGEST shares LINEST's size method, linestArraySize, because
    both return the same coefficient and statistics layout.
  • New error messages: ZeroVariance, PositiveValues, and the parameterized StaticResultSize(name) and
    StaticStats(name). No new error types.
  • Catalogue entries in categories/statistical.ts, and names in all language packs, read from Excel in each
    locale. FORECAST.LINEAR is not localized in any Excel locale; FORECAST is.
  • License catalog: the five functions are listed among the ungrouped functions in functionCapabilities.ts, so
    fun:all and their single-function tokens cover them. FORECAST travels with FORECAST.LINEAR as an alias.
  • Docs: a TREND, GROWTH and LOGEST section in list-of-differences.md and in known-limitations.md, next to
    LINEST's.

How did you test your changes?

  • Expected values come from Microsoft Excel Online (en-US). Each test is one measured case, entered on the same
    data the tests use, so any case can be re-checked in Excel.
  • 566 new tests in hyperformula-tests: function-forecast.linear 97, function-intercept 70, function-trend
    138, function-growth 136, function-logest 125. Numbers are compared with a relative tolerance, as in
    function-linest.spec.ts, because the fit can differ from Excel in the last digits. Cases cover the docs examples,
    exact and noisy data, several variables in both orientations, collinear columns, const and stats, coercion and
    errors in every argument, error precedence, and the FORECAST alias.
  • Full Jest suite with the private tests on this branch: 506 of 508 suites pass (6,916 tests passed, 3 skipped,
    pre-existing). The 7 failures are all VERSION tests in function-version.spec.ts and function-metadata-api.spec.ts:
    HF-307: license-key entitlement gating — capability model, key reader, API guards #1728 changed VERSION, and the tests branch is based on LINEST's tests branch, which does not include develop's
    updated specs yet. With develop's versions of those two specs, both pass. function-linest.spec.ts passes
    unchanged.
  • License catalog: develop's capability-registry.spec.ts completeness check covers all five functions (the only name it
    reports is LINEST, which is handled in feat(HF-221): add LINEST regression function #1769).
  • Mutation check: breaking one rule per function fails tests.
  • npm run lint (no new warnings), npm run compile, npm run docs:generate-function-docs, git diff --check.

Types of changes

  • Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore)
  • New feature or improvement (a non-breaking change that adds functionality)
  • Bug fix (a non-breaking change that fixes an issue)
  • Additional language file, or a change to an existing language file (translations)
  • Change to the documentation

Related issues:

  1. Depends on feat(HF-221): add LINEST regression function #1769 (LINEST).
  2. Tests: handsontable/hyperformula-tests#76

Checklist:

  • I have reviewed the guidelines about Contributing to HyperFormula and I confirm that my code follows the code style of this project.
  • I have signed the Contributor License Agreement.
  • My change is compliant with the OpenDocument standard.
  • My change is compatible with Microsoft Excel.
  • My change is compatible with Google Sheets.
  • I described my changes in the CHANGELOG.md file.
  • My changes require a documentation update.
  • My changes require a migration guide.

🤖 Generated with Claude Code


Note

Medium Risk
Large new numerical and array-sizing paths affect formula evaluation and spilled results; behavior is Excel-sensitive but confined to statistical functions rather than core engine security.

Overview
Adds Excel-style linear and exponential regression to the formula engine: LINEST (simple/multiple regression with optional statistics), plus FORECAST.LINEAR / FORECAST, INTERCEPT, TREND, GROWTH, and LOGEST.

INTERCEPT and FORECAST.LINEAR are implemented in StatisticalAggregationPlugin via a new simpleLinearFit helper (pairing behavior aligned with SLOPE). LINEST, TREND, GROWTH, and LOGEST live in a new RegressionPlugin backed by fitLinearRegression (Householder QR) in LinearRegression.ts, with shared shape/prediction helpers and static array-size prediction so stats (and result width for LINEST/LOGEST) must be constants—mismatches yield #VALUE!.

Also adds regression-specific error messages, statistical function metadata, localized names across language packs, changelog entries, and docs on limitations and Excel differences (constant stats, pre-sized arrays, numerical edge cases). The unreleased changelog in this diff also records unrelated fixes (MOD, pool functions, etc.) if they ship in the same release.

Reviewed by Cursor Bugbot for commit 2bad285. Bugbot is set up for automated code reviews on this repo. Configure here.

@cla-external-contractor-signup

Copy link
Copy Markdown

@Tobiadefami thanks for the pull request. No CLA step needed here — our records show you signed the Contributor License Agreement on 2026-07-31. That signature came from our previous signing form and has been carried over, so there is nothing for you to re-sign.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
hyperformula-docs 130c9dc Commit Preview URL

Branch Preview URL
Oct 08 2026, 10:55 PM

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread src/interpreter/plugin/RegressionPlugin.ts
Comment thread src/interpreter/functionMetadata/categories/statistical.ts Outdated
Tobiadefami and others added 5 commits October 8, 2026 23:50
Add INTERCEPT and FORECAST.LINEAR, with FORECAST as an alias of
FORECAST.LINEAR, next to SLOPE in StatisticalAggregationPlugin. Like
SLOPE, they skip pairs with a non-numeric value, propagate errors from
the ranges, and return #N/A when the ranges have a different number of
cells. Fewer than two points, or x values that are all equal, return
#DIV/0! (new ErrorMessage.ZeroVariance).

The fit uses plain two-pass sums for the means and sums of squares,
which reproduce Microsoft Excel's results to the last digits. In x,
FORECAST.LINEAR coerces a blank cell, booleans and numeric text, and
returns #VALUE! for an empty string, as Excel does. Both functions
enable array arithmetic for their arguments, so computed ranges work
in the default configuration.

Includes the catalogue entries and the names in all language packs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add TREND, GROWTH and LOGEST to RegressionPlugin, built on LINEST's
shape detection and fitLinearRegression.

- TREND returns the values of the least-squares fit at new_x (or at
  known_x when new_x is omitted). Its result size is predicted by the
  new trendArraySize: the shape of new_x for one predictor, one value
  per row or column of new_x for several.
- GROWTH fits ln y with the same method and evaluates b * m^x in
  Microsoft Excel's product form, so it overflows and underflows where
  Excel does. Non-positive known_y values return #NUM!.
- LOGEST returns LINEST's layout for the fit to ln y, with the first row
  exponentiated. It shares LINEST's size method and, like LINEST,
  requires a constant stats argument.

As in Excel, any non-number in known_y or known_x returns #VALUE!
(no pairs are skipped), dimension mismatches return #REF!, and const
and stats treat a blank cell as FALSE and text as #VALUE!. An error
read from a cell reference in const or stats returns #VALUE!; an error
written in the formula propagates.

New error messages: PositiveValues, StaticResultSize(name) and
StaticStats(name). Includes the catalogue entries, the names in all
language packs, list-of-differences and known-limitations sections, and
the changelog entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TREND, GROWTH and LOGEST returned #VALUE! for the text "TRUE" or
"FALSE" as const or stats, while LINEST accepts it and LOGEST's size
prediction already treated it as a constant. Read the options the way
LINEST does, which matches Microsoft Excel: "TRUE" and "FALSE" in any
letter case are booleans; other text, numeric text and an empty string
are #VALUE!. An error read from a cell reference is still #VALUE!.

Also state in LOGEST's short description that stats must be a constant,
with a link to the known limitations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add FORECAST.LINEAR, GROWTH, INTERCEPT, LOGEST and TREND to the
ungrouped functions of the function capability table, so they are
covered by fun:all and by their single-function tokens, like SLOPE and
RSQ. FORECAST is an alias and travels with FORECAST.LINEAR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 130c9dc. Configure here.

'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INTERVAL', 'ISBINARY', 'ISFORMULA', 'ISNONTEXT', 'ISPMT',
'ISREF', 'LCM', 'LOG10', 'LOGNORM.DIST', 'LOGNORM.INV', 'MAXA', 'MAXPOOL', 'MEDIANPOOL', 'MINA', 'MIRR',
'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INTERCEPT', 'INTERVAL', 'ISBINARY', 'ISFORMULA', 'ISNONTEXT', 'ISPMT',
'ISREF', 'LCM', 'LOG10', 'LOGEST', 'LOGNORM.DIST', 'LOGNORM.INV', 'MAXA', 'MAXPOOL', 'MEDIANPOOL', 'MINA', 'MIRR',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LINEST omitted from license table

Medium Severity

LINEST is a new built-in in RegressionPlugin but is not listed in UNGROUPED_FUNCTIONS or any FUNCTION_GROUPS entry. A built-in absent from this table is not gated, so a subset license that does not include LINEST still evaluates it. FORECAST.LINEAR, INTERCEPT, TREND, GROWTH, and LOGEST were added to the same list.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 130c9dc. Configure here.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Performance comparison of head (130c9dc) vs base (eff7928)

                                     testName |    base |    head |  change
---------------------------------------------------------------------------
                                      Sheet A |  389.26 |  399.01 |  +2.50%
                                      Sheet B |  126.03 |  133.63 |  +6.03%
                                      Sheet T |  110.44 |   115.4 |  +4.49%
                                Column ranges |  416.47 |  423.77 |  +1.75%
                                Sorted lookup | 12401.4 | 12555.4 |  +1.24%
Sheet A:  change value, add/remove row/column |   12.87 |   15.01 | +16.63%
 Sheet B: change value, add/remove row/column |  115.92 |  135.06 | +16.51%
                   Column ranges - add column |  129.12 |  142.23 | +10.15%
                Column ranges - without batch |  423.25 |  425.45 |  +0.52%
                        Column ranges - batch |  108.49 |   102.1 |  -5.89%

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant