13

I'd like to convert a decimal to a string, with commas as thousands seperators, and preserve the same precision the decimal was created with. (Will have 2-5 significant digits)

        decimal d = 1234.4500M;

        //I'd like "1,234.4500"

        var notRight = d.ToString("###,###.#######");     //1,234.45
        var alsoNotRight = d.ToString("###,###.00000");;  //1,234.45000
        var notRightEither = d.ToString("N");    //1,234.45
        var notRightEither2 = d.ToString("G");   //1234.45000

Is there no built in way to do this without parsing the string manually? If there is no single format string, what's the easiest way to do this?

foson
  • 10,037
  • 2
  • 35
  • 53
  • I decided to look into how the formatting is done. Unfortunately the heavy lifting is not managed code (unsurprising) looking at the [Standard Numeric Format String](http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx#GFormatString) it looks like only `"G"` (the default) preserves whitespace. – Guvante Jun 14 '12 at 17:30

2 Answers2

13

According to the documentation, a decimal number preserves the trailing zeros. You can display them if you use the "G" specifier or no specifier at all. They are lost when you use one of the specifiers that includes a thousand separator.

If you want to specify the number of trailing zeros when converting to string, you can do it by adding a precision specifier (0 to 99 digits) after the format character, like this:

decimal d=1234.45M;
var numberAsString=d.ToString("N4");

The result will be

 1,234.4500

UPDATE: You can get the number of decimal digits using the Decimal.GetBits method which returns the binary representation of the number. The number of decimals is stored in bits 16-23 (third byte) of the fourth element.

The fourth element of the returned array contains the scale factor and sign. It consists of the following parts:

...

Bits 16 to 23 must contain an exponent between 0 and 28, which indicates the power of 10 to divide the integer number.

Getting a string representation using all digits can be done like this:

decimal d=1234.45000M;
var nums=Decimal.GetBits(d);
var decimals=BitConverter.GetBytes(nums[3])[2];
var formatStr="N"+decimals;
d.ToString(formatStr);

which will produce

1,234.45000
foson
  • 10,037
  • 2
  • 35
  • 53
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • You are partially incorrect. Although the debugger will show that the decimal's value equals 1234.45, the trailing 0's are preserved. Try the following code for example: `decimal d = 1234.4500M;` `Console.WriteLine(d); // outputs 1234.4500`. – Jon Senchyna Jun 14 '12 at 17:02
  • 1
    Your answer only covers the example where the number has exactly 4 decimals. I believe he's trying to preserve the original number of decimals (2-5). – Jon Senchyna Jun 14 '12 at 17:05
  • 1
    The format is specified as a number divided by a power of 10. For this reason it can store 12345/100 or 1234500/10000 distinctively. – Guvante Jun 14 '12 at 17:16
  • Thanks for the background information Guvante. I didn't know that! Now I know why it keeps trailing 0's. – Jon Senchyna Jun 14 '12 at 17:19
  • Updated to show how to extract the number of digits from the binary representation – Panagiotis Kanavos Jun 14 '12 at 17:30
  • @PanagiotisKanavos: I doubt the OP will run into the issue, but technically you are dropping information, excluding the mid and upper ints could cause problems. – Guvante Jun 14 '12 at 17:33
  • There is no such issue. The documentation specifies that the number of digits (specifically, the power of 10 to divide the integer) is stored in bits 16-23 of the third element ONLY. – Panagiotis Kanavos Jun 14 '12 at 17:35
  • While code is more complicated than Jon Senchyna's answer, its also more complete, works for my case (0-5 decimal), and runs 2x as fast as the string split method. – foson Jun 14 '12 at 19:27
1

Since you plan on having a variable number of decimal places (2-5), I don't think you can pull it off via a string format.

This solution is not necessarilly pretty, but it gets the job done. Do note that it will allocate several strings in the process (I believe 5), so it may not perform well in large-scale use. You will retain the number of decimal places, and get comma separated groups in the portion before the decimal.

public static string FormatDecimal(decimal d)
{
    return d.ToString("N0") + // Format portion before decimal
           "." + 
           d.ToString().Split('.')[1];  // Retain number of decimal places
}
Jon Senchyna
  • 7,867
  • 2
  • 26
  • 46
  • You can extract the number of digits from the binary representation of the decimal and construct a format string with the correct number of digits – Panagiotis Kanavos Jun 14 '12 at 17:31
  • You are also using culture-specific separators, which means your code will break if used in a culture that uses ',' for decimals, like France, Germany, Spain, Italy, Greece,South Africa – Panagiotis Kanavos Jun 14 '12 at 17:41
  • I like it -- its easy enough to understand the code from a glance. Though you could get an index out of bounds if the value has no decimal component (like 5M) – foson Jun 14 '12 at 18:36
  • Very true. I was trying to come up with a least a starting point for the solution. It definitely is not globalized and does not handle numbers with no decimals (OP's use case was 2-5 decimals). It looks like Panagiotis has already gone ahead and come up with a full solution though, so I won't update this any further. – Jon Senchyna Jun 14 '12 at 18:42