Как преобразовать процентную строку в двойную? - PullRequest
32 голосов
/ 31 января 2010

У меня есть строка типа "1.5%", и я хочу преобразовать ее в двойное значение.

Это можно сделать просто с помощью следующего:

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

но я не хочу использовать этот подход анализа.

Есть ли какой-либо другой подход с IFormatProvider или что-то вроде этого?

Ответы [ 10 ]

45 голосов
/ 31 января 2010

Он чувствителен к культуре, замените его следующим образом:

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

Тогда разбери.

44 голосов
/ 31 января 2010

Если вы хотите отловить ошибки форматирования, я бы использовал TrimEnd, а не Replace. Замена позволит передавать ошибки форматирования незамеченными.

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

Это гарантирует, что значением должно быть некоторое десятичное число, за которым следует любое количество пробелов и знаков процента, т. Е. Оно должно начинаться со значения в правильном формате Точнее, вы можете разделить на «%», не удаляя пустые записи, а затем убедитесь, что есть только два результата, а второй - пустой. Первым должно быть значение для преобразования.

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

Использование Replace позволит вам успешно и неправильно IMO анализировать такие вещи, как:

  • % * 1,5 * 1 010
  • 1% 0,5
  • 1% 5

в дополнение к 1.5%

9 голосов
/ 31 января 2010

Только немного лучше, но менее подвержен ошибкам:

public static double FromPercentageString(this string value)
{
    return double.Parse(value.Replace("%","")) / 100;
}
8 голосов
/ 31 января 2010

TypeConverter предоставляет унифицированный способ преобразования типов значений в другие типы, а также для доступа к стандартным значениям и подвойствам. http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter%28VS.80%29.aspx

Это, вероятно, излишне для одноразовых конверсий. Это гораздо более полезно при связывании свойств в ASP.NET или XAML или при разборе файлов конфигурации.

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

Процент и его TypeConverter определены как:

[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 );
    }
}
6 голосов
/ 24 мая 2014

Похоже, что многие ответы на этот вопрос включают замену символа процента культуры пустой строкой, а затем синтаксический анализ полученной строки в виде числового значения.

Возможно, я что-то упускаю, но здесь все еще есть некоторые необработанные случаи. В частности, что произойдет, если PercentDecimalSeparator отличается от NumberDecimalSeparator для текущей культуры? Что произойдет, если PercentGroupSeparator отличается от NumberGroupSeparator для текущей культуры? Что произойдет, если PercentGroupSizes отличается от NumberGroupSizes?

Независимо от того, существует ли такая культура на практике (если она не существует, она вполне может появиться в будущем, если форматирование культуры будет изменено), я думаю, что лучшее решение проблемы можно найти, если мы рассмотрим эти дополнительные, особые случаи.

Вот фрагмент кода, который показывает ситуацию, в которой другие ответы (основанные только на замене символа процента), терпят неудачу, и предложение того, как это можно сделать лучше правильно:

        // Modify a culture so that it has different decimal separators and group separators for numbers and percentages.
        var customCulture = new CultureInfo("en-US")
            {
                NumberFormat = { PercentDecimalSeparator = "PDS", NumberDecimalSeparator = "NDS", PercentGroupSeparator = "PGS", NumberGroupSeparator = "NGS", PercentSymbol = "PS"}
            };
        // Set the current thread's culture to our custom culture
        Thread.CurrentThread.CurrentCulture = customCulture;
        // Create a percentage format string from a decimal value
        var percentStringCustomCulture = 123.45m.ToString("p");
        Console.WriteLine(percentStringCustomCulture); // renders "12PGS345PDS00 PS"
        // Now just replace the percent symbol only, and try to parse as a numeric value (as suggested in the other answers)
        var deceptiveNumericStringInCustomCulture = percentStringCustomCulture.Replace(customCulture.NumberFormat.PercentSymbol, string.Empty);
        // THE FOLLOWING LINE THROWS A FORMATEXCEPTION
        var decimalParsedFromDeceptiveNumericStringInCustomCulture = decimal.Parse(deceptiveNumericStringInCustomCulture); 

        // A better solution...replace the decimal separators and number group separators as well.
        var betterNumericStringInCustomCulture = deceptiveNumericStringInCustomCulture.Replace(customCulture.NumberFormat.PercentDecimalSeparator, customCulture.NumberFormat.NumberDecimalSeparator);
        // Here we mitigates issues potentially caused by group sizes by replacing the group separator by the empty string
        betterNumericStringInCustomCulture = betterNumericStringInCustomCulture.Replace(customCulture.NumberFormat.PercentGroupSeparator, string.Empty); 
        // The following parse then yields the correct result
        var decimalParsedFromBetterNumericStringInCustomCulture = decimal.Parse(betterNumericStringInCustomCulture)/100m;

Да, код немного длиннее, и, возможно, я педантичен (то есть, возможно, такая культура никогда не будет существовать). Тем не менее, мне кажется, это более общее решение. Надеюсь, это кому-нибудь поможет:).

6 голосов
/ 26 октября 2012

Вы также можете объединить два верхних ответа, чтобы избежать принятия недопустимых значений при сохранении гибкости для разных культур.

var num = double.Parse(value.TrimEnd(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol.ToCharArray() ) ) / 100d;
4 голосов
/ 14 марта 2011

Вы можете проголосовать за это предложение .NET Framework 4 для Microsoft Connect: Расширить double.Parse для интерпретации значений процента

4 голосов
/ 20 января 2011

Отражение в .NET 4, вот реализация Microsoft (находится в System.Windows.Documents.ZoomPercentageConverter.ConvertBack). Вы можете изменить это в соответствии с вашими потребностями. Я всегда использую реализацию MS, когда это возможно!

        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)
        {
        }
3 голосов
/ 07 апреля 2016

Я не уверен, что со всеми этими заменами строк, подстановками и преобразователями.

Используйте часть NumberFormat Currency, но заполните ее форматами процентов из требуемой культуры.

// input test value
string value = (.015m).ToString("P", CultureInfo.CurrentCulture);

// set up your format.
double doubleTest;
var numFormat = CultureInfo.CurrentCulture.NumberFormat;

NumberFormatInfo nfi = new NumberFormatInfo()
{
    CurrencyDecimalDigits = numFormat.PercentDecimalDigits,
    CurrencyDecimalSeparator = numFormat.PercentDecimalSeparator,
    CurrencyGroupSeparator = numFormat.PercentGroupSeparator,
    CurrencyGroupSizes = numFormat.PercentGroupSizes,
    CurrencyNegativePattern = numFormat.PercentNegativePattern,
    CurrencyPositivePattern = numFormat.PercentPositivePattern,
    CurrencySymbol = numFormat.PercentSymbol
};

// load it.
if (double.TryParse(value,  NumberStyles.Currency, nfi, out doubleTest))
{
    doubleTest /= 100D;
    // use as required.
}
0 голосов
/ 31 января 2010

Это строка, независимо от того, что вы с ней делаете, чтобы удалить знак%, вам все равно придется разобрать ее на двойное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...