Format a number with comma separators and variable decimal places.

When I first ran into this problem, I though I was crazy and that I was just missing the obvious answer. But after an hour of Google searching I'm out of ideas.
I'm trying to format a number that the user types in so that it has commas in the right places (at the thousands, millions, etc), but also allow for decimals IF THERE ARE ANY.

So far I've found Decimal.ToString("N"). which works fine for the thousands separator but only allows for a fixed number of decimal points. I need to be able to have no decimal points if the number is an integer, and any number of decimal points is the number is a decimal.

Here are some examples of user input and how I want them to come out:

12 -> 12
12.5 -> 12.5
1000 -> 1,000
1000.5 -> 1,000.5
1000.567 -> 1,000.567
123123123.45678 -> 123,123,123.45678

Is there any way to get the desired format?
BROOKLYN1950Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ElrondCTCommented:
Use Format(number, "#,0.########")

You only need one pound sign to the left of the decimal place, but on the right, you need to include as many as you will potentially want to output. However, any extras won't display, and if there's no decimal component to the number, the decimal point won't display, either.
it_saigeDeveloperCommented:
*No Points* - Proof of Concept:

using System;
using System.Collections.Generic;

namespace EE_Q28630878
{
	class Program
	{
		static readonly List<Decimal> values = new List<Decimal>() { 12, 12.5M, 1000, 1000.5M, 1000.567M, 123123123.45678M };

		static void Main(string[] args)
		{
			foreach (Decimal value in values)
				Console.WriteLine("{0} --> {1}", value, value.ToString("#,0.########"));

			Console.ReadLine();
		}
	}
}

Open in new window

Produces the following output -Capture.JPG
And if you need it in VB.NET -
Module Module1
	ReadOnly values As List(Of Decimal) = New List(Of Decimal)() From {12, 12.5, 1000, 1000.5, 1000.567, 123123123.45678}

	Sub Main()
		For Each value As Decimal In values
			Console.WriteLine("{0} --> {1}", value, value.ToString("#,0.########"))
		Next
		Console.ReadLine()
	End Sub
End Module

Open in new window

Produces the same output as above.

-saige-
BROOKLYN1950Author Commented:
Hi,

Thanks for that answer, I suppose if I put as many "#"s as the Decimal type was able to hold, it would be as dynamic as one with a "variable number of decimal points". However I do want to hold out and see if someone can find an answer that doesn't involve typing a bunch of "#"s.
If no one else can give me a better answer I'll go with the above.
it_saigeDeveloperCommented:
I found a solution.  You could use the Decimal.GetBits method in order to extrapolate the number of decimal places.

According to MSDN -
The binary representation of a Decimal number consists of a 1-bit sign, a 96-bit integer number, and a scaling factor used to divide the integer number and specify what portion of it is a decimal fraction. The scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28.

The return value is a four-element array of 32-bit signed integers.

The first, second, and third elements of the returned array contain the low, middle, and high 32 bits of the 96-bit integer number.

The fourth element of the returned array contains the scale factor and sign. It consists of the following parts:
Bits 0 to 15, the lower word, are unused and must be zero.
Bits 16 to 23 must contain an exponent between 0 and 28, which indicates the power of 10 to divide the integer number.
Bits 24 to 30 are unused and must be zero.
Bit 31 contains the sign; 0 meaning positive, and 1 meaning negative.

Note that the bit representation differentiates between negative and positive zero. These values are treated as being equal in all operations.
What this essentially means is that we could do the following:
C# -
using System;
using System.Collections.Generic;

namespace EE_Q28630878CSharp
{
	class Program
	{
		static readonly List<Decimal> values = new List<Decimal>() { 12, 12.5M, 1000, 1000.5M, 1000.567M, 123123123.45678M };

		static void Main(string[] args)
		{
			foreach (Decimal value in values)
			{
				var bits = Decimal.GetBits(value);
				var decimals = BitConverter.GetBytes(bits[3])[2];
				Console.WriteLine("{0} --> {1}", value, value.ToString("N" + decimals));
			}

			Console.ReadLine();
		}
	}
}

Open in new window

VB.NET -
Imports System.Runtime.CompilerServices

Module Module1
	ReadOnly values As List(Of Decimal) = New List(Of Decimal)() From {12, 12.5, 1000, 1000.5, 1000.567, 123123123.45678}

	Sub Main()
		For Each value As Decimal In values
			Dim bits = Decimal.GetBits(value)
			Dim decimals = BitConverter.GetBytes(bits(3))(2)
			Console.WriteLine("{0} --> {1}", value, value.ToString("N" & decimals))
		Next
		Console.ReadLine()
	End Sub
End Module

Open in new window

Of course, I would probably use this to build an extension method:
C# -
using System;
using System.Collections.Generic;

namespace EE_Q28630878CSharp
{
	class Program
	{
		static readonly List<Decimal> values = new List<Decimal>() { 12, 12.5M, 1000, 1000.5M, 1000.567M, 123123123.45678M };

		static void Main(string[] args)
		{
			foreach (Decimal value in values)
				Console.WriteLine("{0} --> {1}", value, value.Formatted("N"));

			Console.ReadLine();
		}
	}

	static class Extensions
	{
		public static string Formatted(this decimal value, string format)
		{
			var bits = Decimal.GetBits(value);
			var decimals = BitConverter.GetBytes(bits[3])[2];
			return value.ToString(format + decimals);
		}
	}
}

Open in new window

VB.NET -
Imports System.Runtime.CompilerServices

Module Module1
	ReadOnly values As List(Of Decimal) = New List(Of Decimal)() From {12, 12.5, 1000, 1000.5, 1000.567, 123123123.45678}

	Sub Main()
		For Each value As Decimal In values
			Console.WriteLine("{0} --> {1}", value, value.Formatted("N"))
		Next
		Console.ReadLine()
	End Sub
End Module

Module Extensions
	<Extension()> _
	Public Function Formatted(ByVal value As Decimal, ByVal format As String) As String
		Dim bits = Decimal.GetBits(value)
		Dim decimals = BitConverter.GetBytes(bits(3))(2)
		Return value.ToString(format & decimals)
	End Function
End Module

Open in new window

In any case, all of these produce the same results:Capture.JPG-saige-

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
BROOKLYN1950Author Commented:
This gave me even more than I needed. Not only does it give me a way to dynamically format a decimal, it also gives me a method by which I can find the number of decimal points if I ever need to do that in the future. And it provides a way of doing it neatly with an extension method.
All of this along with a Microsoft-quoted explanation on why it works, and with example code.
A perfect solution to a simple problem.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.