How do I format numbers as lakh and crore?
Indian Number Format: Lakh and Crore in Code
In the Indian numbering system, the last three digits of the integer part form one group and every group to the left of that has two digits, so 1234567 is written 12,34,567 (twelve lakh, thirty-four thousand, five hundred sixty-seven) rather than 1,234,567. A lakh is 1,00,000 (10^5) and a crore is 1,00,00,000 (10^7). In code, the dependable way to produce this grouping is a locale-aware formatter with the en-IN (or hi-IN) locale: new Intl.NumberFormat('en-IN').format(n) in JavaScript, Babel's format_decimal(n, locale='en_IN') in Python, ICU4J in Java, the en-IN culture in .NET and SQL Server, and either a Windows regional setting or a conditional custom format in Excel. The built-in Java formatter and Python's format specification do not support it, which is where most bugs come from.
How the grouping works
| Value | International | Indian | Spoken (Indian English) |
|---|---|---|---|
| 10^3 | 1,000 | 1,000 | one thousand |
| 10^4 | 10,000 | 10,000 | ten thousand |
| 10^5 | 100,000 | 1,00,000 | one lakh |
| 10^6 | 1,000,000 | 10,00,000 | ten lakh |
| 10^7 | 10,000,000 | 1,00,00,000 | one crore |
| 10^8 | 100,000,000 | 10,00,00,000 | ten crore |
| 10^9 | 1,000,000,000 | 1,00,00,00,000 | one hundred crore (traditionally one arab) |
| 10^12 | 1,000,000,000,000 | 1,00,00,00,00,000 | one lakh crore |
The rule has a primary group size of 3 and a secondary group size of 2. The Unicode CLDR data behind most locale-aware formatters encodes it as the pattern #,##,##0.###, and it continues in pairs indefinitely: new Intl.NumberFormat('en-IN').format(1234567890123) produces 12,34,56,78,90,123. Older words for larger units (arab, kharab) are rarely used in business writing; large amounts are expressed in lakh crore, as in government budgets.
The decimal separator is a point, as in English. Currency is written with the rupee sign U+20B9 (₹), introduced in 2010, or the code INR; Rs. is still common in informal text and older systems.
Seeing it in the tool
Enter a number in the Number Formatter:
1234567.891With the locale set to en-IN, the decimal style prints 12,34,567.891; the currency style with INR prints ₹12,34,567.89; and the lakh/crore words style prints 12.35 lakh. The same value in en-US is 1,234,567.891. The tool shows the common locales side by side, which is the quickest way to check what a given locale will produce before you commit to it in code.
Code, language by language
| Language | Code | Output for 1234567.891 | Notes |
|---|---|---|---|
| JavaScript | new Intl.NumberFormat('en-IN').format(n) |
12,34,567.891 |
Also n.toLocaleString('en-IN'); works with BigInt |
| JavaScript (currency) | new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(n) |
₹12,34,567.89 |
Two decimal places by default for INR |
| JavaScript (compact) | new Intl.NumberFormat('en-IN', { notation: 'compact' }).format(n) |
12L |
123456789 gives 12Cr; compactDisplay: 'long' gives 12 crore |
| Python + Babel | format_decimal(n, locale='en_IN') |
12,34,567.891 |
format_currency(n, 'INR', locale='en_IN') gives ₹12,34,567.89 |
| Python standard library | f"{n:,}" |
1,234,567.891 |
Wrong: the format spec only knows groups of three |
Python locale |
locale.format_string('%.2f', n, grouping=True) after setlocale(LC_ALL, 'en_IN.UTF-8') |
12,34,567.89 |
Depends on the locale being installed; setlocale is process-wide and not thread-safe |
Java java.text |
NumberFormat.getInstance(Locale.forLanguageTag("en-IN")) |
1,234,567.891 |
Wrong: see below |
| Java ICU4J | com.ibm.icu.text.NumberFormat.getInstance(new ULocale("en_IN")) |
12,34,567.891 |
Supports secondary grouping |
| C# / .NET | n.ToString("N2", CultureInfo.GetCultureInfo("en-IN")) |
12,34,567.89 |
The culture's NumberGroupSizes is {3, 2} |
| SQL Server | FORMAT(n, 'N2', 'en-IN') |
12,34,567.89 |
Uses the .NET culture data; slow on large result sets |
| PostgreSQL | to_char(n, 'FM99,99,99,999.00') |
12,34,567.89 |
A fixed picture: add 99, pairs for larger values |
| Excel | Custom format (see below) | 12,34,567.89 |
Or set Windows regional format to English (India) |
The JavaScript and Python Babel outputs above were produced by running the code; Intl in every current browser and in Node.js ships full ICU data, so en-IN works without extra packages.
Java: the trap in java.text
Java's standard NumberFormat for en-IN prints 1,234,567.891. The locale data is not the problem; java.text.DecimalFormat supports only one grouping size. Its documentation states that when a pattern contains several grouping separators, the interval between the last one and the end of the integer is used everywhere, so #,##,##0.00 is treated as #,##0.00. On Java 21:
new DecimalFormat("#,##,##0.00").format(1234567.891); // "1,234,567.89"Use ICU4J's NumberFormat or DecimalFormat (package com.ibm.icu.text), which implement secondary grouping, or format the string yourself.
Python without Babel
If adding a dependency is not an option, a small function is enough for display purposes:
import re
def indian(n: float, places: int = 2) -> str:
sign = "-" if n < 0 else ""
whole, frac = f"{abs(n):.{places}f}".split(".")
head, tail = whole[:-3], whole[-3:]
if head:
head = re.sub(r"(\d)(?=(\d{2})+$)", r"\1,", head)
tail = head + "," + tail
return f"{sign}{tail}.{frac}"
indian(1234567.891) # '12,34,567.89'
indian(1234567890123) # '12,34,56,78,90,123.00'For money, pass a Decimal formatted to a string rather than a float, so rounding happens in decimal arithmetic.
Excel
With Windows regional settings set to English (India), the ordinary #,##0.00 format already groups in lakhs. On other systems, a conditional custom format does it:
[>=10000000]##\,##\,##\,##0.00;[>=100000]##\,##\,##0.00;##,##0.00The first section handles crores, the second lakhs, and the third everything smaller. Excel allows only two conditions plus a default, so this format does not handle negative numbers (they fall into the last section and are grouped in thousands), and values of 100 crore and above get their extra digits prepended without a separator. For reports with negative values or very large amounts, set the regional format instead or format in the source system.
PostgreSQL
to_char places a group separator wherever a , appears in the picture and suppresses leading ones with the FM prefix, so 'FM99,99,99,999.00' produces Indian grouping for values up to 99,99,99,999. Extend the picture with more 99, pairs for larger values. The G pattern takes the separator character from lc_numeric, but the positions of the separators still come from the picture, so there is no way to get lakh grouping from the locale alone; the fixed picture is the practical approach.
Reporting in lakhs and crores
Indian financial statements, budgets and MIS reports often express every figure in a single unit stated in the header, such as "₹ in lakh" or "₹ in crore", rather than printing full amounts. That is a scaling operation, not a formatting one, and it has its own rules:
- Divide by 10^5 for lakh or 10^7 for crore, then round to a fixed number of decimals, usually two. ₹12,34,567.89 becomes 12.35 in a "₹ in lakh" column and 0.12 in a "₹ in crore" column.
- Round each figure for display only, and compute totals from the unrounded values. A column of rounded figures often does not add up to its rounded total; reports conventionally accept that difference or footnote it.
- Keep the Indian grouping on the scaled figures too. A "₹ in lakh" column can still contain
1,23,456.78, which is 1,234.57 crore. - State the unit once, in the header, and do not repeat "lakh" in every cell.
For mixed audiences, remember that the conversion is not obvious to everyone. A useful set of equivalences for writers: 10 lakh is 1 million, 1 crore is 10 million, 100 crore is 1 billion, and 1 lakh crore is 1 trillion. International readers can misread 1,00,000 as a typing mistake, so documents that travel outside India often give both forms, for example "₹50 crore (₹500 million)", or use international grouping throughout with the locale set to en-IN only for the Indian edition.
Parsing Indian-formatted numbers
Going the other way is where silent errors hide:
Number("12,34,567")in JavaScript isNaN, andparseFloat("12,34,567")is12, becauseparseFloatstops at the first comma. Remove the separators first.- Validation patterns written for Western grouping, such as
^\d{1,3}(,\d{3})*$, reject12,34,567. Accept both, or strip separators before validating. - Spreadsheets opened on a non-Indian locale may treat
12,34,567as text rather than a number.
The Number Formatter reads 12,34,567 as input directly, and a line that is not a number at all, such as N/A, is left unchanged with a notice rather than being turned into 0 — a useful check before a column of mixed values goes into a report.
The safest approach for any number that crosses a system boundary is to store and transmit it without grouping (1234567.891) and apply lakh and crore formatting only when displaying it to a person.
Formatting guidelines
- Use the locale, not a hand-written pattern, wherever a locale-aware formatter exists:
en-INfor English interfaces,hi-INor other Indian locales for localised interfaces. - For Devanagari digits, request them explicitly:
new Intl.NumberFormat('hi-IN', { numberingSystem: 'deva' })gives१२,३४,५६७.८९१. - Keep currency amounts in integer paise or decimal types; format only at the edge.
- For large headline figures, words read better than digits: "₹12.35 lakh", "₹4.5 crore". The Number Formatter's lakh/crore words style and
Intl's compact notation both produce this. Watch values just below a unit: rounded to two decimals, 99,999 is1 lakh, not100 thousand, and the words style moves up to the larger unit in that case. - For dates in the same interfaces, the common Indian order is day, month, year; see date format patterns for the tokens.