🇫🇷

France Number Format

CLDR Locale:
ISO: FR / FRARegion: EuropeDigits: western

Try It — Live Preview

Number
1 234 567,89
Currency (EUR)
1 234 567,89 €
Percentage
1 234 568 %
Compact
1,2 M

Format Rules

Decimal Separator
,comma
Thousands Separator
·narrow no-break space
Grouping Pattern
3
Negative Format
-1 234 567,89
Currency Symbol
€ (EUR)
Currency Position
After number with space → 1 234 567,89 €
Percent Position
After number with space → 13 %
Zero
0,00
Compact
1,2 M

France formatting implementation notes

For France, format numbers with the explicit locale fr-FR; the reference value 1234567.89 renders as 1 234 567,89.

France's reference format uses "," as the decimal mark and a narrow non-breaking space (U+202F) for grouping. The integer uses repeating groups of three digits. Pass "fr-FR" directly to Intl.NumberFormat instead of replacing punctuation in a preformatted string.

EUR is shown with € after the amount with a separating space; the same reference value renders as 1 234 567,89 €. The percent sign appears after the amount with a separating space. Use the ISO currency code for storage and interchange because a visible symbol can be shared by unrelated currencies.

Western 0–9 digits are the page's reference digit system, and the negative pattern is -n. Treat fr-FR as the reference locale for this page, but still respect the user's explicitly selected language and numbering-system preferences. Validate output with formatToParts() when spacing, signs, or bidirectional literals affect the interface.

  • Locale: fr-FR
  • Decimal and grouping: 1 234 567,89
  • Currency: EUR → 1 234 567,89 €
  • Digits: Western 0–9 digits

Reviewed 19 July 2026. Sources: Unicode locale number formats · Intl.NumberFormat reference

What's different here

  • Decimal mark is a comma (,) (en-US uses a period (.)).
  • Thousands are grouped with a narrow no-break space (U+202F) (en-US uses a comma (,)).
  • The currency symbol goes after the amount with a space (en-US: immediately before the amount).
  • The percent sign goes after the number with a space (en-US: immediately after the number).
  • Uses the same number pattern (decimal mark, grouping, symbol and percent placement) as Belgium, Portugal, Finland, Czech Republic, Hungary and Slovakia in Europe, plus 2 pages in other regions.
  • Nearby differences: Luxembourg (fr-LU) groups with a period (.) instead of a narrow no-break space (U+202F); Germany (de-DE) groups with a period (.) instead of a narrow no-break space (U+202F); Sweden (sv-SE) groups with a no-break space (U+00A0) instead of a narrow no-break space (U+202F); Norway (nb-NO) groups with a no-break space (U+00A0) instead of a narrow no-break space (U+202F).
  • Currency: EUR has ISO minor unit 2.
  • The grouping character is a narrow no-break space (U+202F), not a regular space. Normalise it before parsing user input or comparing strings.
  • Postal address order: name → building → street number + street → postcode + city → country. See address formats.

French number format

French (fr-FR) writes 1234567.89 as 1 234 567,89 €, and there are two different invisible characters in it. The spaces between digit groups are U+202F NARROW NO-BREAK SPACE. The space before € (and before %) is U+00A0 NO-BREAK SPACE. Neither is the U+0020 you type.

Three spaces that look the same

CharacterNameWhere fr-FR Intl uses it
U+0020SPACEnowhere in formatted numbers
U+00A0NO-BREAK SPACEbefore € and before %
U+202FNARROW NO-BREAK SPACEbetween digit groups

Both no-break spaces keep a number on one line. Swap in U+0020 and a long amount can wrap mid-number. Keep them, and a plain string comparison fails:

const s = new Intl.NumberFormat('fr-FR').format(1234567.89);
s === "1 234 567,89";          // → false
s.includes("\u202F");          // → true
const norm = t => t.replace(/[\u00A0\u202F]/g, ' ');
norm(s) === "1 234 567,89";    // → true

formatToParts() shows which character sits where:

new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' })
  .formatToParts(1234.5)
  .filter(p => ['group', 'decimal', 'literal'].includes(p.type))
  .map(p => `${p.type} U+${p.value.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}`)
  .join(', ');
// → "group U+202F, decimal U+002C, literal U+00A0"

In tests, compare formatToParts() results or normalise both sides. When parsing, accept all three spaces as group separators, because users type U+0020.

Francophone variants

Values are CLDR 48.2 / Intl output for 1234567.89:

LocaleGroupingPlain numberCurrencyPercent (0.13)
fr-FRU+202F1 234 567,891 234 567,89 €13 %
fr-BEU+202F1 234 567,891 234 567,89 €13 %
fr-CAU+00A01 234 567,891 234 567,89 $13 %
fr-CHapostrophe U+0027 (CLDR 48.2+); U+202F on older runtimes1'234'567,891'234'567.89 CHF13%

fr-CA uses the wider no-break space. fr-CH now uses the Swiss apostrophe, switches to a period decimal inside CHF amounts, and puts no space before %.

Q&A

Why won't my French-formatted number match my test string?

Your test string has U+0020. Intl uses U+202F between groups and U+00A0 before € or %. Normalise both sides, or compare formatToParts() output.

Is there a space before % in French?

In fr-FR, fr-BE and fr-CA, yes: 13 %, and it's a no-break space. fr-CH has none: 13%.

Can I use a regular space as the thousands separator?

Accept it in input. In output, keep what Intl produces, so the number can't wrap.

Do Canada, Switzerland and Belgium format like France?

Belgium (fr-BE) matches France. Canada (fr-CA) groups with U+00A0 instead of U+202F. Switzerland (fr-CH) groups with an apostrophe and writes CHF amounts with a period decimal (1'234'567.89 CHF), which is CLDR data, not a typo.

Code Examples

// Basic number
new Intl.NumberFormat('fr-FR').format(1234567.89)
// → "1 234 567,89"

// Currency
new Intl.NumberFormat('fr-FR', {
  style: 'currency',
  currency: 'EUR'
}).format(1234567.89)
// → "1 234 567,89 €"

// Percentage
new Intl.NumberFormat('fr-FR', {
  style: 'percent',
  minimumFractionDigits: 1
}).format(0.125)

// Compact notation
new Intl.NumberFormat('fr-FR', {
  notation: 'compact'
}).format(1234567)

Negative Number Formats

Standard-1 234 567,89
new Intl.NumberFormat('fr-FR').format(-1234567.89)
Accounting style(1 234 567,89 €)
new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', currencySign: 'accounting' }).format(-1234567.89)

Edge Cases & Notes

Thousands separator is U+202F NARROW NO-BREAK SPACE, not a regular space. This catches many developers off-guard.