Как преобразовать процентную строку в двойную?
У меня есть строка типа "1,5%" и вы хотите преобразовать ее в двойное значение.
Это можно сделать просто следующим образом:
public static double FromPercentageString(this string value)
{
return double.Parse(value.SubString(0, value.Length - 1)) / 100;
}
но я не хочу использовать этот подход синтаксического анализа.
Есть ли другой подход с IFormatProvider или что-то вроде этого?
Ответы
Ответ 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%
Ответ 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)
{
}
Ответ 8
Вы можете проголосовать за это предложение .NET Framework 4 в Microsoft Connect: Расширить double.Parse для интерпретации значений процентов
Ответ 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
Это строка, независимо от того, что вы делаете с ней, чтобы удалить знак%, который вам еще нужно разобрать на двойной.