Need 1234567 to print as 1,234,567? To format a number with commas in C#, call number.ToString("N0") for whole numbers or ToString("N2") for two decimals, and pass an en-US culture so the separator doesn’t change from machine to machine.
In this tutorial, I’ll cover each case I’ve hit in production code for C# format number with commas: custom patterns, interpolation, cultures, a space separator, no separators at all, currency, negatives, big numbers and parsing back.
My sample data is a US retailer’s quarterly sales in dollars, and I ran every snippet with dotnet run on .NET 10.
Format a Number with Commas Using ToString(“N0”) and “N2”
The N format specifier adds group separators, and the digit after it sets the decimal places. Here’s the same revenue figure printed several different ways:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
int unitsSold = 1234567; // Q3 units, all US stores
double revenue = 9876543.219; // Q3 revenue in USD
Console.WriteLine(unitsSold.ToString("N0"));
Console.WriteLine(revenue.ToString("N2"));
Console.WriteLine(revenue.ToString("#,##0.00"));
Console.WriteLine(revenue.ToString("#,##0.##"));
Console.WriteLine($"Revenue: {revenue:N2}");
Console.WriteLine(string.Format("Units: {0:N0}", unitsSold));
Console.WriteLine(revenue.ToString("N"));
var sb = new System.Text.StringBuilder();
sb.AppendFormat("Q3 total: {0:N2} USD", revenue);
Console.WriteLine(sb.ToString());
Output:
1,234,567
9,876,543.22
9,876,543.22
9,876,543.22
Revenue: 9,876,543.22
Units: 1,234,567
9,876,543.219
Q3 total: 9,876,543.22 USD

N0 gives 1,234,567 with no decimals, and N2 rounds 9876543.219 to 9,876,543.22. The custom pattern #,##0.00 prints the same thing, while #,##0.## drops trailing zeros.
The interpolated {revenue:N2} and string.Format("{0:N0}", ...) take the exact same format strings, so pick whichever reads better in your line of code. I compare the other overloads in my guide to the ToString method in C#.
Watch the plain "N" line. It printed three decimals, 9,876,543.219, because the default comes from NumberDecimalDigits, and en-US reports 3 on .NET 10 here. Always write the precision you want.
If you only care about the decimals, not the commas, my post on C# 2 decimal places with F2 and Math.Round goes deeper into rounding.
Convert a Number to a Comma-Separated String with StringBuilder
The last lines show StringBuilder.AppendFormat, which accepts the same {0:N2} item. It’s only worth it when you’re already building a long report, as I explain in string vs StringBuilder in C#.
If you actually want to join several values with commas, like "Texas, Ohio, Utah", that’s a different job. Use string.Join, as shown in how to convert a list to a comma-separated string in C#.
Why Does the C# Thousand Separator Change by Culture?
N0 and #,##0 don’t insert a literal comma. They insert the current culture’s NumberGroupSeparator, using its group sizes. I printed the same decimal in four cultures with the separator’s Unicode code point:
using System.Globalization;
decimal sales = 1234567.89m;
string[] cultures = { "en-US", "en-IN", "de-DE", "fr-FR" };
foreach (string name in cultures)
{
var culture = CultureInfo.GetCultureInfo(name);
string text = sales.ToString("N2", culture);
string sep = culture.NumberFormat.NumberGroupSeparator;
Console.WriteLine($"{name}: {text,-16} group separator U+{(int)sep[0]:X4}");
}
Console.WriteLine($"This PC's culture: {CultureInfo.CurrentCulture.Name} -> {sales:N2}");
Output:
en-US: 1,234,567.89 group separator U+002C
en-IN: 12,34,567.89 group separator U+002C
de-DE: 1.234.567,89 group separator U+002E
fr-FR: 1 234 567,89 group separator U+202F
This PC's culture: en-IN -> 12,34,567.89

This PC is set to en-IN, so without an explicit culture it groups as 12,34,567.89, the Indian lakh style. That’s exactly the kind of surprise you’ll get on a build server, and it’s why the other snippets start with en-US.
de-DE swaps the roles, a dot for groups and a comma for decimals. fr-FR uses U+202F, a narrow no-break space, not the regular space you’d type.
The same rule applies when you read numbers back in. My post on how to convert a string to double in C# shows why parsing needs the matching culture too.
Format a Number with a Space as the Thousand Separator in C#
If you want a plain space, such as 15 750 000, clone a NumberFormatInfo and set NumberGroupSeparator yourself:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
long annualVisitors = 15750000; // yearly store visits, nationwide
decimal revenue = 48250375.5m;
var spaced = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
spaced.NumberGroupSeparator = " ";
Console.WriteLine(annualVisitors.ToString("N0", spaced));
Console.WriteLine(revenue.ToString("N2", spaced));
Console.WriteLine(revenue.ToString("#,##0.00", spaced));
Console.WriteLine(annualVisitors.ToString("N0").Replace(",", " "));
var fr = CultureInfo.GetCultureInfo("fr-FR");
string french = annualVisitors.ToString("N0", fr);
Console.WriteLine($"{french} == \"15 750 000\"? {french == "15 750 000"}");
Output:
15 750 000
48 250 375.50
48 250 375.50
15 750 000
15 750 000 == "15 750 000"? False

The clone matters, because the invariant culture’s own NumberFormat is read-only. Both N0 and #,##0.00 pick up the space, and the decimal point stays a period.
The Replace(",", " ") trick also works for en-US output, but it breaks the moment a culture uses a comma for decimals. I’d keep string surgery like removing a character from a string in C# for text, not numbers.
The last line proves the fr-FR point: its output looks identical on screen, but it isn’t equal to a string typed with normal spaces.
Format Numbers Without Commas or Thousand Separators in C#
You’ll often need digits only, for CSV files, IDs or API payloads. These formats never add a group separator:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
int orders = 1234567;
double revenue = 9876543.2;
Console.WriteLine(orders.ToString("D"));
Console.WriteLine(orders.ToString("D10"));
Console.WriteLine(orders.ToString());
Console.WriteLine(revenue.ToString("F2"));
Console.WriteLine(revenue.ToString("0.##"));
Console.WriteLine(revenue.ToString("F2", CultureInfo.InvariantCulture));
Console.WriteLine(revenue.ToString("N0", CultureInfo.InvariantCulture));
try { Console.WriteLine(revenue.ToString("D")); }
catch (FormatException) { Console.WriteLine("\"D\" on a double throws FormatException"); }
Output:
1234567
0001234567
1234567
9876543.20
9876543.2
9876543.20
9,876,543
"D" on a double throws FormatException
D works on integers only, and D10 pads to ten digits. A double needs F2, 0.## or plain ToString(), and D throws a FormatException on it.
For machine-readable output, I pass CultureInfo.InvariantCulture so the decimal point is always a period. Note that N0 with InvariantCulture still prints 9,876,543, so it doesn’t remove commas.
For fixed-width codes with zeros in front, see how to add leading zeros to a string in C#.
Format Currency, Negative and Large Numbers with Commas
For money, I use decimal with C2, which adds the dollar sign as well as the commas. Here’s a Texas region’s monthly profit, with a loss and a zero month, plus big US numbers:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
decimal[] monthlyProfit = { 1250000.5m, -48210.75m, 0m }; // Texas region, USD
foreach (decimal p in monthlyProfit)
Console.WriteLine($"{p,14:N2} | {p,15:C2} | {p.ToString("#,##0.00;(#,##0.00);-")}");
long usPopulation = 340_110_988;
decimal nationalDebt = 37_640_000_000_000m;
Console.WriteLine(usPopulation.ToString("N0"));
Console.WriteLine(nationalDebt.ToString("C0"));
Console.WriteLine(long.MaxValue.ToString("N0"));
Console.WriteLine(nationalDebt.ToString("$#,##0,,,,.0 T"));
Console.WriteLine(usPopulation.ToString("#,##0,, M"));
Output:
1,250,000.50 | $1,250,000.50 | 1,250,000.50
-48,210.75 | -$48,210.75 | (48,210.75)
0.00 | $0.00 | -
340,110,988
$37,640,000,000,000
9,223,372,036,854,775,807
$37.6 T
340 M

en-US puts the minus before the dollar sign, -$48,210.75. The three-section pattern #,##0.00;(#,##0.00);- prints accounting-style parentheses and a dash for zero.
long and decimal take the same specifiers, so long.MaxValue formats fine. Commas right before the decimal point divide by 1,000 each, which is how $#,##0,,,,.0 T shows the debt in trillions.
The C# data types guide lists each type’s range, and this is the same display code I’d put at the end of a C# compound interest program.
How Do You Parse a Number with Commas Back in C#?
Formatted text doesn’t parse back by default. Pass a NumberStyles value that allows thousands separators, plus the culture that wrote it:
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
var us = CultureInfo.GetCultureInfo("en-US");
decimal total = decimal.Parse("1,234,567.89", NumberStyles.Number, us);
Console.WriteLine(total + 1);
int units = int.Parse("12,500", NumberStyles.AllowThousands, us);
Console.WriteLine(units);
bool ok = int.TryParse("12,500", out int noStyle);
Console.WriteLine($"int.TryParse without AllowThousands: {ok}");
Output:
1234568.89
12500
int.TryParse without AllowThousands: False
NumberStyles.Number includes AllowThousands and AllowDecimalPoint, so the total parses and adding 1 proves it’s a real decimal. Plain int.TryParse("12,500") returns false.
For European input such as "1.234,56", follow my full walkthrough on how to convert a number with a comma into decimal in C#.
Which C# Number Format with Commas Should You Use?
| You want | Use | en-US result |
|---|---|---|
| Whole number with commas | N0 or #,##0 | 1,234,567 |
| Commas and 2 decimals | N2 or #,##0.00 | 9,876,543.22 |
| US dollars | C2 on a decimal | $1,250,000.50 |
| Space separator | cloned NumberFormatInfo + N0 | 15 750 000 |
| No separators | D, F2, 0.## | 1234567 |
| Negatives in parentheses | #,##0.00;(#,##0.00) | (48,210.75) |
A few more number and string conversions I get asked about a lot:
Frequently Asked Questions
How do I format a number with commas in C#?
Call value.ToString("N0", CultureInfo.GetCultureInfo("en-US")) for whole numbers or "N2" for two decimals. 1234567 becomes 1,234,567. The standard numeric format strings page on Microsoft Learn lists every specifier.
What’s the difference between N2 and #,##0.00?
In en-US they print the same result. N2 is a standard format, while #,##0.00 is a custom pattern you can extend with sections for negatives and zero. See the custom numeric format strings reference.
Why does my number show as 12,34,567 instead of 1,234,567?
Your machine’s culture is en-IN, which uses Indian digit grouping. Pass CultureInfo.GetCultureInfo("en-US") or set CultureInfo.CurrentCulture to en-US.
How do I format a number without commas in C#?
Use D for integers, or F2 or 0.## for doubles and decimals. Pass CultureInfo.InvariantCulture when the output goes to a file or an API.
How do I use a space as the thousand separator in C#?
Clone CultureInfo.InvariantCulture.NumberFormat, set NumberGroupSeparator to a space, and pass it to ToString("N0", info). fr-FR uses a narrow no-break space (U+202F) instead.
How do I show negative numbers in parentheses?
Use a two-section custom format such as #,##0.00;(#,##0.00). -48210.75 prints as (48,210.75).
Bijay Kumar is a renowned software engineer, accomplished author, and distinguished Microsoft Most Valuable Professional (MVP) specializing in SharePoint. With a rich professional background spanning over 15 years, Bijay has established himself as an authority in the field of information technology. He possesses unparalleled expertise in multiple programming languages and technologies such as ASP.NET, ASP.NET MVC, C#.NET, and SharePoint, which has enabled him to develop innovative and cutting-edge solutions for clients across the globe. Read more…