Text functions
NUMBERVALUE
Matches all 8 recorded answers
Recorded: 8 workbook cells. How we measure this · See the Rust code
Local names in 40 languages
| Language | Name in Excel |
|---|---|
| Arabic | NUMBERVALUE |
| Bulgarian | NUMBERVALUE |
| Czech | NUMBERVALUE |
| Danish | TALVÆRDI |
| German | NUMBERVALUE |
| Greek | NUMBERVALUE |
| English (Australia) | NUMBERVALUE |
| English (United Kingdom) | NUMBERVALUE |
| English (United States) | NUMBERVALUE |
| Spanish | VALOR.NUMERO |
| Estonian | NUMBERVALUE |
| Finnish | NROARVO |
| French | VALEURNOMBRE |
| Hebrew | NUMBERVALUE |
| Croatian | NUMBERVALUE |
| Hungarian | SZÁMÉRTÉK |
| Indonesian | NUMBERVALUE |
| Italian | NUMBERVALUE |
| Japanese | NUMBERVALUE |
| Korean | NUMBERVALUE |
| Lithuanian | NUMBERVALUE |
| Latvian | NUMBERVALUE |
| Norwegian | NUMBERVALUE |
| Dutch | NUMERIEKE.WAARDE |
| Polish | WARTOŚĆ.LICZBOWA |
| Portuguese (Brazil) | NUMBERVALUE |
| Portuguese (Portugal) | VALOR.NÚMERO |
| Romanian | NUMBERVALUE |
| Russian | ЧЗНАЧ |
| Slovak | NUMBERVALUE |
| Slovenian | NUMBERVALUE |
| Serbian (Latin) | NUMBERVALUE |
| Swedish | TALVÄRDE |
| Thai | NUMBERVALUE |
| Turkish | SAYIDEĞERİ |
| Ukrainian | NUMBERVALUE |
| Vietnamese | NUMBERVALUE |
| Chinese (Simplified) | NUMBERVALUE |
| Chinese (Hong Kong) | NUMBERVALUE |
| Chinese (Traditional) | NUMBERVALUE |
Names from Microsoft's support site.
What it does
NUMBERVALUE(text, [decimal_separator], [group_separator])
NUMBERVALUE(text, [decimal_separator], [group_separator]) parses text as a number using
the two separator characters you name, instead of the two the host locale would have chosen.
That is the entire point of the function, and it is worth stating sharply: VALUE asks the
machine what a decimal point looks like; NUMBERVALUE lets the formula say so. A workbook
that reads "1.234,56" with NUMBERVALUE(A1, ",", ".") gets 1234.56 on every machine on
earth. The same string through VALUE gets a different answer, or an error, depending on
where the workbook is opened.
The parse Microsoft documents is not a plain decimal scan. Three rules make it its own grammar:
- Whitespace is ignored anywhere in
text, including in the middle." 3 000 "parses as 3000 even without a group separator. - Group separators are cosmetic before the decimal separator and fatal after it. They may appear anywhere in the integer part — Microsoft's page does not require them to be in groups of three — but a group separator occurring after the decimal separator is an error.
- Percent signs are trailing and cumulative. A
%intextdivides the result by 100, and multiple percent signs compound:"9%%"is 9 / 100 / 100 = 0.0009.
The third rule is the one that surprises people. It is not a typo tolerance; it is arithmetic.
Arguments
| Argument | Meaning | Default |
|---|---|---|
text | The string to parse. Required. | — |
decimal_separator | The character separating the integer and fractional parts. | The host locale's decimal separator |
group_separator | The character separating groups within the integer part. | The host locale's group separator |
Two documented details about the separator arguments:
- Only the first character of each is used. Passing
",."as a separator is not an error and not a two-character separator; it is,.
If you are using NUMBERVALUE for the reason it was added, supply both separators.
Try it
Quirks
Result and edge cases
Returns Number.
Documented boundary behaviours:
- Empty
textreturns 0, not an error and not an empty result. This is a real difference fromVALUE, which does not treat the empty string as zero. - Whitespace-only
textfollows from the ignore-whitespace rule and the empty-string rule, though Microsoft states the two rules separately rather than combining them.
Empty cells, missing arguments and incoming error values follow the shared call model; see
Coercion and lifting. Nothing here is specific to
NUMBERVALUE except the empty-string-is-zero rule above, which is a function-level decision
rather than an engine one.
Errors
As documented on Microsoft's NUMBERVALUE page:
| Error | Condition |
|---|---|
#VALUE! | decimal_separator and group_separator are the same character. |
#VALUE! | A group separator occurs after the decimal separator in text. |
#VALUE! | Any argument is not valid — including text that does not parse under the given separators. |
The Handbook has not verified this list against Excel, and in particular has not established whether "not valid" covers cases such as an empty-string separator argument or a separator that is a digit.
How to implement
Notes for implementers
The locale dependence does not disappear when the separators are supplied — it only shrinks. Currency symbols, sign conventions and non-ASCII digits are not covered by the two separator arguments, and Microsoft's page does not say what the parser does with them. An implementation has to decide, and should record the decision as a decision.
Cumulative percent handling has to be applied after the numeric parse, not as a prefix scan,
and each % divides once. Implementations that treat % as a flag rather than a count will
be right on "9%" and wrong on "9%%".
The "only the first character is used" rule for separators means the separator arguments cannot be validated by length. They are validated by comparison — the two must differ — and that comparison is on the first characters, not the whole strings.
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.
NUMBERVALUE · 1 lines · calc-func/src/catalogue/text_format.rs on GitHub5 numbervalue_fn "NUMBERVALUE" (1, 3) [V "text", V "decimal_separator", V "group_separator"] |s, a, sv| { numbervalue(s, a) }
numbervalue · 60 lines · calc-func/src/catalogue/text_format.rs on GitHub/// NUMBERVALUE: the separators default to the locale's.
fn numbervalue(site: &Site<'_>, a: &[CalcValue]) -> Step<CalcValue> {
let text = text_arg(site, a, 0)?;
if text.len_utf16_code_units() > MAX_READER_TEXT {
return fail(WorksheetErrorCode::Value);
}
let separator = |index: usize, default: &str| -> Step<Option<char>> {
let given_text;
let text = if given(a, index) {
given_text = text_arg(site, a, index)?.to_string_lossy();
given_text.as_str()
} else {
default
};
match text.chars().next() {
Some(c) => Ok(Some(c)),
None => fail(WorksheetErrorCode::Value),
}
};
let (decimal_default, group_default) = if given(a, 1) && given(a, 2) {
(".", ",")
} else {
let (_, profile) = profile(site)?;
(profile.decimal_separator, profile.thousands_separator)
};
let decimal = separator(1, decimal_default)?;
let group = separator(2, group_default)?;
let host_profile = site.environment().and_then(|environment| environment.profile.as_ref())
.filter(|profile| value_text::measured_host_reader(profile)
&& decimal == Some('.') && group == Some(' '));
if host_profile.is_some_and(|profile| text.to_string_lossy().chars()
.any(|ch| value_text::measured_space(ch, profile))) {
return fail(WorksheetErrorCode::Value);
}
let result = match k::numbervalue(&text, decimal, group) {
// A numeric range error is final, including a zero coefficient. The host
// grammar fallback must not admit the same rejected exponent as VALUE.
Err(k::NumberValueError::Range) => return fail(WorksheetErrorCode::Value),
result => result.map_err(|_| WorksheetErrorCode::Value),
};
if result.is_ok() || host_profile.is_none() || !matches!(a[0].core_ref(), CoreRef::Text(_)) {
return out_number(site, result);
}
// The measured host admits the same currency, digit and date grammar as
// VALUE after ASCII spaces and trailing percents are removed. Keep the
// existing numeric parser and its percent arithmetic for its prior forms.
let compact: String = text.to_string_lossy().chars().filter(|&ch| ch != ' ').collect();
// Slash/colon forms have no new service evidence; keep their prior route.
if compact.contains(['/', ':']) {
return out_number(site, result);
}
let base = compact.trim_end_matches('%');
let percent_count = (compact.len() - base.len()) as u32;
let value = super::kit::text_value(base);
match read_value(site, &[value], 0) {
Ok(reading) => site.number(k::numbervalue_percent(reading.value(), percent_count)),
Err(Exit::Error(WorksheetErrorCode::Value)) => out_number(site, result),
Err(other) => Err(other),
}
}
out_number · 6 lines · calc-func/src/catalogue/text_format.rs on GitHubfn out_number(site: &Site<'_>, result: Result<f64, WorksheetErrorCode>) -> Step<CalcValue> {
match result {
Ok(n) => site.number(n),
Err(code) => fail(code),
}
}
numbervalue · 90 lines · calc-func/src/ported/text_more.rs on GitHubpub(crate) fn numbervalue(
text: &ExcelText,
decimal_separator: Option<char>,
group_separator: Option<char>,
) -> Result<f64, NumberValueError> {
if decimal_separator.is_some() && decimal_separator == group_separator {
return Err(NumberValueError::Syntax);
}
let mut raw: String = text
.to_string_lossy()
.chars()
.filter(|ch| !ch.is_whitespace())
.collect();
if raw.is_empty() {
// NUMBERVALUE("") is 0 (Microsoft's NUMBERVALUE page).
return Ok(0.0);
}
let mut percent_count = 0u32;
while raw.ends_with('%') {
raw.pop();
percent_count += 1;
}
if raw.is_empty() {
return Err(NumberValueError::Syntax);
}
let mut chars = raw.chars().peekable();
let mut sign = 1.0;
if let Some(ch) = chars.peek().copied()
&& (ch == '+' || ch == '-')
{
sign = if ch == '-' { -1.0 } else { 1.0 };
chars.next();
}
let mut normalized = String::new();
let mut seen_digit = false;
let mut seen_decimal = false;
let mut seen_exponent = false;
for ch in chars {
if ch.is_ascii_digit() {
normalized.push(ch);
seen_digit = true;
continue;
}
if decimal_separator == Some(ch) {
if seen_decimal || seen_exponent {
return Err(NumberValueError::Syntax);
}
normalized.push('.');
seen_decimal = true;
continue;
}
if group_separator == Some(ch) && !seen_decimal {
continue;
}
if matches!(ch, 'e' | 'E') && seen_digit && !seen_exponent {
normalized.push('e');
seen_exponent = true;
continue;
}
if matches!(ch, '+' | '-') && normalized.ends_with('e') {
normalized.push(ch);
continue;
}
return Err(NumberValueError::Syntax);
}
if !seen_digit {
return Err(NumberValueError::Syntax);
}
let (mantissa, exponent) = if let Some((mantissa, exponent)) = normalized.split_once('e') {
if matches!(exponent, "" | "+" | "-") {
return Err(NumberValueError::Syntax);
}
(mantissa, exponent.parse::<i64>().map_err(|_| NumberValueError::Range)?)
} else {
(normalized.as_str(), 0)
};
let fraction_digits = mantissa.split_once('.').map_or(0, |(_, fraction)| fraction.len());
let digits = mantissa.replace('.', "");
let scale = exponent.checked_sub(fraction_digits as i64).ok_or(NumberValueError::Range)?;
let position = scale.checked_add(digits.trim_start_matches('0').len() as i64)
.ok_or(NumberValueError::Range)?;
// NUMBERVALUE checks this range even for an all-zero coefficient and before
// applying trailing percents (fn12's explicit-separator Excel controls).
if !(-308..=308).contains(&position) {
return Err(NumberValueError::Range);
}
let mut value = crate::value_text::decimal(&digits, scale).ok_or(NumberValueError::Range)?;
value *= sign;
Ok(numbervalue_percent(value, percent_count))
}
numbervalue_percent · 7 lines · calc-func/src/ported/text_more.rs on GitHub/// The existing NUMBERVALUE percent arithmetic, shared with its host reader.
pub(crate) fn numbervalue_percent(mut value: f64, percent_count: u32) -> f64 {
for _ in 0..percent_count {
value /= 100.0;
}
value
}
Kernels shared with other functions
read_valuecatalogue/text_formatvaluecatalogue/text_formatprofilecatalogue/text_formatrenderedcatalogue/text_formattextcatalogue/text_formatgivencatalogue/text_formatlow32decimalNatural::zerodecimalNatural::from_u64decimalNatural::pow10decimalNatural::normalizedecimalNatural::is_zerodecimalNatural::mul_add_smalldecimalNatural::times_pow10decimalNatural::times_pow5decimalNatural::times_pow2decimalExactDecimal::readdecimalExactDecimal::binarydecimalhas_invalid_exponentformat/datetimelooks_like_datetime_formatformat/datetimewithout_subsecondsformat/datetimerender_with_datetime_tokensformat/datetimetokenize_datetime_formatformat/datetimeelapsed_token_atformat/datetimeclassify_minute_tokensformat/datetimetoken_kindformat/datetimeis_time_neighborformat/datetimepush_literalformat/datetimerender_datetime_literalformat/datetimeformat_elapsedformat/datetimeTimeParts::from_serialformat/datetimemeasured_hostformatcurrency_literalformatdigitsformatfifteenformatsplitformatgroupedformatgeneralformatsignificantformatsectionsformatbracketformatis_conditionformatis_elapsedformatcondition_holdsformatcondition_offormatstrip_tokensformatchooseformatitemsformatnumericformatliteral_textformatinteger_partformatfraction_partformatscientificformatfraction_codeformatFractionCode::renderformatmixed_fraction_under_space_groupingformatconvergentformattext_declared_date_localeformattextformattext_of_textformatmonth_nameformat/namesweekday_nameformat/nameslocale_namesformat/namesLocaleId::from_bcp47_language_taglocaleLocaleId::from_excel_lcidlocaleshort_date_orderlocaleshort_date_patternlocalecurrency_placementlocalecurrency_spacinglocalecurrency_negative_patternlocaleformat_profilelocaledays_from_civillocalecivil_from_dayslocaleymd_from_excel_seriallocaledecimalnumber_textfirst_precisionnumber_textnear_tienumber_textfirst_roundingnumber_textroundednumber_textincrementnumber_textasciinumber_textdays_from_civilported/date_kernelscivil_from_daysported/date_kernelsserial_unbounded_1900ported/date_kernelsymd_1900ported/date_kernelsis_leapported/date_kernelsdays_in_monthported/date_kernelswhole_serialported/date_kernelswholeported/date_kernelsweekday_sunday_one_basedported/date_kernelsremap_weekdayported/date_kernelsweekday_ofported/date_kernelsweekdayported/date_kernelstextported/text_kernelstext_from_strported/text_kernelsdate_integer_floorported/text_kernelspush_singleton_or_originalported/text_kernelspush_upperported/text_kernelsupperported/text_kernelsmeasured_host_readervalue_textmeasured_spacevalue_textreadvalue_textnumbervalue_textdecimalvalue_textfractionvalue_texttokensvalue_textmonth_namedvalue_texttime_atvalue_textfull_yearvalue_textvalid_dayvalue_textthis_yearvalue_textserial_1900value_textdate_ofvalue_textdate_timevalue_text
Argument and result plumbing
failcatalogue/kittext_argcatalogue/kittext_valuecatalogue/kitnumbercoerce
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
VALUEis the locale-following text-to-number conversion. The pair is not legacy/modern —VALUEis not superseded and Microsoft continues to document both. They differ in one axis only: where the separator characters come from. Reach forNUMBERVALUEwhenever the text's format is a property of the data rather than of the machine — parsed files, web sources, fixed feed formats.TEXTis the inverse direction and has the mirror-image locale problem:TEXTtakes a format string that is itself interpreted in a language. There is noTEXTcounterpart toNUMBERVALUE's explicit separators; see TEXT.- Implicit coercion — what happens when text lands in a numeric argument slot — is a third,
separate rule set, described in
Coercion and lifting. It is neither
VALUEnorNUMBERVALUE, and the chapter is explicit that Excel's full text-to-number recognizer is broader than the pinned baseline. DOLLARandFIXEDproduce formatted numeric text thatNUMBERVALUEcan often read back, given the right separators.
Named variants
No named variants are recorded yet.
Suggest a correction or contribute an implementation · For agents