Подтвердить что ты не робот

Как преобразовать процентную строку в двойную?

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

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

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

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

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

4b9b3361

Ответ 1

Если вы беспокоитесь об ошибках форматирования, я бы использовал 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%.5
  • 1. 5%

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

Ответ 2

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

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

Затем проанализируем его.

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

var num = double.Parse(value.TrimEnd(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol.ToCharArray() ) ) / 100d;

Ответ 6

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

Возможно, я что-то упустил, но здесь все еще есть необработанные случаи. В частности, что произойдет, если 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 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;

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

Ответ 7

Отражая в .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)
        {
        }

Ответ 9

Я не уверен, что это со всей этой заменой строки, заменой и конвертерами.

Используйте часть CurrencyFormat 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.
}

Ответ 10

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