Lookup and reference functions

TRIMRANGE

Matches all 2 recorded answers

Recorded: 2 workbook cells. How we measure this · See the Rust code

Local names in 40 languages
LanguageName in Excel
ArabicTRIMRANGE
BulgarianTRIMRANGE
CzechOŘÍZNOUTOBLAST
DanishTRIMOMRÅDE
GermanTRIMRANGE
GreekTRIMRANGE
English (Australia)TRIMRANGE
English (United Kingdom)TRIMRANGE
English (United States)TRIMRANGE
SpanishRECORTAR.RANGO
EstonianTRIMRANGE
FinnishRAJAAALUE
FrenchSUPPR.PLAGE
HebrewTRIMRANGE
CroatianTRIMRANGE
HungarianKIMETSZÉSITARTOMÁNY
IndonesianTRIMRANGE
ItalianTRIMRANGE
JapaneseTRIMRANGE
KoreanTRIMRANGE
LithuanianTRIMRANGE
LatvianTRIMRANGE
NorwegianTRIMRANGE
DutchTRIMBEREIK
PolishPRZYTNIJZAKRES
Portuguese (Brazil)TRIMRANGE
Portuguese (Portugal)APARARINTERVALO
RomanianTRIMRANGE
RussianУРЕЗДИАПАЗОН
SlovakTRIMRANGE
SlovenianTRIMRANGE
Serbian (Latin)TRIMRANGE
SwedishKLIPPOMRÅDE
ThaiTRIMRANGE
TurkishKIRPARALIĞI
UkrainianTRIMRANGE
VietnameseTRIMRANGE
Chinese (Simplified)TRIMRANGE
Chinese (Hong Kong)TRIMRANGE
Chinese (Traditional)TRIMRANGE

Names from Microsoft's support site.

What it does

TRIMRANGE(range, [trim_rows], [trim_cols])

TRIMRANGE removes blank rows and columns from the edges of a range or array and returns what is left.

The operation scans inward from each edge until it meets a non-blank cell, then keeps the rectangle bounded by those four positions. Interior blanks are untouched — a blank row in the middle of populated data stays exactly where it is. TRIMRANGE is a margin operation, not a compaction; anything that removes interior blanks (TOCOL(…, 1), FILTER) is a different function.

Which edges are trimmed is controlled per axis: rows and columns have independent settings for whether leading blanks, trailing blanks, both, or neither are removed.

Arguments

Microsoft documents TRIMRANGE(range, [trim_rows], [trim_cols]):

ArgumentDocumented defaultMeaning
rangerequiredThe range (or array) to be trimmed
trim_rows30 none · 1 leading blank rows · 2 trailing blank rows · 3 both
trim_cols30 none · 1 leading blank cols · 2 trailing blank cols · 3 both

So the documented default trims blank margins on all four sides.

The argument most likely to be misread is the code itself, because 1 and 2 are not symmetric-looking and there is nothing in the call site to remind you which is which. 0 and 3 are safe; the intermediate values are worth spelling out in a comment.

Try it

Input cells (optional)

Example: {"B2": 3, "B3": 4}. A1 holds the formula. Other cells need values before a formula can read them.

Enter a formula, then calculate.

Runs locally in your browser with OneCalc. Its answer may differ from Excel. Use English function names and commas between arguments.

Quirks

A documented divergence

Microsoft's page, retrieved for this entry, documents three arguments, 1 meaning leading, 2 meaning trailing, and a default of 3.

The Handbook does not resolve this here, and does not silently prefer either side. What can be said honestly:

  • The two accounts disagree about the default, about the meaning of the codes 1 and 2, and about the existence of a fourth argument.
  • Nobody has run the experiment that would settle it. It is a single afternoon's work: enter TRIMRANGE with each code in each position against a range with known leading and trailing blanks, and record what comes back.

Until that happens, treat Microsoft's table as the documented behaviour and treat any implementation's code mapping as an implementation detail to verify rather than to trust.

Result and edge cases

The return kind is an array containing the trimmed contents.

  • What counts as blank is the pivotal question, exactly as for TOCOL: an empty cell certainly, but a cell holding "", or a formula returning "", is a different matter. A column of formulas that return "" below the data is the single most common real input to TRIMRANGE, and the documentation does not say what happens to it.
  • Interior blanks survive, always.
  • Dynamic-array publication and #SPILL! are host-side adaptation.

Errors

The Handbook therefore states the error surface as unsettled: there is a declared implementation mapping and no documented counterpart, and the two have not been compared.

How to implement

Notes for implementers

  • Trimming is per axis and per edge: four independent decisions driven by two codes. Scanning in from each edge is the natural implementation and is O(m + n) in the populated case, not O(mn) — worth getting right, because whole-column references are the intended input.
  • Interior blanks must survive. A compaction pass is a different function.
  • The blank predicate must be stated and shared with the rest of the engine.
  • The code table and default are exactly the place to not guess. Whatever mapping is implemented should be traceable to a source, and the divergence recorded above should be resolved by observation before either mapping is trusted.

Rust code

Show the CalcFunc source of this function

Quoted from the public repository DnaCalc/DnaCalc, crates calc-func and calc-fml, at commit 9e1884369a67. Comments are the source's own. A kernel shared with other functions appears once, on its module page.

TRIMRANGE · 13 lines · calc-func/src/catalogue/offset.rs on GitHub
CatalogueEntry {
    id: TRIMRANGE,
    spelling: "TRIMRANGE",
    notation: Notation::Call,
    arity: CallableArityShape::range(1, 3),
    parameters: &[
        Parameter::value_or_reference("range"),
        Parameter::value("trim_rows"),
        Parameter::value("trim_cols"),
    ],
    demand: ArgumentDemand::Eager,
    kernel: Kernel::Eager(trimrange),
}
trimrange · 85 lines · calc-func/src/catalogue/offset.rs on GitHub
/// TRIMRANGE(range, [trim_rows], [trim_cols]) (dc-h9u): the reference to `range` without its
/// leading and trailing blank rows and columns. Each trim argument, truncated, is 0 (none), 1
/// (leading), 2 (trailing) or 3 (both, the default); any other is #VALUE!. A blank cell is one
/// with no value (empty text is a value). A range with no value is #REF!. The trim references
/// `A1:.B9` (trailing), `A1.:B9` (leading) and `A1.:.B9` (both) call it with both arguments the
/// same (calc-fml).
fn trimrange(
    arguments: &[CalcValue],
    services: &mut EvalServices<'_>,
) -> Result<CalcValue, FunctionFault> {
    let site = Site::new(TRIMRANGE, services);
    let step = (|| {
        site.scalar_operands(&arguments[1..])?;
        if let CoreRef::Error(code) = arguments[0].core_ref() {
            return Err(Exit::Error(code));
        }
        let mode = |index: usize| -> Step<u8> {
            match arguments.get(index) {
                None => Ok(3),
                Some(value) if value.is_missing() => Ok(3),
                // Only 0 to 3 exactly: 2.9 is #VALUE! (Excel 16.0.20527).
                Some(value) => match site.number_argument(index, value)? {
                    n if (0.0..=3.0).contains(&n) && n.fract() == 0.0 => Ok(n as u8),
                    _ => Err(Exit::Error(WorksheetErrorCode::Value)),
                },
            }
        };
        let (rows, cols) = (mode(1)?, mode(2)?);
        // The retained unswept scalar controls keep their number, text or logical value.
        if matches!(arguments[0].core_ref(), CoreRef::Number(_) | CoreRef::Text(_) | CoreRef::Logical(_)) {
            return Ok(Err(Ok(arguments[0].clone())));
        }
        if let CoreRef::Array(array) = arguments[0].core_ref() {
            return Ok(Err(trim_array(array, rows, cols)));
        }
        let Some(reference) = arguments[0].as_reference() else {
            return Err(Exit::Error(WorksheetErrorCode::Value));
        };
        let [area] = reference.areas() else {
            return Err(Exit::Error(WorksheetErrorCode::Value));
        };
        Ok(Ok((*area, rows, cols)))
    })();
    let (area, rows, cols) = match step {
        Ok(Ok(found)) => found,
        Ok(Err(trimmed)) => return Site::finish(trimmed),
        Err(exit) => return Site::finish(Err(exit)),
    };
    // The rows and columns of the cells that hold a value.
    let mut bounds: Option<(u32, u32, u32, u32)> = None;
    let read = match services.cells() {
        Some(cells) => cells.read_area(area, &mut |key, value| {
            if !matches!(value.core_ref(), CoreRef::Empty | CoreRef::Missing) {
                let (r, c) = (key.row(), key.column());
                bounds = Some(match bounds {
                    None => (r, r, c, c),
                    Some((t, b, l, rt)) => (t.min(r), b.max(r), l.min(c), rt.max(c)),
                });
            }
            ControlFlow::Continue(())
        }),
        None => Err(crate::call::Unreadable),
    };
    if read.is_err() {
        return Err(FunctionFault::ReferenceUnreadable {
            id: TRIMRANGE,
            index: 0,
        });
    }
    let Some((top, bottom, left, right)) = bounds else {
        return Site::finish(Err(Exit::Error(WorksheetErrorCode::Ref)));
    };
    let (first, last) = (area.first(), area.last());
    let keep = |mode: u8, low: u32, high: u32, min: u32, max: u32| -> (u32, u32) {
        let lead = matches!(mode, 1 | 3);
        let trail = matches!(mode, 2 | 3);
        (if lead { min } else { low }, if trail { max } else { high })
    };
    let (r0, r1) = keep(rows, first.row(), last.row(), top, bottom);
    let (c0, c1) = keep(cols, first.column(), last.column(), left, right);
    let sheet = area.sheet();
    let corner = |row: u32, column: u32| CellKey::new(sheet, row, column).expect("inside the area");
    let trimmed = GridArea::new(corner(r0, c0), corner(r1, c1)).expect("one sheet");
    Site::finish(Ok(CalcValue::reference(ReferenceLike::area(trimmed))))
}
trim_array · 43 lines · calc-func/src/catalogue/offset.rs on GitHub
/// TRIMRANGE of an array: its leading and trailing rows and columns with no value left out, as
/// a range's are (Excel 16.0.20527: `ROWS(TRIMRANGE({1,2},1))` is 1); an array with no value is
/// #REF!.
fn trim_array(array: &CalcArray, rows: u8, cols: u8) -> Step<CalcValue> {
    let shape = array.shape();
    let held = |r: usize, c: usize| {
        array
            .get(r, c)
            .is_some_and(|v| !matches!(v.core_ref(), CoreRef::Empty | CoreRef::Missing))
    };
    let row_held: Vec<usize> = (0..shape.rows)
        .filter(|&r| (0..shape.cols).any(|c| held(r, c)))
        .collect();
    let col_held: Vec<usize> = (0..shape.cols)
        .filter(|&c| (0..shape.rows).any(|r| held(r, c)))
        .collect();
    let (Some(&top), Some(&bottom), Some(&left), Some(&right)) = (
        row_held.first(),
        row_held.last(),
        col_held.first(),
        col_held.last(),
    ) else {
        return Err(Exit::Error(WorksheetErrorCode::Ref));
    };
    let span = |mode: u8, low: usize, high: usize, first: usize, last: usize| {
        (
            if matches!(mode, 1 | 3) { first } else { low },
            if matches!(mode, 2 | 3) { last } else { high },
        )
    };
    let (r0, r1) = span(rows, 0, shape.rows - 1, top, bottom);
    let (c0, c1) = span(cols, 0, shape.cols - 1, left, right);
    let out: Vec<Vec<CalcValue>> = (r0..=r1)
        .map(|r| {
            (c0..=c1)
                .map(|c| array.get(r, c).cloned().unwrap_or_else(CalcValue::empty))
                .collect()
        })
        .collect();
    Ok(CalcValue::array(
        CalcArray::from_rows(out).expect("a rectangular array"),
    ))
}

Kernels shared with other functions

  • span catalogue/offset

Argument and result plumbing

No implementation has been published with a Handbook test suite for this function.

Version differences

No comparison between specific Excel builds is recorded here yet. This does not establish that all builds behave alike.

Alternatives

Relationships

  • The trim-reference operators are the operator form of the same idea, and are modeled in this Handbook as functions: FUNC.OP_TRIM_REF_LEADING, FUNC.OP_TRIM_REF_TRAILING and FUNC.OP_TRIM_REF_BOTH (see the call pipeline, "Operators are functions"). A.:A and its relatives are the terse spelling of TRIMRANGE(A:A, …).
  • OFFSET with COUNTA is the idiom TRIMRANGE replaces, and it is volatile.
  • INDEX-built ranges (A1:INDEX(A:A, n)) are the non-volatile pre-TRIMRANGE idiom.
  • TOCOL(…, 1) removes all blanks and flattens; TRIMRANGE removes only the margin and keeps the rectangle.
  • FILTER removes rows by predicate.
  • ROWS/COUNTA are what people reach for when they want the size of the populated region rather than its contents.

Named variants

No named variants are recorded yet.

Suggest a correction or contribute an implementation · For agents