How to convert percentage string to double?

Viewed 62006

I have a string like "1.5%" and want to convert it to double value.

It can be done simple with following:

public static double FromPercentageString(this string value)
{
    return double.Parse(value.SubString(0, value.Length - 1)) / 100;
}

but I don't want to use this parsing approach.

Is any other approach with IFormatProvider or something like this?

10 Answers

It is culture sensitive, replace it like this:

  value = value.Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol, "");

Then parse it.

If you care about catching formatting errors, I would use TrimEnd rather than Replace. Replace would allow formatting errors to pass undetected.

var num = decimal.Parse( value.TrimEnd( new char[] { '%', ' ' } ) ) / 100M;

This will ensure that the value must be some decimal number followed by any number of spaces and percent signs, i.e, it must at least start with a value in the proper format. To be more precise you might want to split on '%', not removing empty entries, then make sure that there are only two results and the second is empty. The first should be the value to convert.

var pieces = value.Split( '%' );
if (pieces.Length > 2  || !string.IsNullOrEmpty(pieces[1]))
{ 
    ... some error handling ... 
}
var num = decimal.Parse( pieces[0] ) / 100M;

Using Replace will allow you to successfully, and wrongfully IMO, parse things like:

  • %1.5
  • 1%.5
  • 1.%5

in addtion to 1.5%

Only slightly better, but less error-prone:

public static double FromPercentageString(this string value)
{
    return double.Parse(value.Replace("%","")) / 100;
}

TypeConverter provides a unified way of converting types of values to other types, as well as for accessing standard values and subproperties. http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter%28VS.80%29.aspx

This is probably overkill for one-off conversions. It is far more useful when binding properties in ASP.NET or XAML, or when parsing config files.

var result = new Percentage("1.5%");
double d = result.Value;

Percentage and its TypeConverter are defined as:

[TypeConverter(typeof(PercentageConverter))]
public struct Percentage
{
    public double Value;

    public Percentage( double value )
    {
        Value = value;
    }

    public Percentage( string value )
    {
        var pct = (Percentage) TypeDescriptor.GetConverter(GetType()).ConvertFromString(value);
        Value = pct.Value;
    }

    public override string ToString()
    {
        return ToString(CultureInfo.InvariantCulture);
    }

    public string ToString(CultureInfo Culture)
    {
        return TypeDescriptor.GetConverter(GetType()).ConvertToString(null, Culture, this);
    }
}

public class PercentageConverter : TypeConverter
{
    static TypeConverter conv = TypeDescriptor.GetConverter(typeof(double));

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return conv.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(Percentage)) {
            return true;
        }

        return conv.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value == null) {
            return new Percentage();
        }

        if (value is string) {
            string s = value as string;
            s = s.TrimEnd(' ', '\t', '\r', '\n');

            var percentage = s.EndsWith(culture.NumberFormat.PercentSymbol);
            if (percentage) {
                s = s.Substring(0, s.Length - culture.NumberFormat.PercentSymbol.Length);
            }

            double result = (double) conv.ConvertFromString(s);
            if (percentage) {
                result /= 100;
            }

            return new Percentage(result);
        }

        return new Percentage( (double) conv.ConvertFrom( context, culture, value ));
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (!(value is Percentage)) {
            throw new ArgumentNullException("value");
        }

        var pct = (Percentage) value;

        if (destinationType == typeof(string)) {
            return conv.ConvertTo( context, culture, pct.Value * 100, destinationType ) + culture.NumberFormat.PercentSymbol;
        }

        return conv.ConvertTo( context, culture, pct.Value, destinationType );
    }
}

Reflecting into .NET 4, here is Microsoft's implementation (found in System.Windows.Documents.ZoomPercentageConverter.ConvertBack). You can modify this to suit your needs. I alway's use MS's implementation when possible!

        try
        {
            string str = (string) value;
            if ((culture != null) && !string.IsNullOrEmpty(str))
            {
                str = ((string) value).Trim();
                if ((!culture.IsNeutralCulture && (str.Length > 0)) && (culture.NumberFormat != null))
                {
                    switch (culture.NumberFormat.PercentPositivePattern)
                    {
                        case 0:
                        case 1:
                            if ((str.Length - 1) == str.LastIndexOf(culture.NumberFormat.PercentSymbol, StringComparison.CurrentCultureIgnoreCase))
                            {
                                str = str.Substring(0, str.Length - 1);
                            }
                            break;

                        case 2:
                            if (str.IndexOf(culture.NumberFormat.PercentSymbol, StringComparison.CurrentCultureIgnoreCase) == 0)
                            {
                                str = str.Substring(1);
                            }
                            break;
                    }
                }
                num = Convert.ToDouble(str, culture);
                flag = true;
            }
        }
        catch (ArgumentOutOfRangeException)
        {
        }
        catch (ArgumentNullException)
        {
        }
        catch (FormatException)
        {
        }
        catch (OverflowException)
        {
        }

It's a string, no matter what you do with it to remove the % sign you still have to parse it to a double.

Related