From 55699e2eacf6ff0b36a609eb93cb511c464b8080 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 5 Oct 2026 12:36:44 +0000 Subject: [PATCH 1/3] Add RANK.AVG function (HF-420) Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/idID.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + .../categories/statistical.ts | 7 ++++ src/interpreter/plugin/MedianPlugin.ts | 34 +++++++++++++++++++ 20 files changed, 59 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 787c642ad9..d3ba442d53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Added a new function: RANK.AVG. - Added 12 database functions: DCOUNT, DSUM, DAVERAGE, DMAX, DMIN, DGET, DPRODUCT, DCOUNTA, DSTDEV, DSTDEVP, DVAR, DVARP. [#1652](https://github.com/handsontable/hyperformula/pull/1652) - Added new functions: PERCENTILE, PERCENTILE.INC, PERCENTILE.EXC, QUARTILE, QUARTILE.INC, QUARTILE.EXC. [#1650](https://github.com/handsontable/hyperformula/pull/1650) - Added `maxPendingLazyTransformations` configuration option to control memory usage by limiting accumulated transformations before cleanup. [#1629](https://github.com/handsontable/hyperformula/issues/1629) diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index fea286083c..90a2fa78b8 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTIL', 'PERCENTILE.INC': 'PERCENTIL.INC', 'PERCENTILE.EXC': 'PERCENTIL.EXC', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'QUARTIL', 'QUARTILE.INC': 'QUARTIL.INC', 'QUARTILE.EXC': 'QUARTIL.EXC', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 9264551249..ca60bd1603 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'FRAKTIL', 'PERCENTILE.INC': 'FRAKTIL.MEDTAG', 'PERCENTILE.EXC': 'FRAKTIL.UDELAD', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'KVARTIL', 'QUARTILE.INC': 'KVARTIL.MEDTAG', 'QUARTILE.EXC': 'KVARTIL.UDELAD', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 4d3d39319b..723e5b8a3e 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'QUANTIL', 'PERCENTILE.INC': 'QUANTIL.INKL', 'PERCENTILE.EXC': 'QUANTIL.EXKL', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'QUARTILE', 'QUARTILE.INC': 'QUARTILE.INKL', 'QUARTILE.EXC': 'QUARTILE.EXKL', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index d271b732cd..3367c5b02c 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTILE', 'PERCENTILE.INC': 'PERCENTILE.INC', 'PERCENTILE.EXC': 'PERCENTILE.EXC', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'QUARTILE', 'QUARTILE.INC': 'QUARTILE.INC', 'QUARTILE.EXC': 'QUARTILE.EXC', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 9c6b817b98..f92ff6648e 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -390,6 +390,7 @@ export const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTIL', 'PERCENTILE.INC': 'PERCENTIL.INC', 'PERCENTILE.EXC': 'PERCENTIL.EXC', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'CUARTIL', 'QUARTILE.INC': 'CUARTIL.INC', 'QUARTILE.EXC': 'CUARTIL.EXC', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 99ff05708f..ddc11ef4c7 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PROSENTTIPISTE', 'PERCENTILE.INC': 'PROSENTTIPISTE.SIS', 'PERCENTILE.EXC': 'PROSENTTIPISTE.ULK', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'NELJÄNNES', 'QUARTILE.INC': 'NELJÄNNES.SIS', 'QUARTILE.EXC': 'NELJÄNNES.ULK', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index f7b9be45f7..a534a2380d 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'CENTILE', 'PERCENTILE.INC': 'CENTILE.INCLURE', 'PERCENTILE.EXC': 'CENTILE.EXCLURE', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'QUARTILE', 'QUARTILE.INC': 'QUARTILE.INCLURE', 'QUARTILE.EXC': 'QUARTILE.EXCLURE', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index e40219553a..8dd6491e24 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTILIS', 'PERCENTILE.INC': 'PERCENTILIS.TARTALMAZ', 'PERCENTILE.EXC': 'PERCENTILIS.KIZÁR', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'KVARTILIS', 'QUARTILE.INC': 'KVARTILIS.TARTALMAZ', 'QUARTILE.EXC': 'KVARTILIS.KIZÁR', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index 719cb11db6..4d3ab417ff 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTILE', 'PERCENTILE.INC': 'PERCENTILE.INC', 'PERCENTILE.EXC': 'PERCENTILE.EXC', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'QUARTILE', 'QUARTILE.INC': 'QUARTILE.INC', 'QUARTILE.EXC': 'QUARTILE.EXC', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 1a494628f8..2184f10321 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTILE', 'PERCENTILE.INC': 'INC.PERCENTILE', 'PERCENTILE.EXC': 'ESC.PERCENTILE', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'QUARTILE', 'QUARTILE.INC': 'INC.QUARTILE', 'QUARTILE.EXC': 'ESC.QUARTILE', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index ff15415a98..f2a340b5e4 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERSENTIL', 'PERCENTILE.INC': 'PERSENTIL.INK', 'PERCENTILE.EXC': 'PERSENTIL.EKS', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'KVARTIL', 'QUARTILE.INC': 'KVARTIL.INK', 'QUARTILE.EXC': 'KVARTIL.EKS', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index c491e2f44b..e4e693493b 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTIEL', 'PERCENTILE.INC': 'PERCENTIEL.INC', 'PERCENTILE.EXC': 'PERCENTIEL.EXC', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'KWARTIEL', 'QUARTILE.INC': 'KWARTIEL.INC', 'QUARTILE.EXC': 'KWARTIEL.EXC', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 6fa4f016b4..6aebc64b1e 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTYL', 'PERCENTILE.INC': 'PERCENTYL.PRZEDZ.ZAMK', 'PERCENTILE.EXC': 'PERCENTYL.PRZEDZ.OTW', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'KWARTYL', 'QUARTILE.INC': 'KWARTYL.PRZEDZ.ZAMK', 'QUARTILE.EXC': 'KWARTYL.PRZEDZ.OTW', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 6c1e68c328..ce9e4ee96b 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTIL', 'PERCENTILE.INC': 'PERCENTIL.INC', 'PERCENTILE.EXC': 'PERCENTIL.EXC', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'QUARTIL', 'QUARTILE.INC': 'QUARTIL.INC', 'QUARTILE.EXC': 'QUARTIL.EXC', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 6b35c1ce2f..2ba613c484 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'ПЕРСЕНТИЛЬ', 'PERCENTILE.INC': 'ПРОЦЕНТИЛЬ.ВКЛ', 'PERCENTILE.EXC': 'ПРОЦЕНТИЛЬ.ИСКЛ', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'КВАРТИЛЬ', 'QUARTILE.INC': 'КВАРТИЛЬ.ВКЛ', 'QUARTILE.EXC': 'КВАРТИЛЬ.ИСКЛ', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 5f87580087..1f4e5a1019 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'PERCENTIL', 'PERCENTILE.INC': 'PERCENTIL.INK', 'PERCENTILE.EXC': 'PERCENTIL.EXK', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'KVARTIL', 'QUARTILE.INC': 'KVARTIL.INK', 'QUARTILE.EXC': 'KVARTIL.EXK', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 93c2176d37..5eca4325c2 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -390,6 +390,7 @@ const dictionary: RawTranslationPackage = { PERCENTILE: 'YÜZDEBİRLİK', 'PERCENTILE.INC': 'YÜZDEBİRLİK.DHL', 'PERCENTILE.EXC': 'YÜZDEBİRLİK.HRC', + 'RANK.AVG': 'RANK.AVG', QUARTILE: 'DÖRTTEBİRLİK', 'QUARTILE.INC': 'DÖRTTEBİRLİK.DHL', 'QUARTILE.EXC': 'DÖRTTEBİRLİK.HRC', diff --git a/src/interpreter/functionMetadata/categories/statistical.ts b/src/interpreter/functionMetadata/categories/statistical.ts index 1752c3eaf6..b3014c35ae 100644 --- a/src/interpreter/functionMetadata/categories/statistical.ts +++ b/src/interpreter/functionMetadata/categories/statistical.ts @@ -430,6 +430,13 @@ export const STATISTICAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=POISSON.DIST(3, 5, FALSE())', '=POISSON.DIST(3, 5, TRUE())'], }, + 'RANK.AVG': { + category: 'Statistical', + shortDescription: 'Returns the rank of a number in a range of numbers; tied values get the average of their ranks.', + parameters: [{name: 'number', description: 'The number whose rank to find.'}, {name: 'ref', description: 'The range of numbers to rank within; non-numeric cells are ignored.'}, {name: 'order', description: 'Optional. 0 or omitted ranks in descending order; any other number ranks in ascending order.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=RANK.AVG(A2, A1:A10)', '=RANK.AVG(A2, A1:A10, 1)'], + }, 'QUARTILE.EXC': { category: 'Statistical', shortDescription: 'Returns the quartile of a data set, based on exclusive percentile values.', diff --git a/src/interpreter/plugin/MedianPlugin.ts b/src/interpreter/plugin/MedianPlugin.ts index a6a2a1078e..d77c798135 100644 --- a/src/interpreter/plugin/MedianPlugin.ts +++ b/src/interpreter/plugin/MedianPlugin.ts @@ -38,6 +38,14 @@ export class MedianPlugin extends FunctionPlugin implements FunctionPluginTypech {argumentType: FunctionArgumentType.NUMBER, minValue: 1}, ], }, + 'RANK.AVG': { + method: 'rankAvg', + parameters: [ + {argumentType: FunctionArgumentType.NUMBER}, + {argumentType: FunctionArgumentType.RANGE}, + {argumentType: FunctionArgumentType.NUMBER, optionalArg: true, defaultValue: 0}, + ], + }, } /** @@ -100,4 +108,30 @@ export class MedianPlugin extends FunctionPlugin implements FunctionPluginTypech } ) } + + /** + * Corresponds to RANK.AVG(number, ref, [order]). + * + * Returns the rank of a number in a range of numbers. Tied values get the average of the ranks they span. + * The rank is descending when order is 0 or omitted, and ascending for any other order. + * + * @param ast + * @param state + */ + public rankAvg(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('RANK.AVG'), + (value: number, range: SimpleRangeValue, order: number) => { + const vals = this.arithmeticHelper.manyToExactNumbers(range.valuesFromTopLeftCorner()) + if (vals instanceof CellError) { + return vals + } + const ties = vals.filter(val => val === value).length + if (ties === 0) { + return new CellError(ErrorType.NA, ErrorMessage.ValueNotFound) + } + const ranksBefore = vals.filter(val => order === 0 ? val > value : val < value).length + return ranksBefore + (ties + 1) / 2 + } + ) + } } From 2423880c2609199a2dcec8b1465955fefab49fc5 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 5 Oct 2026 12:49:32 +0000 Subject: [PATCH 2/3] docs: link the PR in the CHANGELOG entry Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3ba442d53..fcc51349f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- Added a new function: RANK.AVG. +- Added a new function: RANK.AVG. [#1787](https://github.com/handsontable/hyperformula/pull/1787) - Added 12 database functions: DCOUNT, DSUM, DAVERAGE, DMAX, DMIN, DGET, DPRODUCT, DCOUNTA, DSTDEV, DSTDEVP, DVAR, DVARP. [#1652](https://github.com/handsontable/hyperformula/pull/1652) - Added new functions: PERCENTILE, PERCENTILE.INC, PERCENTILE.EXC, QUARTILE, QUARTILE.INC, QUARTILE.EXC. [#1650](https://github.com/handsontable/hyperformula/pull/1650) - Added `maxPendingLazyTransformations` configuration option to control memory usage by limiting accumulated transformations before cleanup. [#1629](https://github.com/handsontable/hyperformula/issues/1629) From 905872a962dd341234fbefdeefa3e9ad0c241c0c Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Thu, 8 Oct 2026 03:53:42 +0000 Subject: [PATCH 3/3] Register RANK.AVG in the license capability table and fix its changelog placement Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 +- src/license/functionCapabilities.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcc51349f6..abc626afde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Added support for the new license key format. A proprietary key can now grant a subset of the library: a function your key does not include evaluates to a `#LIC!` error, and the matching parts of the API throw a `LicenseCapabilityMissingError`. `getAvailableFunctions()` and `getFunctionDetails()` describe only the functions your key includes. [#1728](https://github.com/handsontable/hyperformula/pull/1728) +- Added a new function: RANK.AVG. [#1787](https://github.com/handsontable/hyperformula/pull/1787) ### Changed @@ -47,7 +48,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- Added a new function: RANK.AVG. [#1787](https://github.com/handsontable/hyperformula/pull/1787) - Added 12 database functions: DCOUNT, DSUM, DAVERAGE, DMAX, DMIN, DGET, DPRODUCT, DCOUNTA, DSTDEV, DSTDEVP, DVAR, DVARP. [#1652](https://github.com/handsontable/hyperformula/pull/1652) - Added new functions: PERCENTILE, PERCENTILE.INC, PERCENTILE.EXC, QUARTILE, QUARTILE.INC, QUARTILE.EXC. [#1650](https://github.com/handsontable/hyperformula/pull/1650) - Added `maxPendingLazyTransformations` configuration option to control memory usage by limiting accumulated transformations before cleanup. [#1629](https://github.com/handsontable/hyperformula/issues/1629) diff --git a/src/license/functionCapabilities.ts b/src/license/functionCapabilities.ts index a26733ec5f..62fcf55d80 100644 --- a/src/license/functionCapabilities.ts +++ b/src/license/functionCapabilities.ts @@ -92,7 +92,7 @@ const UNGROUPED_FUNCTIONS = [ 'ISREF', 'LCM', 'LOG10', 'LOGNORM.DIST', 'LOGNORM.INV', 'MAXA', 'MAXPOOL', 'MEDIANPOOL', 'MINA', 'MIRR', 'MMULT', 'MULTINOMIAL', 'NEGBINOM.DIST', 'NETWORKDAYS.INTL', 'NOMINAL', 'NORM.DIST', 'NORM.INV', 'NORM.S.DIST', 'NORM.S.INV', 'NPER', 'OCT2BIN', 'OCT2DEC', 'OCT2HEX', 'PDURATION', 'PERCENTILE.EXC', - 'PHI', 'POISSON.DIST', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'ROMAN', 'RRI', 'RSQ', 'SEC', 'SECH', + 'PHI', 'POISSON.DIST', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'RANK.AVG', 'ROMAN', 'RRI', 'RSQ', 'SEC', 'SECH', 'SERIESSUM', 'SHEET', 'SHEETS', 'SINH', 'SKEW', 'SKEW.P', 'SLOPE', 'SPLIT', 'SQRTPI', 'STANDARDIZE', 'STEYX', 'SUMX2MY2', 'SUMX2PY2', 'SYD', 'T.DIST', 'T.DIST.2T', 'T.DIST.RT', 'T.INV', 'T.INV.2T', 'T.TEST', 'TANH', 'TBILLEQ', 'TBILLPRICE', 'TBILLYIELD', 'TDIST', 'TIMEVALUE', 'UNICODE', 'VARA', 'VARPA',