【问题标题】:Format decimal value with unusual rules使用异常规则格式化十进制值
【发布时间】:2015-10-11 16:11:28
【问题描述】:

在给定以下规则的情况下,将十进制值转换为字符串的优雅方法是什么?

  1. 显示小数点前的所有数字。
  2. 始终显示逗号来代替小数点。
  3. 如果小数点后的部分不为零,则仅显示有效数字,但最少为 2。

例子:

decimal       string
------------  ----------
500000        500000,
500000.9      500000,90
500000.90     500000,90
500000.900    500000,90
500000.9000   500000,90
500000.99     500000,99
500000.999    500000,999
500000.9999   500000,9999

通过将值转换为int,我可以轻松地显示小数点之前的部分和逗号。但是处理小数点后部分的不同情况会变得冗长乏味。

如果有一种方法可以指定我只想要小数点后的数字,但没有小数点,我就会有这个。 String.Format("{0:.00#}", value) 之类的,只是不显示小数点。

【问题讨论】:

    标签: c# type-conversion decimal string-formatting


    【解决方案1】:

    我不会称之为漂亮,但它属于“它有效”的类别。

    首先是实现,

    public static class FormatProviderExtensions
    {
        public static IFormatProvider GetCustomFormatter(this NumberFormatInfo info, decimal d)
        {
            var truncated = Decimal.Truncate(d);
    
            if (truncated == d)
            {
                return new NumberFormatInfo
                {
                    NumberDecimalDigits = 0,
                    NumberDecimalSeparator = info.NumberDecimalSeparator,
                    NumberGroupSeparator = info.NumberGroupSeparator
                };
            }
    
            // The 4th element contains the exponent of 10 used by decimal's 
            // representation - for more information see
            // https://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx
            var fractionalDigitsCount = BitConverter.GetBytes(Decimal.GetBits(d)[3])[2];
            return fractionalDigitsCount <= 2
                ? new NumberFormatInfo
                {
                    NumberDecimalDigits = 2,
                    NumberDecimalSeparator = info.NumberDecimalSeparator,
                    NumberGroupSeparator = info.NumberGroupSeparator
                }
                : new NumberFormatInfo
                {
                    NumberDecimalDigits = fractionalDigitsCount,
                    NumberDecimalSeparator = info.NumberDecimalSeparator,
                    NumberGroupSeparator = info.NumberGroupSeparator
            };
        }
    }
    

    和示例用法:

    var d = new[] { 500000m, 500000.9m, 500000.99m, 500000.999m, 500000.9999m };
    var info = new NumberFormatInfo { NumberDecimalSeparator = ",", NumberGroupSeparator = "" };
    
    d.ToList().ForEach(x =>
    {
        Console.WriteLine(String.Format(info.GetCustomFormatter(x), "{0:N}", x));
    });
    

    输出:

    500000
    500000,90
    500000,99
    500000,999
    500000,9999
    

    它从现有的NumberFormatInfo 中获取我们关心的属性,并返回一个带有我们想要的NumberDecimalDigits 的新属性。它在丑陋的规模上相当高,但使用起来很简单。

    【讨论】:

    • 这对于生产代码来说看起来很可怕:BitConverter.GetBytes(Decimal.GetBits(d)[3])[2]。我理解你在做什么,但它对一些开发人员来说是不透明的。嗯……
    • 在丑陋的规模上相当高 - ;)
    • 请注意,对于500000.9000m 的输入,此代码生成"500000,9000"。最后的两个额外的零可能是可取的,也可能是不可取的。
    【解决方案2】:

    这是一个简洁明了的解决方案 (.NET Fiddle):

    public static string FormatDecimal(decimal d)
    {
        string s = d.ToString("0.00##", NumberFormatInfo.InvariantInfo).Replace(".", ",");
        if (s.EndsWith(",00", StringComparison.Ordinal))
            s = s.Substring(0, s.Length - 2); // chop off the "00" after integral values
        return s;
    }
    

    如果您的值可能包含多于四个小数位,请根据需要添加额外的 # 字符。格式字符串0.00########################## 有28 个小数位,将容纳所有可能的decimal 值。

    【讨论】:

    • InvariantInfoStringComparison.Ordinal的谨慎使用表示赞赏。
    • P.S.你的回答帮助我创建了这个函数:private static int GetNumberOfUsedDecimalPlaces(decimal value) { return (value - (int)value).ToString("0.############################", NumberFormatInfo.InvariantInfo).Replace(".", "").Length - 1; }
    【解决方案3】:

    不知道您希望它有多优雅,但这是实现您所要求的直接方法。

    List<decimal> decimals = new List<decimal>
    {
        500000M,
        500000.9M,
        500000.99M,
        500000.999M,
        500000.9999M,
        500000.9000M 
    };
    
    foreach (decimal d in decimals)
    {
        string dStr = d.ToString();
        if (!dStr.Contains("."))
        {
            Console.WriteLine(d + ",");
        }
        else
        {
            // Trim any trailing zeroes after the decimal point
            dStr = dStr.TrimEnd('0');
    
            string[] pieces = dStr.Split('.');
            if (pieces[1].Length < 2)
            {
                // Ensure 2 significant digits
                pieces[1] = pieces[1].PadRight(2, '0');
            }
    
            Console.WriteLine(String.Join(",", pieces));
        }
    }
    

    结果:

    500000,
    500000,90
    500000,99
    500000,999
    500000,9999
    500000,90
    

    【讨论】:

    • 请注意,对于500000.9000m 的输入,此代码生成"500000,9000"。最后的两个额外的零可能是可取的,也可能是不可取的。
    • 在小数点上使用普通的ToString() 可能会在不同的文化中产生奇怪的结果,是吗?如果默认区域性是使用逗号作为小数分隔符的区域性,这可以正常工作吗?
    猜你喜欢
    • 2017-10-12
    • 1970-01-01
    • 1970-01-01
    • 2013-07-29
    • 2011-09-24
    • 1970-01-01
    • 2014-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多