16

From a XML file I receive decimals on the format:

1.132000
6.000000

Currently I am using Decimal.Parse like this:

decimal myDecimal = Decimal.Parse(node.Element("myElementName").Value, System.Globalization.CultureInfo.InvariantCulture);
  • How do print myDecimal to string to look like below ?

    1.132
    6
    
Prix
  • 19,417
  • 15
  • 73
  • 132
  • 1
    Possible duplicate: http://stackoverflow.com/questions/3104615/best-way-to-display-decimal-without-trailing-zeros-in-c – digEmAll Nov 28 '10 at 20:05
  • indeed it seems to be a duplicated, i was not sure of what to use to search for this, thanks. – Prix Nov 28 '10 at 20:28
  • Notice that @JonSkeet gave you another option with `myDecimal.ToString.TrimEnd( { '0' } );` but that this doesn't work if the string doesn't contain a decimal – jcolebrand Nov 28 '10 at 20:37
  • @drachenstern it is always a string with decimal content or null, when it is null it is not parsed to decimal, thanks for point it out. – Prix Nov 29 '10 at 00:51
  • ~ maybe it is a language issue, I don't know. "contains a decimal" means if the character `.` is in the string ... `6000` does not contain a decimal, whereas `6000.` or `6000.0` do ... I don't know if `decimal myDecimal = 6000; Console.WriteLine(myDecimal.ToString());` shows as any one of thsoe three. – jcolebrand Nov 29 '10 at 01:36

5 Answers5

32

I don't think there are any standard numeric format strings which will always omit trailing insignificant zeroes, I'm afraid.

You could try to write your own decimal normalization method, but it could be quite tricky. With the BigInteger class from .NET 4 it would be reasonably feasible, but without that (or something similar) it would be very hard indeed.

EDIT: Okay, I think this is what you want:

using System;
using System.Numerics;

public static class DecimalExtensions
{
    // Avoiding implicit conversions just for clarity
    private static readonly BigInteger Ten = new BigInteger(10);
    private static readonly BigInteger UInt32Mask = new BigInteger(0xffffffffU);

    public static decimal Normalize(this decimal input)
    {
        unchecked
        {
            int[] bits = decimal.GetBits(input);
            BigInteger mantissa = 
                new BigInteger((uint) bits[0]) +
                (new BigInteger((uint) bits[1]) << 32) +
                (new BigInteger((uint) bits[2]) << 64);

            int sign = bits[3] & int.MinValue;            
            int exponent = (bits[3] & 0xff0000) >> 16;

            // The loop condition here is ugly, because we want
            // to do both the DivRem part and the exponent check :(
            while (exponent > 0)
            {
                BigInteger remainder;
                BigInteger divided = BigInteger.DivRem(mantissa, Ten, out remainder);
                if (remainder != BigInteger.Zero)
                {
                    break;
                }
                exponent--;
                mantissa = divided;
            }
            // Okay, now put it all back together again...
            bits[3] = (exponent << 16) | sign;
            // For each 32 bits, convert the bottom 32 bits into a uint (which won't
            // overflow) and then cast to int (which will respect the bits, which
            // is what we want)
            bits[0] = (int) (uint) (mantissa & UInt32Mask);
            mantissa >>= 32;
            bits[1] = (int) (uint) (mantissa & UInt32Mask);
            mantissa >>= 32;
            bits[2] = (int) (uint) (mantissa & UInt32Mask);

            return new decimal(bits);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Check(6.000m);
            Check(6000m);
            Check(6m);
            Check(60.00m);
            Check(12345.00100m);
            Check(-100.00m);
        }

        static void Check(decimal d)
        {
            Console.WriteLine("Before: {0}  -  after: {1}", d, d.Normalize());
        }
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Just cast to a string and recursively remove right hand zeros before returning, no? – jcolebrand Nov 28 '10 at 20:16
  • @drachenstern: Well you could... but you'd also have to be careful with, say, "6000". – Jon Skeet Nov 28 '10 at 20:25
  • @JonSkeet ~ Very true, I should add that to my answer :p – jcolebrand Nov 28 '10 at 20:26
  • @configurator notice I did that in my example too, where I showed how to make it an extension method. I've started really liking extension methods, but have to wonder about the readability of chaining, especially when an extension returns something different from _this_ – jcolebrand Nov 29 '10 at 16:03
  • 5
    actually there's one format that does just this : toString("G29") – Brann Apr 05 '12 at 14:53
  • 1
    I'm quite pleased with [my answer below](http://stackoverflow.com/a/14243545/1077279) – shamp00 Jan 09 '13 at 18:35
12

This will remove all the trailing zeros from the decimal and then you can just use ToString().

public static class DecimalExtensions
{
    public static Decimal Normalize(this Decimal value)
    {
        return value / 1.000000000000000000000000000000000m;
    }
}

Or alternatively, if you want an exact amount of precision, say 5 decimal places, first Normalize() and then multiply by 1.00000m.

shamp00
  • 11,106
  • 4
  • 38
  • 81
  • 1
    Good method. However, you use 33 zeroes as does [another answer from another thread](http://stackoverflow.com/a/7983330/1336654). As was said there, there is no need for more than 28 zeroes (29 places in total counting the leading `1` as well). – Jeppe Stig Nielsen Aug 26 '13 at 13:37
  • Thanks, just what I was after. – Liam Apr 26 '16 at 15:22
4

This meets the example given but POORLY. (I THINK)

myDecimal.ToString("#.######") 

What other requirements are there? Are you going to manipulate the values and show the manipulated values to this number of decimals?

Alternate answer involves recursiveness, like so:

  //use like so:
  myTextbox.Text = RemoveTrailingZeroes( myDecimal.ToString() );

private string RemoveTrailingZeroes(string input) {
  if ( input.Contains( "." ) && input.Substring( input.Length - 1 ) == "0" ) { //test the last character == "0"
    return RemoveTrailingZeroes( input.Substring( 0, input.Length - 2 ) ) 
    //drop the last character and recurse again
  }
  return input; //else return the original string
}

And if you wanted an extension method, then this is an option

  //use like so:
  myTextbox.Text = myDecimal.ToString().RemoveTrailingZeroes();

private string RemoveTrailingZeroes(this string input) {
  if ( input.Contains( "." ) &&  input.Substring( input.Length - 1 ) == "0" ) { //test the last character == "0"
    return RemoveTrailingZeroes( input.Substring( 0, input.Length - 2 ) ) 
    //drop the last character and recurse again
  }
  return input; //else return the original string
}

Added input.Contains( "." ) && per comment from Jon Skeet, but bear in mind this is going to make this incredibly slow. If you know that you'll always have a decimal and no case like myDecimal = 6000; then you could drop that test, or you could make this into a class and have several private methods based on whether the input contained a decimal, etc. I was going for simplest and "it works" instead of Enterprise FizzBuzz

jcolebrand
  • 15,889
  • 12
  • 75
  • 121
  • What does that give you for an input of "6000"? – Jon Skeet Nov 28 '10 at 20:25
  • You might also consider how the TrimEnd method might make this easier :) – Jon Skeet Nov 28 '10 at 20:27
  • @JonSkeet I edited based on your feedback and yet doesn't TrimEnd fail on `1.02` if given an array of `{ '0' }` – jcolebrand Nov 28 '10 at 20:30
  • @drachenstern: Why would it? It wouldn't trim anything, because the 0 isn't at the end. – Jon Skeet Nov 28 '10 at 20:32
  • @JonSkeet /facepalm ... Additionally, that means you've provided two answers to the same question, no? – jcolebrand Nov 28 '10 at 20:33
  • PS: Yay for things you didn't know existed :| (aka TrimEnd) – jcolebrand Nov 28 '10 at 20:39
  • The recursion in this solution makes me squirm. And now I'm mildly curious as to whether the csharp compiler would magically optimise the tail recursion in this particular case.. – Nick Nov 29 '10 at 03:23
  • If you use "#.0" as your format string you would know there's always a decimal point - then you can remove it if it's the last character. – configurator Nov 29 '10 at 10:46
  • @Configurator wouldn't it truncate any remaining decimals tho, as in the case of `1.024` ? – jcolebrand Nov 29 '10 at 16:00
  • @Nick ~ Yeah, I wasn't too happy with this result, wanted "works" moreso over "elegant" ... Then after Skeet posted once and got like 20 upvotes in a matter of minutes it disheartened me to continue. Also see his comments above, such as TrimEnd which I had never heard of... go figure. – jcolebrand Nov 29 '10 at 16:01
4

If you only need to do this for display then how about using a custom format string?

// decimal has 28/29 significant digits so 30 "#" symbols should be plenty
public static readonly string TrimmedDecimal = "0." + new string('#', 30);

// ...

decimal x = 1.132000m;
Console.WriteLine(x.ToString() + " - " + x.ToString(TrimmedDecimal));  // 1.132

decimal y = 6.000000m;
Console.WriteLine(y.ToString() + " - " + y.ToString(TrimmedDecimal));  // 6
LukeH
  • 263,068
  • 57
  • 365
  • 409
1

What about using toString("G29") on your decimal? this does exactly what you're trying to achieve !

Brann
  • 31,689
  • 32
  • 113
  • 162
  • Need to be careful with this for certain numbers like `0.000010000` scientific notation is used resulting in `1E-05` which breaks the ability to do a round-trip because `decimal.Parse("1E-05")` fails. – Drew Turner Aug 30 '18 at 19:30