开发者

Parse decimal and filter extra 0 on the right?

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);


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());
        }
    }
}


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.


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


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


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜