@@ -235,6 +235,35 @@ function evaluateFormulaFunction(name: string, args: string[], context: Conditio
235235 if ( normalizedName === "COUNT" ) {
236236 return formulaNumericArgs ( args , context ) . length ;
237237 }
238+ if ( normalizedName === "COUNTA" ) {
239+ return formulaRawArgs ( args , context ) . filter ( ( value ) => asString ( value ) . trim ( ) . length > 0 ) . length ;
240+ }
241+ if ( normalizedName === "COUNTBLANK" ) {
242+ return countBlankFormulaValues ( args , context ) ;
243+ }
244+ if ( normalizedName === "MEDIAN" ) {
245+ return medianFormulaNumber ( formulaNumericArgs ( args , context ) ) ;
246+ }
247+ if ( normalizedName === "LARGE" || normalizedName === "SMALL" ) {
248+ return rankedFormulaNumber (
249+ formulaNumericArgs ( [ args [ 0 ] ?? "" ] , context ) ,
250+ Number ( evaluateFormulaValue ( args [ 1 ] ?? "1" , context ) ) ,
251+ normalizedName ,
252+ ) ;
253+ }
254+ if ( normalizedName === "RANK" || normalizedName === "RANK.EQ" ) {
255+ return rankFormulaNumber (
256+ Number ( evaluateFormulaValue ( args [ 0 ] ?? "" , context ) ) ,
257+ formulaNumericArgs ( [ args [ 1 ] ?? "" ] , context ) ,
258+ Number ( evaluateFormulaValue ( args [ 2 ] ?? "0" , context ) ) ,
259+ ) ;
260+ }
261+ if ( normalizedName === "PERCENTILE" || normalizedName === "PERCENTILE.INC" ) {
262+ return percentileFormulaNumber (
263+ formulaNumericArgs ( [ args [ 0 ] ?? "" ] , context ) ,
264+ Number ( evaluateFormulaValue ( args [ 1 ] ?? "" , context ) ) ,
265+ ) ;
266+ }
238267 if ( normalizedName === "COUNTIF" ) {
239268 return countIf ( args , context ) ;
240269 }
@@ -363,6 +392,20 @@ function formulaNumericArgs(args: string[], context: ConditionalFormulaContext):
363392 return values ;
364393}
365394
395+ function formulaRawArgs ( args : string [ ] , context : ConditionalFormulaContext ) : unknown [ ] {
396+ const values : unknown [ ] = [ ] ;
397+ for ( const arg of args ) {
398+ const rangeValues = formulaRangeValues ( arg , context ) ;
399+ if ( rangeValues ) {
400+ values . push ( ...rangeValues ) ;
401+ continue ;
402+ }
403+
404+ values . push ( evaluateFormulaValue ( arg , context ) ) ;
405+ }
406+ return values ;
407+ }
408+
366409function formulaRangeValues ( expression : string , context : ConditionalFormulaContext ) : string [ ] | null {
367410 const cells = formulaRangeCells ( expression , context ) ;
368411 return cells ? cells . map ( ( cell ) => cell . value ) : null ;
@@ -416,6 +459,53 @@ function countIfs(args: string[], context: ConditionalFormulaContext): number {
416459 } ) . length ;
417460}
418461
462+ function countBlankFormulaValues ( args : string [ ] , context : ConditionalFormulaContext ) : number {
463+ let count = 0 ;
464+ for ( const arg of args ) {
465+ const range = formulaRange ( arg , context ) ;
466+ const cells = formulaRangeCells ( arg , context ) ;
467+ if ( range && cells ) {
468+ const area = range . rowSpan * range . columnSpan ;
469+ const explicitBlankCount = cells . filter ( ( cell ) => cell . value . trim ( ) . length === 0 ) . length ;
470+ count += area <= 100_000 ? area - cells . filter ( ( cell ) => cell . value . trim ( ) . length > 0 ) . length : explicitBlankCount ;
471+ continue ;
472+ }
473+
474+ if ( asString ( evaluateFormulaValue ( arg , context ) ) . trim ( ) . length === 0 ) count += 1 ;
475+ }
476+ return count ;
477+ }
478+
479+ function medianFormulaNumber ( values : number [ ] ) : number {
480+ if ( values . length === 0 ) return Number . NaN ;
481+ const sorted = [ ...values ] . sort ( ( left , right ) => left - right ) ;
482+ const middle = Math . floor ( sorted . length / 2 ) ;
483+ return sorted . length % 2 === 1 ? sorted [ middle ] : ( sorted [ middle - 1 ] + sorted [ middle ] ) / 2 ;
484+ }
485+
486+ function rankedFormulaNumber ( values : number [ ] , rank : number , mode : string ) : number {
487+ const position = Math . trunc ( rank ) - 1 ;
488+ if ( values . length === 0 || ! Number . isFinite ( position ) || position < 0 || position >= values . length ) return Number . NaN ;
489+ const sorted = [ ...values ] . sort ( ( left , right ) => mode === "SMALL" ? left - right : right - left ) ;
490+ return sorted [ position ] ;
491+ }
492+
493+ function rankFormulaNumber ( value : number , values : number [ ] , order : number ) : number {
494+ if ( ! Number . isFinite ( value ) || values . length === 0 ) return Number . NaN ;
495+ const ascending = Number . isFinite ( order ) && order !== 0 ;
496+ return 1 + values . filter ( ( candidate ) => ascending ? candidate < value : candidate > value ) . length ;
497+ }
498+
499+ function percentileFormulaNumber ( values : number [ ] , percentile : number ) : number {
500+ if ( values . length === 0 || ! Number . isFinite ( percentile ) || percentile < 0 || percentile > 1 ) return Number . NaN ;
501+ const sorted = [ ...values ] . sort ( ( left , right ) => left - right ) ;
502+ const position = ( sorted . length - 1 ) * percentile ;
503+ const lower = Math . floor ( position ) ;
504+ const upper = Math . ceil ( position ) ;
505+ if ( lower === upper ) return sorted [ lower ] ;
506+ return sorted [ lower ] + ( sorted [ upper ] - sorted [ lower ] ) * ( position - lower ) ;
507+ }
508+
419509type FormulaCriteriaRange = {
420510 cellsByOffset : Map < string , string > ;
421511 criterion : { operator : string ; value : unknown } ;
0 commit comments