It might be the case that you cannot use the culture approach at all, for instance when you want to have a specific formatting independently from any culture or currency. Even @jackjgordon's approach is not reliable, because some cultures have a different number format than currency format, making this inconsistent.
Here is a manual formatter that works without any culture, so you can format your numbers independently.
public static class CustomNumberFormatExtensions
{
static readonly StringBuilder formatTextBuilder = new StringBuilder();
public static string ToCustomFormattedString(this decimal d, int decimalPrecision, string decimalPoint, string groupSeperator = "", int groupLength = 3, MidpointRounding rounding = MidpointRounding.AwayFromZero)
{
lock (formatTextBuilder)
{
formatTextBuilder.Clear();
string rawDigits = Math.Round(d * (decimal)Math.Pow(10, decimalPrecision), 0, rounding).ToString();
rawDigits = rawDigits.PadLeft(decimalPrecision + 1, '0');
if (decimalPrecision > 0)
{
formatTextBuilder.Insert(0, rawDigits.Substring(rawDigits.Length - decimalPrecision));
rawDigits = rawDigits.Substring(0, rawDigits.Length - decimalPrecision);
formatTextBuilder.Insert(0, decimalPoint);
}
while (rawDigits.Length > groupLength)
{
formatTextBuilder.Insert(0, rawDigits.Substring(rawDigits.Length - groupLength));
rawDigits = rawDigits.Substring(0, rawDigits.Length - groupLength);
formatTextBuilder.Insert(0, groupSeperator);
}
return rawDigits + formatTextBuilder.ToString();
}
}
public static string ToCustomFormattedString(this double d, int decimalPrecision, string decimalSeperator, string groupSeperator = "", int groupLength = 3, MidpointRounding rounding = MidpointRounding.AwayFromZero)
{
return ((decimal)d).ToCustomFormattedString(decimalPrecision, decimalSeperator, groupSeperator, groupLength, rounding);
}
}