💰 | Number & Currency Formatting 🎯 Learning Objectives Understand global variations in number formatting Handle decimal separators and digit grouping correctly Format currency with proper symbols and placement Work with different numbering systems Implement locale-aware number parsing and validation 🌍 The Global Number Challenge The same number can look completely different around the world. What appears as 1,234.56 in the US becomes 1.234,56 in Germany and 1 234,56 in France. Same Number, Different Representations Locale Number Format Currency Format English (US) 1,234.56 $1,234.56 German (Germany) 1.234,56 1.234,56 € French (France) 1 234,56 1 234,56 € Hindi (India) 1,234.56 ₹1,234.56 Arabic (Saudi Arabia) ١٬٢٣٤٫٥٦ ١٬٢٣٤٫٥٦ ر.س Japanese (Japan) 1,234.56 ¥1,235 (no decimals) Swiss (German) 1'234.56 CHF 1'234.56 ⚠️ Critical Insight Never assume a specific number format! Hard-coding formats like "1,234.56" will confuse or alienate users from other regions. Always use locale-aware formatting functions. 🔤 Key Formatting Elements Decimal Separator The character that separates the integer part from the fractional part. Period (.) — US, UK, China, Japan Comma (,) — Most of Europe, Latin America Arabic decimal (٫) — Middle East (Arabic) Group Separator The character that groups digits for readability (typically thousands). Comma (,) — US, UK, China, Japan Period (.) — Germany, Italy, Netherlands Space ( ) — France, Sweden, Czech Apostrophe (') — Switzerland Grouping Size How many digits appear in each group. 3 digits — Most of the world: 1,234,567 Indian system — After first 3, then 2: 12,34,567 Chinese — Groups of 4: 1234,5678 Digit Symbols Not all locales use Western Arabic numerals (0-9). Western: 0 1 2 3 4 5 6 7 8 9 Arabic-Indic: ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ Devanagari: ० १ २ ३ ४ ५ ६ ७ ८ ९ 💻 Implementation Guidelines Number Formatting JavaScript Example const number = 1234567.89; // US English const usFormatter = new Intl.NumberFormat('en-US'); console.log(usFormatter.format(number)); // → "1,234,567.89" // German const deFormatter = new Intl.NumberFormat('de-DE'); console.log(deFormatter.format(number)); // → "1.234.567,89" // French const frFormatter = new Intl.NumberFormat('fr-FR'); console.log(frFormatter.format(number)); // → "1 234 567,89" // Arabic (Saudi Arabia) - with Arabic-Indic digits const arFormatter = new Intl.NumberFormat('ar-SA'); console.log(arFormatter.format(number)); // → "١٬٢٣٤٬٥٦٧٫٨٩" // With options: 2 decimal places, minimum integer digits const formatted = new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2, minimumIntegerDigits: 1 }).format(42); console.log(formatted); // → "42.00" Python Example import locale from babel.numbers import format_decimal, format_currency number = 1234567.89 # Using locale module (system-dependent) locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') formatted = locale.format_string('%.2f', number, grouping=True) print(formatted) # → "1,234,567.89" # Better: Using Babel (recommended) # US English us_format = format_decimal(number, locale='en_US') print(us_format) # → "1,234,567.89" # German de_format = format_decimal(number, locale='de_DE') print(de_format) # → "1.234.567,89" # French fr_format = format_decimal(number, locale='fr_FR') print(fr_format) # → "1 234 567,89" # With custom decimal places precise = format_decimal(42, locale='en_US', decimal_quantization=False) print(f"{precise:.2f}") # → "42.00" Java Example import java.text.NumberFormat; import java.util.Locale; double number = 1234567.89; // US English NumberFormat usFormat = NumberFormat.getInstance(Locale.US); System.out.println(usFormat.format(number)); // → "1,234,567.89" // German NumberFormat deFormat = NumberFormat.getInstance(Locale.GERMANY); System.out.println(deFormat.format(number)); // → "1.234.567,89" // French NumberFormat frFormat = NumberFormat.getInstance(Locale.FRANCE); System.out.println(frFormat.format(number)); // → "1 234 567,89" // With custom decimal places NumberFormat customFormat = NumberFormat.getInstance(Locale.US); customFormat.setMinimumFractionDigits(2); customFormat.setMaximumFractionDigits(2); System.out.println(customFormat.format(42)); // → "42.00" 💵 Currency Formatting Currency formatting is more complex than simple numbers. You must consider symbol placement, spacing, negative formatting, and currency-specific decimal places. Currency Format Variations Locale Positive Amount Negative Amount Notes en-US $1,234.56 -$1,234.56 Symbol before, no space de-DE 1.234,56 € -1.234,56 € Symbol after, with space fr-FR 1 234,56 € -1 234,56 € Space group separator ja-JP ¥1,235 -¥1,235 No decimal places for JPY ar-SA ١٬٢٣٤٫٥٦ ر.س ؜-١٬٢٣٤٫٥٦ ر.س Arabic digits, RTL en-GB £1,234.56 -£1,234.56 Symbol before Currency Formatting Code Examples JavaScript Example const amount = 1234.56; // US Dollar const usdFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }); console.log(usdFormatter.format(amount)); // → "$1,234.56" // Euro in Germany const eurDeFormatter = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }); console.log(eurDeFormatter.format(amount)); // → "1.234,56 €" // Japanese Yen (no decimal places) const jpyFormatter = new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }); console.log(jpyFormatter.format(amount)); // → "¥1,235" (automatically rounded) // Display currency code instead of symbol const codeFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', currencyDisplay: 'code' }); console.log(codeFormatter.format(amount)); // → "USD 1,234.56" // Accounting format (parentheses for negative) const accountingFormatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', currencySign: 'accounting' }); console.log(accountingFormatter.format(-amount)); // → "($1,234.56)" Python Example from babel.numbers import format_currency amount = 1234.56 # US Dollar usd_format = format_currency(amount, 'USD', locale='en_US') print(usd_format) # → "$1,234.56" # Euro in Germany eur_de_format = format_currency(amount, 'EUR', locale='de_DE') print(eur_de_format) # → "1.234,56 €" # Japanese Yen (automatically handles no decimals) jpy_format = format_currency(amount, 'JPY', locale='ja_JP') print(jpy_format) # → "¥1,235" # British Pound gbp_format = format_currency(amount, 'GBP', locale='en_GB') print(gbp_format) # → "£1,234.56" # Display currency code code_format = format_currency( amount, 'USD', locale='en_US', format_type='name' ) print(code_format) # → "1,234.56 US dollars" ✅ Pro Tip: Currency-Specific Decimal Places Not all currencies use 2 decimal places! The formatting library automatically handles this: 0 decimals: JPY (¥), KRW (₩), VND (₫) 2 decimals: USD ($), EUR (€), GBP (£) — Most common 3 decimals: BHD (BD), JOD (JD), KWD (KD), OMR (OR), TND (TD) Always use locale-aware formatting libraries to handle these variations automatically! ⚠️ Common Pitfalls & Solutions ❌ Pitfall 1: String Concatenation for Currency Wrong: // Hard-coded format const price = "$" + amount.toFixed(2); // Result: "$1234.56" // Problems: No thousands separator, // wrong for most countries Right: // Locale-aware formatting const price = new Intl.NumberFormat( userLocale, { style: 'currency', currency: userCurrency }).format(amount); ❌ Pitfall 2: Parsing User Input Without Locale When users enter "1.234,56" , parseFloat() will fail or give wrong results. You need locale-aware parsing. // JavaScript: Use a library like numbro or parser import { parse } from 'numbro'; const germanInput = "1.234,56"; const parsed = parse(germanInput, { locale: 'de-DE' }); console.log(parsed); // → 1234.56 // Python: Use locale or babel from babel.numbers import parse_decimal german_input = "1.234,56" parsed = parse_decimal(german_input, locale='de_DE') print(parsed) # → Decimal('1234.56') ❌ Pitfall 3: Storing Formatted Strings in Database Never store formatted numbers or currency in your database! Store: Raw numeric values (preferably as integers for currency) // Store cents/pence to avoid floating-point issues const priceInCents = 123456; // $1,234.56 // Or use Decimal type in your database Format: Only when displaying to users const displayPrice = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(priceInCents / 100); 🎯 Best Practices Checklist Practice Priority ✅ Use locale-aware formatting libraries (Intl, Babel, ICU) CRITICAL ✅ Store raw numeric values, format only for display CRITICAL ✅ Use integers for currency (cents/pence) to avoid float errors HIGH ✅ Always specify currency code (USD, EUR) when formatting CRITICAL ✅ Use locale-aware parsing for user input HIGH ✅ Test with multiple locales (US, Germany, Japan, Arabic) HIGH ✅ Handle right-to-left (RTL) for Arabic/Hebrew currencies MEDIUM ✅ Provide clear error messages for invalid number input MEDIUM 📚 Additional Resources Unicode CLDR: Common Locale Data Repository for number patterns ISO 4217: Currency code standard Intl.NumberFormat (JavaScript): MDN documentation Babel (Python): Comprehensive i18n library ICU (C/C++/Java): International Components for Unicode Next Topic: Date & Time Formatting →