可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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:
in addtion to 1.5%
回答2:
It is culture sensitive, replace it like this:
value = value.Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol, "");
Then parse it.
回答3:
Only slightly better, but less error-prone:
public static double FromPercentageString(this string value)
{
return double.Parse(value.Replace("%","")) / 100;
}
回答4:
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 );
}
}
回答5:
You could also combine the top two answers to avoid accepting invalid values while keeping it flexible for different cultures.
var num = double.Parse(value.TrimEnd(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol.ToCharArray() ) ) / 100d;
回答6:
It seems that many answers to this question involve replacing the culture's percentage symbol with the empty string, and then parsing the resulting string as a numeric value.
Perhaps I'm missing something, but there are still some unhandled cases here. Specifically, what happens if the PercentDecimalSeparator
is different to the NumberDecimalSeparator
for the current culture? What happens if the PercentGroupSeparator
is different to the NumberGroupSeparator
for the current culture? What happens if the PercentGroupSizes
are different to the NumberGroupSizes
?
Regardless of whether such a culture practically exists (if it doesn't, it may well come into existence in the future if the formatting for a culture is changed), I think that a better solution to the problem can be found if we consider these additional, special cases.
Here's a code snippet that shows a situation in which the other answers (based only on replacing the percent symbol) will fail, and a suggestion for how it could be done better properly:
// 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;
Yes, the code is a bit longer, and perhaps I'm being pedantic (i.e. maybe such a culture will never actually exist). That said, it seems to me to be a more general solution. Hope it helps somebody :).
回答7:
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)
{
}
回答8:
You might vote for this .NET Framework 4 suggestion on Microsoft Connect: Extend double.Parse to interpret Percent values
回答9:
I'm not sure what it is with all this string replacement, substitution, and converters.
Use the NumberFormat Currency portion, but fill it with the percent formats from your required culture.
// 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:
It's a string, no matter what you do with it to remove the % sign you still have to parse it to a double.