Format decimal value with unusual rules
What's an elegant way to convert a decimal value to a string, given the following rules?
- Display all digits up to the decimal point.
- Display comma invariantly instead of decimal point.
- If the part after the decimal point is nonzero, display only significant digits, but with a minimum number of characters.
Examples:
decimal string
------------ ----------
500000 500000,
500000.9 500000,90
500000.90 500000,90
500000.900 500000,90
500000.9000 500000,90
500000.99 500000,99
500000.999 500000,999
500000.9999 500000,9999
I can easily display the part before the decimal point and comma by converting the value to int
. But it gets long and tedious, handling different cases for the part after the decimal point.
If there was a way to indicate that I only want the digits after the decimal point, but no decimal point, I would have it in my hand. Something like String.Format("{0:.00#}", value)
, just doesn't display the decimal point.
source to share
Here's a short and simple solution ( .NET Fiddle ):
public static string FormatDecimal(decimal d)
{
string s = d.ToString("0.00##", NumberFormatInfo.InvariantInfo).Replace(".", ",");
if (s.EndsWith(",00", StringComparison.Ordinal))
s = s.Substring(0, s.Length - 2); // chop off the "00" after integral values
return s;
}
If your values ββcan contain more than four fractional digits, add additional characters #
as needed. A format string 0.00##########################
that has 28 decimal digits will contain all possible values decimal
.
source to share
I wouldn't call it cute, but it falls under the "it works" category.
Implementation first,
public static class FormatProviderExtensions
{
public static IFormatProvider GetCustomFormatter(this NumberFormatInfo info, decimal d)
{
var truncated = Decimal.Truncate(d);
if (truncated == d)
{
return new NumberFormatInfo
{
NumberDecimalDigits = 0,
NumberDecimalSeparator = info.NumberDecimalSeparator,
NumberGroupSeparator = info.NumberGroupSeparator
};
}
// The 4th element contains the exponent of 10 used by decimal
// representation - for more information see
// https://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx
var fractionalDigitsCount = BitConverter.GetBytes(Decimal.GetBits(d)[3])[2];
return fractionalDigitsCount <= 2
? new NumberFormatInfo
{
NumberDecimalDigits = 2,
NumberDecimalSeparator = info.NumberDecimalSeparator,
NumberGroupSeparator = info.NumberGroupSeparator
}
: new NumberFormatInfo
{
NumberDecimalDigits = fractionalDigitsCount,
NumberDecimalSeparator = info.NumberDecimalSeparator,
NumberGroupSeparator = info.NumberGroupSeparator
};
}
}
and an example of use:
var d = new[] { 500000m, 500000.9m, 500000.99m, 500000.999m, 500000.9999m };
var info = new NumberFormatInfo { NumberDecimalSeparator = ",", NumberGroupSeparator = "" };
d.ToList().ForEach(x =>
{
Console.WriteLine(String.Format(info.GetCustomFormatter(x), "{0:N}", x));
});
Outputs:
500000 500000,90 500000,99 500000,999 500000,9999
It grabs the properties we want from the existing NumberFormatInfo
one and returns the new one with the NumberDecimalDigits
one we want. It's pretty high on an ugly scale, but the usage is simple enough.
source to share
I don't know how elegant you are, but here is a direct way to achieve what you ask.
List<decimal> decimals = new List<decimal>
{
500000M,
500000.9M,
500000.99M,
500000.999M,
500000.9999M,
500000.9000M
};
foreach (decimal d in decimals)
{
string dStr = d.ToString();
if (!dStr.Contains("."))
{
Console.WriteLine(d + ",");
}
else
{
// Trim any trailing zeroes after the decimal point
dStr = dStr.TrimEnd('0');
string[] pieces = dStr.Split('.');
if (pieces[1].Length < 2)
{
// Ensure 2 significant digits
pieces[1] = pieces[1].PadRight(2, '0');
}
Console.WriteLine(String.Join(",", pieces));
}
}
Results:
500000, 500000,90 500000,99 500000,999 500000,9999 500000,90
source to share