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
| Language | Name in Excel |
|---|---|
| Arabic | TRIMRANGE |
| Bulgarian | TRIMRANGE |
| Czech | OŘÍZNOUTOBLAST |
| Danish | TRIMOMRÅDE |
| German | TRIMRANGE |
| Greek | TRIMRANGE |
| English (Australia) | TRIMRANGE |
| English (United Kingdom) | TRIMRANGE |
| English (United States) | TRIMRANGE |
| Spanish | RECORTAR.RANGO |
| Estonian | TRIMRANGE |
| Finnish | RAJAAALUE |
| French | SUPPR.PLAGE |
| Hebrew | TRIMRANGE |
| Croatian | TRIMRANGE |
| Hungarian | KIMETSZÉSITARTOMÁNY |
| Indonesian | TRIMRANGE |
| Italian | TRIMRANGE |
| Japanese | TRIMRANGE |
| Korean | TRIMRANGE |
| Lithuanian | TRIMRANGE |
| Latvian | TRIMRANGE |
| Norwegian | TRIMRANGE |
| Dutch | TRIMBEREIK |
| Polish | PRZYTNIJZAKRES |
| Portuguese (Brazil) | TRIMRANGE |
| Portuguese (Portugal) | APARARINTERVALO |
| Romanian | TRIMRANGE |
| Russian | УРЕЗДИАПАЗОН |
| Slovak | TRIMRANGE |
| Slovenian | TRIMRANGE |
| Serbian (Latin) | TRIMRANGE |
| Swedish | KLIPPOMRÅDE |
| Thai | TRIMRANGE |
| Turkish | KIRPARALIĞI |
| Ukrainian | TRIMRANGE |
| Vietnamese | TRIMRANGE |
| 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]):
| Argument | Documented default | Meaning |
|---|---|---|
range | required | The range (or array) to be trimmed |
trim_rows | 3 | 0 none · 1 leading blank rows · 2 trailing blank rows · 3 both |
trim_cols | 3 | 0 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
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
1and2, 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
TRIMRANGEwith 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 toTRIMRANGE, 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, notO(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 GitHubCatalogueEntry {
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
spancatalogue/offset
Argument and result plumbing
Parameter::valuecatalogueParameter::value_or_referencecatalogue
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_TRAILINGandFUNC.OP_TRIM_REF_BOTH(see the call pipeline, "Operators are functions").A.:Aand its relatives are the terse spelling ofTRIMRANGE(A:A, …). OFFSETwithCOUNTAis the idiomTRIMRANGEreplaces, and it is volatile.INDEX-built ranges (A1:INDEX(A:A, n)) are the non-volatile pre-TRIMRANGEidiom.TOCOL(…, 1)removes all blanks and flattens;TRIMRANGEremoves only the margin and keeps the rectangle.FILTERremoves rows by predicate.ROWS/COUNTAare 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