如何将百分比字符串转换为双精度浮点数?

46

我有一个字符串,例如"1.5%",想将其转换为double类型的值。

可以通过以下简单方法实现:

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

但我不想使用这种解析方法。

还有其他的方法吗,比如使用IFormatProvider或类似的东西?

10个回答

59

这是与文化相关的,应该像这样替换:

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

然后解析它。


我认为这里存在(技术上)未处理的情况。请查看我的回答。 - sammy34
1
将以下代码用作字符串的扩展方法: public static string RemovePercentageSign(this string str) { return str.Replace(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol, string.Empty); } - Rob Sedgwick

57

如果你关心捕获格式错误,我建议使用 TrimEnd 而不是 Replace。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函数将允许您成功地(虽然在我看来不正确地)解析以下内容:

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

以及1.5%


你可以使用 if (value.EndsWith("%")) ... - Bitterblue
4
有些文化习惯于使用百分比,因此您确实需要CultureInfo.CurrentCulture.NumberFormat。 - Harald Coppoolse

13

稍微好一些,但更少出错的方法:

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

11
TypeConverter提供了一种统一的方式来将值的类型转换为其他类型,以及访问标准值和子属性。这在ASP.NET或XAML中绑定属性或解析配置文件时非常有用。http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter%28VS.80%29.aspx 对于一次性的转换来说可能有些过度。
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
在我个人看来,这是这个问题最好的答案。除了你的代码之外,我还会添加两个隐式转换运算符,以便在将百分比转换为基本类型时,消费代码不那么冗长。一个用于String (static public implicit operator String(Percentage pct) { return pct.ToString(); }),另一个用于Decimal(因为我已经更改了您原始示例,以使用decimal来获得更好的精度)(static public implicit operator Decimal(Percentage pct) { return pct._value; })。 - CARLOS LOTH

7
似乎有许多回答这个问题的方法是用空字符串替换文化中的百分比符号,然后将结果解析为数值。
也许我错过了什么,但还有一些未处理的情况。具体来说,如果百分数小数分隔符与当前文化的数字小数分隔符不同会怎样?如果当前文化的百分组分隔符与数字组分隔符不同会怎样?如果百分组大小与数字组大小不同会怎样?
无论这种文化是否存在(如果不存在,将来更改文化格式可能会创造出这样的文化),我认为如果我们考虑这些附加的特殊情况,可以找到更好的解决方案。
以下是一个代码片段,显示了其他答案(仅基于替换百分号)将失败的情况,并提出了如何更好地解决它的建议:
        // 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;

是的,这段代码有点长,也许我有些迂腐(即使这样的一种文化实际上可能永远不存在)。话虽如此,对我来说,这似乎是一个更通用的解决方案。希望它能帮助到某些人 :)


干得好!这可能会对我有很大帮助。同时,我很惊讶微软证明了decimal.ToString("P2");为什么他们不提供decimal.ParseExact("P2", stringValue)呢? - Pawel Cioch
如果你想考虑各种文化,那么这是最好的选择。备注:"10%%"是什么意思?根据你的代码,它等于0.1,但也可以说它意味着(10%)%,即0.001。建议:只删除第一个百分号并递归调用该函数。但这仍然没有考虑到PercentPositivePattern和PercentNegativePattern,因此,如果你有一个数字中混合了不同文化的百分比模式,你仍然会生成一个数字,就好像它是有效的:“%-10%”。 - Harald Coppoolse
1
如果特朗普明天早上签署行政命令,将%%等同于小数点和分组分隔符,那会怎么样? - Boppity Bop

6
您也可以结合前两个答案,既避免接受无效值,又保持对不同文化的灵活性。
var num = double.Parse(value.TrimEnd(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.PercentSymbol.ToCharArray() ) ) / 100d;

5

我不确定为什么会有这么多关于字符串替换、转换和转换器的内容。

使用NumberFormat货币部分,但用所需文化中的百分比格式填充它。

// 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.
}


1
你需要除以100。 - Erwin Mayer
很棒的小技巧,我喜欢它。我将其反向使用,从而极大地简化了代码,以便从我的 BigDecimal 类中生成百分比格式化值,因为它已经通过利用许多 BCL 功能来生成货币值,所以感谢您的想法! - Mike Marynowski
如果你要反向操作,那么模式需要适当地映射到货币和百分比之间,否则它们不匹配 - 请查看每个模式属性的文档。但是解析并不关心位置,因此模式值是无关紧要的。 - Mike Marynowski

4
在看.NET 4的实现时,这里是微软的实现(可以在System.Windows.Documents.ZoomPercentageConverter.ConvertBack中找到)。您可以修改它以满足您的需求。尽可能使用微软的实现!
        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)
        {
        }

在提及WPF的“ZoomPercentageConverter”时加上+1。这将极大地帮助那些在WPF场景下来到此问答的人们,他们可以直接使用内置转换器而无需编写任何新代码。 - Peter Duniho

4

关闭为“不修复”。令人失望。越快完全开源 .Net 越好。 - kjbartel

-1

这是一个字符串,无论你如何去掉百分号,你仍然需要将它解析为双精度浮点数。


1
@C.Ross,实际上这是一个答案,而且是正确的,如果你真的费心去读问题,你会发现他不想“使用这种解析方法”,而事实上,这是唯一的方法,除非可能等效于Convert.ToDouble。 - Paul Creasey
1
据我理解,OP指的是他在帖子中提到的“特定”解析方法。也就是括号之间的部分 - 而不是parse方法的实际使用。即使您认为自己的贡献是一个答案,不幸的是我发现它并不是很有帮助(-1)。我建议您不要只说“无论您如何处理它以删除%符号”,而是扩展此内容,提供处理%符号的不同选项。这是OP问题的关键,也是我来到这个页面的原因。这也将帮助其他寻找答案的人。 - Ben

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接