Getting the Decimal Comma or Point Right by Locale in AI Output A developer highlights a common pitfall in AI-generated numbers: models often apply a locale's decimal comma without adjusting the thousands separator, producing unparseable strings like '1,234,56'. The post explains the paired nature of separators, the role of CLDR data, and the SI recommendation for space-grouped digits, urging correct locale-aware formatting in AI output. A model writing German produces 1,234,56 . It has correctly learned that German uses a decimal comma and has not learned that German therefore uses something else for the thousands, so it applied one rule and left the other alone. The result is not a foreign-looking number. It is not a number. The two separators are a matched pair. A locale that uses a comma to mark the decimal cannot also use a comma to group thousands, because the string would be unparseable — which is exactly what happens when a generator applies only one half of the pair. It happens because the decimal comma is the salient, frequently-stated fact “Germans use a comma for decimals” while the thousands separator is the consequence nobody writes down. A model following an instruction to use European number format has strong evidence for the first and weak evidence for the second, and applies what it has. Three variants of the resulting output are worth recognising: 1,234,56 . Unparseable. This is the most common and, mercifully, the most visible. 1234,56 . Correct as a number and unidiomatic above four digits; harmless in small amounts, noticeably wrong in a financial table. 1.234,56 in one paragraph and 1,234.56 two paragraphs later, in the same document. This is the worst one, because each individual number is well-formed and only the document as a whole is wrong.One thousand two hundred and thirty-four and fifty-six hundredths: en-US 1,234.56 group comma, decimal point en-GB 1,234.56 group comma, decimal point de-DE 1.234,56 group point, decimal comma es-ES 1.234,56 group point, decimal comma it-IT 1.234,56 group point, decimal comma pt-BR 1.234,56 group point, decimal comma fr-FR 1 234,56 group narrow no-break space, decimal comma ru-RU 1 234,56 group no-break space, decimal comma pl-PL 1 234,56 group no-break space, decimal comma sv-SE 1 234,56 group no-break space, decimal comma de-CH 1'234.56 group apostrophe, decimal point en-IN 1,234.56 group comma but Indian grouping above five digits The French, Russian, Polish and Swedish rows are the ones that break code, and the reason is that the separator is not a plain space. CLDR uses a no-break space so the number cannot be split across a line, and for French the group separator is U+202F NARROW NO-BREAK SPACE specifically. Any string comparison, regular expression or trimming step written against U+0020 will not match it, and any test written by typing a space into an editor will fail against correct output. The Swiss row is a third distinct pattern that undermines any two-camp model of the world: a German-speaking locale with a decimal point and an apostrophe for grouping. And the Indian row points at the separate problem that grouping is not always in threes, which is the lakh-crore system https://multigrid.ai/learn/indian-lakh-crore-number-format . There is a formal recommendation, and it is worth knowing because it appears in scientific and technical documents and looks like an error to people who have not met it. The International Bureau of Weights and Measures, in the SI Brochure https://www.bipm.org/en/publications/si-brochure , states that either a comma or a point may be used as the decimal marker, and that digits should be separated into groups of three by a thin space and never by a comma or a point — precisely so that the grouping character cannot be confused with the decimal marker in either convention. That is why a physics paper writes 1 234.56 where a newspaper would write 1,234.56 . If you are generating technical or scientific copy, the space-grouped form is the correct one and a reviewer who removes the spaces is introducing an error. If you are generating anything else, follow CLDR’s locale data instead. Formatting wrong is embarrassing. Parsing wrong loses money. Consider a model extracting an amount from a European invoice and handing the string to code that calls a naive numeric parser: parseFloat "1.234,56" // 1.234 — stops at the comma Number "1.234,56" // NaN parseFloat "1.234" // 1.234 — was one thousand two hundred and thirty-four The third line is the one that gets into production. 1.234 is a perfectly valid float, so nothing throws, nothing warns, and an amount of 1,234 euro is recorded as 1.234 euro — off by a factor of a thousand, in a field that no validation will question because it is a plausible number. The same string in the other direction turns 1.234 into 1234. The defence is to never let a locale-formatted numeric string reach a parser. Have the extraction produce a normalised value with an explicit, single convention — a JSON number, or a string in 1234.56 form with no grouping at all — and have the model report the source formatting separately if you need it. Where you genuinely must parse a localised string, use a locale-aware parser and pass the locale explicitly; do not infer it from the string, because 1.234 is ambiguous and always will be. The interchange format where this bites hardest is CSV, and the reason is worth spelling out because it produces a file that opens correctly on one desk and not on another. A locale that uses a decimal comma cannot use a comma to separate CSV fields, so the convention in those locales is a semicolon — and spreadsheet software follows the operating system list separator when it decides how to split a .csv file. The same file therefore parses into columns on a US machine and into a single column on a German one, or worse, splits a European number across two columns. If you are generating tabular exports, either emit a real spreadsheet format, or emit unambiguous machine-format numbers with a comma delimiter and quote every field, and state the convention in a header row rather than leaving the receiving application to guess. 1234.56 . This is a machine format, not a display format, and it should never reach a reader. ^-?\d+ \.\d+ ?$ — rather than trusting it. A number with a comma in it is a rejected response, not a value to clean up. new Intl.NumberFormat "de-DE" .format 1234.56 // "1.234,56" new Intl.NumberFormat "fr-FR" .format 1234.56 // "1 234,56" U+202F new Intl.NumberFormat "de-CH" .format 1234.56 // "1'234.56" When the currency symbol is involved as well, its position and spacing are a separate piece of locale data and are covered in where the currency symbol goes https://multigrid.ai/learn/currency-symbol-placement-locale . They resolve from the same formatter call, which is the argument for making that call once rather than assembling a monetary string by concatenation.