← All guides

LibreOffice collapses 41 documented error conditions into #VALUE!

Excel has five everyday error codes and uses them to mean different things. #NUM! is a number that is out of the function’s domain. #DIV/0! is a divisor of zero. #REF! is a reference that points nowhere. #N/A is a lookup that found nothing. #VALUE! is the wrong type of thing entirely.

LibreOffice Calc has all five codes too, and returns them correctly most of the time. But there is a large, systematic class of conditions where it returns #VALUE! and the documentation says something else. We have now executed enough of the corpus to put a number on it.

The count

Our corpus holds 954 executed cases across 299 functions. 108 of those cases have a documented result that is an error rather than a value. In LibreOffice Calc 25.8.7.3:

All 41 of the #VALUE! substitutions returned #VALUE! in every one of the four LibreOffice builds we keep pinned — 24.2.0.3, 24.8.7.2, 25.2.0.3 and 25.8.7.3. This is not a regression anyone is going to fix out from under you; it is how Calc reports invalid arguments.

Google Sheets, executed against the same cases, returned the documented error code on 40 of the 41. So this is not a two-against-one disagreement about what an error “should” be. On this particular axis LibreOffice is the outlier, and Sheets and Microsoft’s documentation agree with each other almost perfectly.

Executed: all 41 cases

Grouped by the error code Microsoft documents, then by function. The Excel column is Microsoft’s documented behaviour as recorded in our test corpus — it is the yardstick, and we do not run Excel. The Google Sheets column is executed output from dated Drive-import runs; rows marked † ran on 2026-08-31 and the rest on 2026-08-29. The LibreOffice column is Calc 25.8.7.3, and every row returned #VALUE! in the other three builds too.

FunctionFormulaInputsExcel (documented)Google Sheets (executed)LibreOffice Calc 25.8.7.3 (executed)
DATEDIF=DATEDIF(DATE(2024,1,10),DATE(2024,1,1),"D")#NUM!#NUM!#VALUE!
DGET=DGET(A1:B4,"Sales",D1:D2)A1 = "Region", A2 = "East", A3 = "West", A4 = "East", B1 = "Sales", B2 = 100, B3 = 200, B4 = 50, D1 = "Region", D2 = "East"#NUM!#NUM!#VALUE!
DOLLARDE=DOLLARDE(1.02,-4)#NUM!#NUM! †#VALUE!
DOLLARFR=DOLLARFR(1.125,-4)#NUM!#NUM! †#VALUE!
FLOOR=FLOOR(2.5,-2)#NUM!#NUM!#VALUE!
LARGE=LARGE(A1:A3,0)A1:A3 = 5, 3, 8#NUM!#NUM!#VALUE!
LARGE=LARGE(A1:A5,6)A1:A5 = 5, 3, 8, 1, 9#NUM!#NUM!#VALUE!
LN=LN(-5)#NUM!#NUM!#VALUE!
LN=LN(0)#NUM!#NUM!#VALUE!
LOG=LOG(-10)#NUM!#NUM!#VALUE!
LOG=LOG(0)#NUM!#NUM!#VALUE!
LOG10=LOG10(-5)#NUM!#NUM!#VALUE!
LOG10=LOG10(0)#NUM!#NUM!#VALUE!
NORM.DIST=NORM.DIST(42,40,0,TRUE)#NUM!#NUM! †#VALUE!
NORM.INV=NORM.INV(0,40,1.5)#NUM!#NUM! †#VALUE!
NORM.INV=NORM.INV(0.5,40,-1)#NUM!#NUM! †#VALUE!
NORM.S.INV=NORM.S.INV(-0.5)#NUM!#NUM! †#VALUE!
NORM.S.INV=NORM.S.INV(1)#NUM!#NUM! †#VALUE!
PDURATION=PDURATION(0,2000,2200)#NUM!#NUM! †#VALUE!
PDURATION=PDURATION(0.025,2000,-2200)#NUM!#NUM! †#VALUE!
PERCENTILE.EXC=PERCENTILE.EXC(A1:A10,0)A1:A10 = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10#NUM!#NUM!#VALUE!
PERCENTILE.EXC=PERCENTILE.EXC(A1:A10,1)A1:A10 = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10#NUM!#NUM!#VALUE!
PERCENTILE.INC=PERCENTILE.INC(A1:A3,1.5)A1:A3 = 1, 2, 3#NUM!#NUM!#VALUE!
QUARTILE.INC=QUARTILE.INC(A1:A3,5)A1:A3 = 1, 2, 3#NUM!#NUM!#VALUE!
RRI=RRI(0,10000,11000)#NUM!#DIV/0!#VALUE!
RRI=RRI(96,0,11000)#NUM!#NUM! †#VALUE!
SMALL=SMALL(A1:A3,0)A1:A3 = 5, 3, 8#NUM!#NUM!#VALUE!
SMALL=SMALL(A1:A5,6)A1:A5 = 5, 3, 8, 1, 9#NUM!#NUM!#VALUE!
SQRT=SQRT(-16)#NUM!#NUM!#VALUE!
VDB=VDB(2400,300,-10,0,1)#NUM!#NUM! †#VALUE!
WEEKDAY=WEEKDAY(DATE(2008,2,14),99)#NUM!#NUM!#VALUE!
YEARFRAC=YEARFRAC(DATE(2012,1,1),DATE(2012,7,30),9)#NUM!#NUM!#VALUE!
DOLLARDE=DOLLARDE(1.02,0)#DIV/0!#DIV/0! †#VALUE!
DOLLARFR=DOLLARFR(1.125,0)#DIV/0!#DIV/0! †#VALUE!
MIRR=MIRR(A1:A3,0.1,0.12)A1:A3 = 100, 200, 300#DIV/0!#DIV/0! †#VALUE!
SKEW=SKEW(A1:A4)A1:A4 = 5, 5, 5, 5#DIV/0!#DIV/0! †#VALUE!
HLOOKUP=HLOOKUP("a",A1:C2,5,FALSE)A1 = "a", A2 = 1, B1 = "b", B2 = 2, C1 = "c", C2 = 3#REF!#REF!#VALUE!
OFFSET=OFFSET(A1,-1,0)#REF!#REF!#VALUE!
VLOOKUP=VLOOKUP("a",A1:B3,5,FALSE)A1 = "a", A2 = "b", A3 = "c", B1 = 1, B2 = 2, B3 = 3#REF!#REF!#VALUE!
MODE=MODE(A1:A4)A1:A4 = 1, 2, 3, 4#N/A#N/A#VALUE!
MODE.SNGL=MODE.SNGL(1,2,3)#N/A#N/A#VALUE!

The one row where Google Sheets is not the documented answer

=RRI(0,10000,11000) is a three-way split, and it is the only one in the table. RRI computes the equivalent interest rate for the growth of an investment; Microsoft documents #NUM! when nper is zero. Google Sheets returned #DIV/0! on 2026-08-31. LibreOffice 25.8.7.3 returned #VALUE!. Three engines, three different codes for the same degenerate input.

Sheets’ answer is arguably the more honest description of what happened — the rate formula divides by nper — but it is not the documented one, and a branch keyed on either code will misfire in one engine or the other. The companion case =RRI(96,0,11000), a present value of zero, does return the documented #NUM! in Sheets, so this is a per-condition difference rather than a whole function behaving differently.

And the two rows that go the other way

LibreOffice is not simply funnelling every failure into #VALUE!. Two executed cases in the corpus return a non-matching error that is not #VALUE!, and one of them inverts the pattern on this page exactly:

FormulaExcel (documented)Google Sheets (executed)LibreOffice Calc 25.8.7.3 (executed)
=ISPMT(0.1,1,0,8000000)#DIV/0!#DIV/0! †#NUM!
=FILTER(A1:A3,B1:B3>100) with no matching rows#CALC!#N/A#N/A

ISPMT with nper = 0 is documented as #DIV/0!, is #DIV/0! in Sheets, and is #NUM! in LibreOffice — the exact reverse of the substitution the rest of this page is about. (Microsoft’s ISPMT page documents no error condition at all; the #DIV/0! in that row is derived from the interest formula the page does publish, which divides by nper, and our corpus marks it as derived rather than quoted.) So “LibreOffice always says #VALUE!” is a useful rule of thumb and a false statement; you cannot predict the code, you can only look it up. The FILTER row is a different story again, about an error code that LibreOffice does not have at all, and it has its own write-up: FILTER with no results returns #CALC! in Excel and #N/A in LibreOffice.

Why it happens

Internally LibreOffice maps a broad class of out-of-domain and invalid-argument conditions onto a single error state — err:502, “invalid argument” — which surfaces to the sheet as #VALUE!. Excel and Google Sheets keep the domain failure (#NUM!), the divisor failure (#DIV/0!), the reference failure (#REF!) and the not-found failure (#N/A) as distinct outcomes.

All three engines agree that these formulas are invalid. They disagree only on which word to print, and every valid input to the same functions matches exactly — =LN(1), =SQRT(16), =LARGE(A1:A5,2), =VLOOKUP("a",A1:B3,2,FALSE), Microsoft’s own documented MIRR, VDB, NORM.DIST and PDURATION examples. It is strictly the error path that diverges. That is what makes this easy to miss and expensive to hit: a workbook’s happy path travels perfectly, and only the edge-case branches change meaning.

What actually breaks: error-branch logic

Nothing on this page changes a number. What changes is which branch of an error handler fires.

IFERROR and ISERROR are safe. They catch every error code, so they behave identically before and after migration. =IFERROR(LN(x),0) returns 0 whether the inner failure was #NUM! or #VALUE!. If a workbook uses nothing but these two, this whole page is trivia.

ERROR.TYPE is where it bites. Microsoft documents ERROR.TYPE as returning 1 for #NULL!, 2 for #DIV/0!, 3 for #VALUE!, 4 for #REF!, 5 for #NAME?, 6 for #NUM! and 7 for #N/A. A formula written =IF(ERROR.TYPE(x)=6,"out of range",...) is keyed on #NUM!. Run it in LibreOffice against any of the 32 #NUM! rows above and the error arrives as #VALUE! — type 3 — so the branch silently never fires and the formula falls through to whatever the ELSE was. The code numbers themselves have their own divergences, which we cover in ERROR.TYPE codes across engines.

ISNA and IFNA are the sharpest case. Both MODE rows are documented as #N/A — the “no repeated value” condition — and both come back as #VALUE! in LibreOffice. A guard written =IF(ISNA(MODE(range)),"no mode",MODE(range)) works in Excel and Sheets and stops working in Calc, where the #VALUE! sails straight past ISNA and lands in the user’s face. Same for =IFNA(VLOOKUP(...),"not found") over the out-of-range-column case, which is documented #REF! and executes as #VALUE!.

Conditional formatting and data validation inherit the problem, because they are usually built on the same ISERROR/ISNA/ERROR.TYPE vocabulary. A red-highlight rule keyed on #N/A simply stops highlighting.

How to migrate safely

Search for the discriminating functions, not for the failing ones. The 29 functions in the table are not the risk — they fail in every engine. The risk is wherever a formula asks which error it got. Grep the workbook for ERROR.TYPE, ISNA(, IFNA(, and ISERR( (which excludes #N/A and so is also code-sensitive). Those hits are the migration work; everything else is noise.

Rewrite discriminating guards as broad ones where the distinction does not earn its keep. Most ISNA wrappers exist to mean “if this did not work, show a dash”, not to distinguish a missing lookup from a bad argument. IFERROR expresses that and is portable.

Where the distinction genuinely matters, test the input instead of the error. If a model really needs to tell “out of range” apart from “wrong type”, check the condition before the call — =IF(k>COUNT(range),"out of range",LARGE(range,k)) — rather than inspecting the wreckage afterwards. That is portable by construction, and it is also the only approach that survives an engine changing its mind later.

Do not build on the substitution. It would be tempting to key a LibreOffice-targeted workbook on ERROR.TYPE(x)=3 now that you know the code. Two of the cases on this site show why that is a bad bet: ISPMT already goes the other way, and =SORT(A1:A1) over an empty cell returned 0, then #VALUE!, then 0 again across three consecutive LibreOffice releases. Error identity is the least stable thing in a spreadsheet engine.

Related pages on this site

This page is the systematic count. Several individual slices of it have their own write-ups with more detail on the function in question:

Honest limits

The counts on this page are counts of our corpus, not of Excel. 954 cases over 299 functions is a lot of ground but it is not every function and it is nowhere near every input. A function with no error-condition case in our set contributes nothing to the 41, and its absence is not evidence of good behaviour.

The Excel column is Microsoft’s documented behaviour as recorded per case in our test corpus, and the ERROR.TYPE code numbers quoted above are documented too. We do not run Excel, and no value in that column is a measurement. A handful of the documented error values in our corpus are marked as derived rather than quoted — where Microsoft’s page documents no error condition at all and the error follows from the rules the page does state — and each such case says so in its own note.

The Google Sheets column is executed output from dated runs, because Sheets has no version number to pin. A formula-only .xlsx carrying no cached results goes to Google Drive, is recalculated by Google’s engine, and comes back as .xlsx for readback, with a deterministic =1111+2222 canary in every sheet proving the recalculation happened. Sixteen of the rows above ran on 2026-08-31 and are marked †; the other twenty-five ran on 2026-08-29.

The LibreOffice column is executed output from Calc 25.8.7.3, with 24.2.0.3, 24.8.7.2 and 25.2.0.3 agreeing on every row. Those four builds are the only ones we tested, so “stable across versions” means stable across those four.

Finally, the err:502 explanation is our reading of why the engine behaves this way, not something we measured. What we measured is the code each formula returned. See our methodology for how recalculation is proven rather than assumed, the LibreOffice version support page for the cross-release picture, and the spreadsheet error reference for what each code means.

Check before you migrate