像 Stack Overflow 一样格式化数字(四舍五入到千位,带 K 后缀)

问题描述 投票:0回答:17

如何用C#格式化像SO这样的数字?

10
500
5k
42k
、...

c# .net formatting numbers number-formatting
17个回答
42
投票

像这样:(编辑:已测试)

static string FormatNumber(int num) {
    if (num >= 100000)
        return FormatNumber(num / 1000) + "K";

    if (num >= 10000)
        return (num / 1000D).ToString("0.#") + "K";

    return num.ToString("#,0");
}

示例:

  • 1 =>
    1
  • 23 =>
    23
  • 136 =>
    136
  • 6968 =>
    6,968
  • 23067 =>
    23.1K
  • 133031 =>
    133K

请注意,这会给 >= 108 的数字提供奇怪的值。
例如,

12345678
变为
12.3KK


32
投票

下面的代码测试了int.MaxValue 这不是最漂亮的代码,但却是最高效的。 但您可以将其用作:

123.KiloFormat(); 4332.KiloFormat(); 2332124.KiloFormat(); int.MaxValue.KiloFormat(); (int1 - int2 * int3).KiloFormat();

等等...

public static class Extensions
{
    public static string KiloFormat(this int num)
    {
        if (num >= 100000000)
            return (num / 1000000).ToString("#,0M");

        if (num >= 10000000)
            return (num / 1000000).ToString("0.#") + "M";

        if (num >= 100000)
            return (num / 1000).ToString("#,0K");

        if (num >= 10000)
            return (num / 1000).ToString("0.#") + "K";

        return num.ToString("#,0");
    } 
}

20
投票

您可以像这样创建一个 CustomFormater:

public class KiloFormatter: ICustomFormatter, IFormatProvider
{
    public object GetFormat(Type formatType)
    {
        return (formatType == typeof(ICustomFormatter)) ? this : null;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        if (format == null || !format.Trim().StartsWith("K")) {
            if (arg is IFormattable) {
                return ((IFormattable)arg).ToString(format, formatProvider);
            }
            return arg.ToString();
        }

        decimal value = Convert.ToDecimal(arg);

        //  Here's is where you format your number

        if (value > 1000) {
            return (value / 1000).ToString() + "k";
        }

        return value.ToString();
    }
}

并像这样使用它:

String.Format(new KiloFormatter(), "{0:K}", 15600);

编辑:将CurrencyFormatter重命名为KiloFormatter


18
投票

SLaks 代码的稍微修改版本

static string FormatNumber(long num)
{
    if (num >= 100000000) {
        return (num / 1000000D).ToString("0.#M");
    }
    if (num >= 1000000) {
        return (num / 1000000D).ToString("0.##M");
    }
    if (num >= 100000) {
        return (num / 1000D).ToString("0.#k");
    }
    if (num >= 10000) {
        return (num / 1000D).ToString("0.##k");
    }

    return num.ToString("#,0");
}

这将返回以下值:

 123        ->  123
 1234       ->  1,234
 12345      ->  12.35k
 123456     ->  123.4k
 1234567    ->  1.23M
 12345678   ->  12.35M
 123456789  ->  123.5M

14
投票

我只是写了一些来提供完整的信息

public static class SIPrefix
{
    private static List<SIPrefixInfo> _SIPrefixInfoList = new
        List<SIPrefixInfo>();

    static SIPrefix()
    {
        _SIPrefixInfoList = new List<SIPrefixInfo>();
        LoadSIPrefix();
    }

    public static List<SIPrefixInfo> SIPrefixInfoList
    { 
        get 
        { 
            SIPrefixInfo[] siPrefixInfoList = new SIPrefixInfo[6];
            _SIPrefixInfoList.CopyTo(siPrefixInfoList);
            return siPrefixInfoList.ToList();
        }
    }

    private static void LoadSIPrefix()
    {
        _SIPrefixInfoList.AddRange(new SIPrefixInfo[]{
            new SIPrefixInfo() {Symbol = "Y", Prefix = "yotta", Example = 1000000000000000000000000.00M, ZeroLength = 24, ShortScaleName = "Septillion", LongScaleName = "Quadrillion"},
            new SIPrefixInfo() {Symbol = "Z", Prefix = "zetta", Example = 1000000000000000000000M, ZeroLength = 21, ShortScaleName = "Sextillion", LongScaleName = "Trilliard"},
            new SIPrefixInfo() {Symbol = "E", Prefix = "exa", Example = 1000000000000000000M, ZeroLength = 18, ShortScaleName = "Quintillion", LongScaleName = "Trillion"},
            new SIPrefixInfo() {Symbol = "P", Prefix = "peta", Example = 1000000000000000M, ZeroLength = 15, ShortScaleName = "Quadrillion", LongScaleName = "Billiard"},
            new SIPrefixInfo() {Symbol = "T", Prefix = "tera", Example = 1000000000000M, ZeroLength = 12, ShortScaleName = "Trillion", LongScaleName = "Billion"},
            new SIPrefixInfo() {Symbol = "G", Prefix = "giga", Example = 1000000000M, ZeroLength = 9, ShortScaleName = "Billion", LongScaleName = "Milliard"},
            new SIPrefixInfo() {Symbol = "M", Prefix = "mega", Example = 1000000M, ZeroLength = 6, ShortScaleName = "Million", LongScaleName = "Million"},
            new SIPrefixInfo() {Symbol = "K", Prefix = "kilo", Example = 1000M, ZeroLength = 3, ShortScaleName = "Thousand", LongScaleName = "Thousand"},
            new SIPrefixInfo() {Symbol = "h", Prefix = "hecto", Example = 100M, ZeroLength = 2, ShortScaleName = "Hundred", LongScaleName = "Hundred"},
            new SIPrefixInfo() {Symbol = "da", Prefix = "deca", Example = 10M, ZeroLength = 1, ShortScaleName = "Ten", LongScaleName = "Ten"},
            new SIPrefixInfo() {Symbol = "", Prefix = "", Example = 1M, ZeroLength = 0, ShortScaleName = "One", LongScaleName = "One"},
        });
    }

    public static SIPrefixInfo GetInfo(long amount, int decimals)
    {
        return GetInfo(Convert.ToDecimal(amount), decimals);
    }

    public static SIPrefixInfo GetInfo(decimal amount, int decimals)
    {
        SIPrefixInfo siPrefixInfo = null;
        decimal amountToTest = Math.Abs(amount);

        var amountLength = amountToTest.ToString("0").Length;
        if(amountLength < 3)
        {
            siPrefixInfo = _SIPrefixInfoList.Find(i => i.ZeroLength == amountLength).Clone() as SIPrefixInfo;
            siPrefixInfo.AmountWithPrefix =  Math.Round(amount, decimals).ToString();

            return siPrefixInfo;
        }

        siPrefixInfo = _SIPrefixInfoList.Find(i => amountToTest > i.Example).Clone() as SIPrefixInfo;

        siPrefixInfo.AmountWithPrefix = Math.Round(
            amountToTest / Convert.ToDecimal(siPrefixInfo.Example), decimals).ToString()
                                        + siPrefixInfo.Symbol;

        return siPrefixInfo;
    }
}

public class SIPrefixInfo : ICloneable
{
    public string Symbol { get; set; }
    public decimal Example { get; set; }
    public string Prefix { get; set; }
    public int ZeroLength { get; set; }
    public string ShortScaleName { get; set; }
    public string LongScaleName { get; set; }
    public string AmountWithPrefix { get; set; }

    public object Clone()
    {
        return new SIPrefixInfo()
                            {
                                Example = this.Example,
                                LongScaleName = this.LongScaleName,
                                ShortScaleName = this.ShortScaleName,
                                Symbol = this.Symbol,
                                Prefix = this.Prefix,
                                ZeroLength = this.ZeroLength
                            };

    }
}

用途:

var amountInfo = SIPrefix.GetInfo(10250, 2);
var amountInfo2 = SIPrefix.GetInfo(2500000, 0);

amountInfo.AmountWithPrefix // 10.25K
amountInfo2.AmountWithPrefix // 2M

4
投票

C#8+ 的解决方案

 public static string ToKiloFormat(this int num)
    {
        return num switch
        {
            >= 1000000000000 => (num / 1000000000000D).ToString("0.#T"),
            >= 100000000000 => (num / 1000000000000D).ToString("0.##T"),
            >= 10000000000 => (num / 1000000000D).ToString("0.#G"),
            >= 1000000000 => (num / 1000000000D).ToString("0.##G"),
            >= 100000000 => (num / 1000000D).ToString("0.#M"),
            >= 1000000 => (num / 1000000D).ToString("0.##M"),
            >= 100000 => (num / 1000D).ToString("0.#k"),
            >= 10000 => (num / 1000D).ToString("0.##k"),
            _ => num.ToString("#,0")
        };
    }

根据之前的回复进行修改


3
投票

我有点晚了,但想到了这个:

`private static List<Tuple<int, string>> ZeroesAndLetters = new List<Tuple<int, string>>()
{
    new Tuple<int, string>(15, "Q"),
    new Tuple<int, string>(12, "T"),
    new Tuple<int, string>(9, "B"),
    new Tuple<int, string>(6, "M"),
    new Tuple<int, string>(3, "K"),
};

public static string GetPointsShortened(ulong num)
{
    int zeroCount = num.ToString().Length;
    for (int i = 0; i < ZeroesAndLetters.Count; i++)
        if (zeroCount >= ZeroesAndLetters[i].Item1)
            return (num / Math.Pow(10, ZeroesAndLetters[i].Item1)).ToString() + ZeroesAndLetters[i].Item2;
    return num.ToString();
}`

ZeroesAndLetters列表中只需添加您需要的数字。

15、12、9 和 ... 是零的数量,Q、T、B 和 ... 是缩写名称。


2
投票

我知道这是一个非常古老的线程,但我认为更通用的答案可能会有用:

ADD:忽略了本来应该是C#,所以这里翻译一下:

    //--Formats the number after scaling by factors of 1000, and appends a metric unit prefix (e.g. M for *1000000)
    //--Mask, Prov are the standard ToString() parameters (after metric scaling has been performed)
    //--MinPow10 (/Max) should be multipla of 3 and a usually a negative (/Positve) number or zero, if say +9 is used, all is in G or above (/below)
    //--SwitchLimit usualy 1, but could be say 10 or 100 with few/zero decimals, The limit at which to switch prefix, if say 33 then 33000000->33M but 32900000->32900K
    static string FormatMetricPrefix(double Input, String Mask ="F2", IFormatProvider Prov=null, int MinPow10 =-24, int MaxPow10 =24, int SwitchLimit =1) {
        string Prefixes ="yzafpnμm KMGTPEZY";
        int idx=9;
        double tmp=Input;
        if (Input!=0.0) {
            if (+24<MaxPow10)MaxPow10=+24;
            if (MinPow10<-24)MaxPow10=-24;
            idx=(int)Math.Truncate(9.0+Math.Log(Math.Abs(Input/SwitchLimit))/Math.Log(1000.0));
            if (idx<9+(MinPow10/3)) idx=9+(MinPow10/3); // below lower limit
            if (9+(MaxPow10/3)<idx) idx=9+(MaxPow10/3); // Above upper limit
            if (idx<=9)tmp *=Math.Pow(1000.0,9-idx);
            if (9<idx) tmp /=Math.Pow(1000.0,idx-9);
        }
        if (Prov==null)Prov=CultureInfo.InvariantCulture;
        return tmp.ToString(Mask,Prov)+Prefixes.Substring(idx-1,1).Trim();
    }

    static string FormatMetricPrefixF2DK(double Input){return FormatMetricPrefix(Input, Prov:CultureInfo.GetCultureInfo("da-DK"));}
    static string FormatMetricPrefixF2US(double Input){return FormatMetricPrefix(Input, Prov:CultureInfo.GetCultureInfo("en-US"));}
    static string FormatMetricPrefixF0DK(double Input){return FormatMetricPrefix(Input, Mask:"F0", MinPow10:0, Prov:CultureInfo.GetCultureInfo("da-DK"),SwitchLimit:100);}
    static string FormatMetricPrefixF0US(double Input){return FormatMetricPrefix(Input, Mask:"F0", MinPow10:0, Prov:CultureInfo.GetCultureInfo("en-US"),SwitchLimit:100);}
    static void Main(string[] args)
    {
        Console.WriteLine(FormatMetricPrefixF2US(1.234567890E+27));
        Console.WriteLine(FormatMetricPrefixF2US(1234567890));
        Console.WriteLine(FormatMetricPrefixF2US(0.01234567890));
        Console.WriteLine(FormatMetricPrefixF2US(0.00000001234567890));
        Console.WriteLine(FormatMetricPrefixF2US(1.234567890E-26));
        Console.WriteLine(FormatMetricPrefixF0US(0.5));
        Console.WriteLine(FormatMetricPrefixF0US(2));
        Console.WriteLine(FormatMetricPrefixF0US(20000));
        Console.WriteLine(FormatMetricPrefixF0US(87654321));

    }

这是 SQL 的:

--Formats the number after scaling by factors of 1000, and appends a metric unit prefix (e.g. M for *1000000)
--@Mask, @Cult are the standard FORMAT parameters (after metric scaling has been performed)
--@MinPow10 (/Max) should be multipla of 3 and a usually a negative (/Positve) number or zero, if say +9 is used, all is in G or above (/below)
--@SwitchLimit usualy 1, but could be say 10 or 100 with few/zero decimals, The limit at which to switch prefix, if say 33 then 33000000->33M but 32900000->32900K
CREATE function FormatMetricPrefix(@Input float, @Mask Varchar(22)='F2', @Cult Varchar(9)='en-us', @MinPow10 int =-24, @MaxPow10 int =24, @SwitchLimit int=1) returns Varchar(99) as
Begin
  Declare @Prefixes Varchar(17)='yzafpnμm KMGTPEZY'
  Declare @idx int = 9
  Declare @tmp float=@input
  if @Input<>0.0
  begin
    if +24<@MaxPow10 set @MaxPow10=+24 --highest limit is y 10^24
    if @MinPow10<-24 set @MinPow10=-24 --lowest limit is y 10^-24
    set @idx=9.0+Log(Abs(@input/@SwitchLimit))/Log(1000)
    If @idx<9+(@MinPow10/3) set @idx=9+(@MinPow10/3) -- below lower limit
    If 9+(@MaxPow10/3)<@idx set @idx=9+(@MaxPow10/3) --above upper limit
    if @idx<=9set @tmp=@tmp*POWER(1000.0,9-@idx)
    if 9<@idx set @tmp=@tmp/POWER(1000.0,@idx-9)
  end
  Return FORMAT(@tmp,@mask,@Cult)+LTrim(Substring(@Prefixes,@idx,1))
end
GO

然后也许是一些相关的包装函数来简化使用,例如

CREATE function FormatMetricPrefixF2US(@Input float) returns Varchar(99) as
begin
    return dbo.FormatMetricPrefix(@Input, default,default,default,default,default)
end
GO
CREATE function FormatMetricPrefixF0US(@Input float) returns Varchar(99) as
begin
    return dbo.FormatMetricPrefix(@Input,  'F0'  ,default,   0   ,default,  100  )
end
GO

还有一堆测试/示例:

      Select  0, dbo.FormatMetricPrefixF2US(1.234567890E+27)+'g'
union Select  1, dbo.FormatMetricPrefixF2US(1234567890)+'g'
union Select  2, dbo.FormatMetricPrefixF2US(123456789.0)+'g'
union Select  3, dbo.FormatMetricPrefixF2US(12345678.90)+'g'
union Select  4, dbo.FormatMetricPrefixF2US(1234567.890)+'g'
union Select  5, dbo.FormatMetricPrefixF2US(123456.7890)+'g'
union Select  6, dbo.FormatMetricPrefixF2US(12345.67890)+'g'
union Select  7, dbo.FormatMetricPrefixF2US(1234.567890)+'g'
union Select  8, dbo.FormatMetricPrefixF2US(123.4567890)+'g'
union Select  9, dbo.FormatMetricPrefixF2US(12.34567890)+'g'
union Select 10, dbo.FormatMetricPrefixF2US(1.234567890)+'g'
union Select 11, dbo.FormatMetricPrefixF2US(0.1234567890)+'g'
union Select 12, dbo.FormatMetricPrefixF2US(0.01234567890)+'g'
union Select 13, dbo.FormatMetricPrefixF2US(0.001234567890)+'g'
union Select 14, dbo.FormatMetricPrefixF2US(0.0001234567890)+'g'
union Select 15, dbo.FormatMetricPrefixF2US(0.00001234567890)+'g'
union Select 16, dbo.FormatMetricPrefixF2US(0.000001234567890)+'g'
union Select 17, dbo.FormatMetricPrefixF2US(0.0000001234567890)+'g'
union Select 18, dbo.FormatMetricPrefixF2US(0.00000001234567890)+'g'
union Select 19, dbo.FormatMetricPrefixF2US(1.234567890E-26)+'g'
union Select 20, dbo.FormatMetricPrefixF0US(0.5)
union Select 20, dbo.FormatMetricPrefixF0US(2)
union Select 21, dbo.FormatMetricPrefixF0US(20000)
union Select 22, dbo.FormatMetricPrefixF0US(87654321)

2
投票

我写了这个方法来缩小

long
数字:

public string minifyLong(long value)
    {
        if (value >= 100000000000)
            return (value / 1000000000).ToString("#,0") + " B";
        if (value >= 10000000000)
            return (value / 1000000000D).ToString("0.#") + " B";
        if (value >= 100000000)
            return (value / 1000000).ToString("#,0") + " M";
        if (value >= 10000000)
            return (value / 1000000D).ToString("0.#") + " M";
        if (value >= 100000)
            return (value / 1000).ToString("#,0") + " K";
        if (value >= 10000)
            return (value / 1000D).ToString("0.#") + " K";
        return value.ToString("#,0"); 
    }

1
投票
public static string KFormatter(long num)
{
  if (num >= 1000000000) { return (num / 1000000000D).ToString("0.#", CultureInfo.InvariantCulture).TrimStart(new char[] { '0' }) + "B"; }
  if (num >= 1000000) { return (num / 1000000D).ToString("0.#", CultureInfo.InvariantCulture).TrimStart(new char[] { '0' }) + "M"; }
  if (num >= 10000) { return (num / 1000D).ToString("0.#", CultureInfo.InvariantCulture).TrimStart(new char[] { '0' }) + "K"; }
  return num.ToString("0,#", CultureInfo.InvariantCulture).TrimStart(new char[] { '0' });
}

0
投票

类似这样的:

string formatted;
if (num >= 1000) {
    formatted = ((double)num / 1000.0).ToString("N1") + "k";
} else {
    formatted = num.ToString("N0");
}

0
投票

如果数字大于某个阈值,请将其除以 1000,然后将其格式化为所需的小数位数。

int input = 12392; // for example

if (input >= 10000)
{
    double thousands = input/1000.0;
    Console.WriteLine(string.Format("{0}K", thousands));
}

0
投票
public static class NumberDisplayHelper
{
    public static string KiloFormat(this decimal number)
    {
        return number >= 1000
            ? $"{(number / 1000):0.##}K"
            : number.ToString(CultureInfo.CurrentCulture);
    }

    public static string KiloFormat(this int number)
    {
        return number >= 1000
            ? $"{((decimal)number / 1000):0.##}K"
            : number.ToString();
    }
}

[Test()]
public void KiloFormatter()
{
    Assert.AreEqual("900", 900m.KiloFormat());
    Assert.AreEqual("1,2K", 1203m.KiloFormat());
    Assert.AreEqual("1,59K", 1588.84m.KiloFormat());
    Assert.AreEqual("1,52K", 1522.84m.KiloFormat());
    Assert.AreEqual("589", 589.KiloFormat());
    Assert.AreEqual("1K", 1001.KiloFormat());
    Assert.AreEqual("1,46K", 1455.KiloFormat());
    Assert.AreEqual("1K", 1000m.KiloFormat());
}

0
投票

这是经过适度测试的解决方案带有舍入。它保留了

meaningfulChars
逗号或点后的数字,并使用
CultureInfo
来确定小数点分隔符。

在撰写本文时,我开始了解到大多数情况下不需要舍入。因此,如果您不需要舍入,您最好使用其他方法或扩展此解决方案。

我试图确保精度不会被浮点运算吃掉,但我认为对于非常大的数字可能会发生这种情况。

我也相信这可以通过

IFormatProvider
或来自 .NET FW 的类似内在机制来改进。

public static class ConversionUtilities {

    class ConversionGroup {
        public ConversionGroup(char symbol, long divider, int power) {
            Symbol = symbol;
            Divider = divider;
            Power = power;
        }

        public char Symbol;
        public long Divider;
        public int Power;
    }

    private static ConversionGroup[] _conversionGroups = {
        new ConversionGroup('k', 1000L, 3), 
        new ConversionGroup('m', 1000000L, 6),
        new ConversionGroup('b', 1000000000L, 9)
    };

    /// <summary>
    /// Converts integer value into string substituting 1 000 -> 1K, 1 000 000 -> 1M, 1 000 000 000 -> 1B
    /// Rounds to meaingful chars
    /// </summary>
    /// <param name="value">value to convert</param>
    /// <param name="meaninfulChars">round to that many characters after comma</param>
    /// <param name="uppercase">when set K and M letters will be uppercase</param>
    /// <returns></returns>
    public static string ConvertIntToShortStringForm(long value, int meaninfulChars, bool uppercase) {
        string ret = "";
        long remainder = 0;
        long fraction = 0;
        char symbol = '\0';
        int maxChars = meaninfulChars;

        for (int i = _conversionGroups.Length - 1; i >= 0; i--) {
            var group = _conversionGroups[i];
            if (value >= group.Divider) {
                if (meaninfulChars < 0) {
                    maxChars = 0;
                }
                else if (meaninfulChars > group.Power) {
                    maxChars = group.Power;
                }

                // int maxChars = Math.Clamp(meaninfulChars, 0, group.Power);
                remainder = value % group.Divider;
                if (remainder != 0) {
                    fraction = (long)Math.Round((double)remainder / (Math.Pow(10.0, (double)(group.Power - maxChars))));
                    if (maxChars == 0) {
                        if (value / group.Divider + fraction == 1000) {
                            if (i + 1 == _conversionGroups.Length) {
                                symbol = group.Symbol;
                                if (uppercase) {
                                    symbol = Char.ToUpper(symbol);
                                }
                                return string.Format("{0}{1}", value / group.Divider + fraction + group.Symbol);
                            }
                            else {
                                symbol = _conversionGroups[i + 1].Symbol;
                                if (uppercase) {
                                    symbol = Char.ToUpper(symbol);
                                }
                                return "1" + symbol;
                            }
                        } else {
                            ret += value / group.Divider + fraction;
                        }
                    }
                    else {
                        ret += value / group.Divider;
                        ret += CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
                        ret += fraction;
                    }
                }
                else {
                    ret += value / group.Divider;
                }
                symbol = group.Symbol;
                if (uppercase) {
                    symbol = Char.ToUpper(symbol);
                }
                ret += symbol;
                return ret;
            }
        }

        return value.ToString();
    }

}

0
投票

这是一个数字截断的示例,小于 10 亿的数字的最大长度为 6 个字符。

using System;
using System.Globalization;

class Program {
    static void Main(string[] args) 
    {
        int num = 0;
        for(int i = 0; i < 9; i++) 
        {
            int factor = i + 1;
            num += (int)Math.Pow(10, i)*factor;
            Console.WriteLine($"{num:N0} -> {num.ToStringShort()}");
        }
        num += (int)Math.Pow(10, 9);
        Console.WriteLine($"{num:N0} -> {num.ToStringShort()}");
    }
}

public static class NumberExtensions
{
    public static string ToStringShort(this int num)
    {
        if(num < 1000)
        {
            return num.ToString("#0", CultureInfo.CurrentCulture);
        }

        if(num < 10000)
        {
            num /= 10;
            return (num/100f).ToString("#.00'K'", CultureInfo.CurrentCulture);
        }

        if(num < 1000000) 
        {
            num /= 100;
            return (num/10f).ToString("#.0'K'", CultureInfo.CurrentCulture);
        }

        if(num < 10000000)
        {
            num /= 10000;
            return (num/100f).ToString("#.00'M'", CultureInfo.CurrentCulture);
        }

        num /= 100000;
        return (num/10f).ToString("#,0.0'M'", CultureInfo.CurrentCulture);
    }
}

输出

1 -> 1
21 -> 21
321 -> 321
4,321 -> 4.32K
54,321 -> 54.3K
654,321 -> 654.3K
7,654,321 -> 7.65M
87,654,321 -> 87.6M
987,654,321 -> 987.6M
1,987,654,321 -> 1,987.6M

0
投票
string ToStringShort(int num)
{
    if (num < 1000)
        return num.ToString("#0");

    if (num < 10_000)
    {
        if (num % 1000 >= 100)
            return Math.Round(num / 1000f, 2, MidpointRounding.ToZero) + "K";

        return (num / 1000f).ToString("#'K'");
    }

    if (num < 1_000_000)
    {
        if (num % 1000 >= 100)
            return Math.Round(num / 1000f, 1, MidpointRounding.ToZero) + "K";
        
        return (num / 1000f).ToString("#'K'");
    }

    if (num < 10_000_000)
        return Math.Round(num / 1_000_000f, 2, MidpointRounding.ToZero) + "M";

    return (num / 1000000f).ToString("#,0.0'M'");
}

  • 1->1
  • 23->23
  • 136->136
  • 6000->6K
  • 6012->6K
  • 6126->6.12K
  • 6666->6.66K
  • 6968->6.96K
  • 23067->23K
  • 23667->23.6K
  • 133631->133.6K
  • 9363166->9.36M
  • 13363166->13.4M
  • 1336316661->1,336.3M

0
投票

此解决方案不进行任何舍入,这正是我的情况所需要的。

它还确保不超过 3 位数字。

public static class LongToCompactString
{
    public static string ToCompactString(this long number)
    {
        var dict = new Dictionary<string, double>
        {
            {"T", 1_000_000_000_000.0},
            {"B", 1_000_000_000.0},
            {"M", 1_000_000.0},
            {"K", 1_000.0}
        };
        foreach (var (suffix, divisor) in dict)
        {
            if (number >= divisor)
            {
                return FormatCompactNumber(number, divisor, suffix);
            }
        }
        return number.ToString();
    }

    private static string FormatCompactNumber(long number, double divisor, string suffix)
    {
        var result = $"{number / divisor}";
        // Check whether there are more than 3 digits
        if (result.Length > 3)
        {
            // Remove extra digits and decimal period
            var shouldRemoveDecimalPeriod = number >= divisor * 100;
            result = shouldRemoveDecimalPeriod ? result[..3] : result[..4];
        }
        return result + suffix;
    }
}

这些测试涵盖了一些边缘情况

public class LongToCompactStringTests
{
    [TestCase(999999999999999, "999T")]
    [TestCase(100000000000000, "100T")]
    [TestCase(99999999999999, "99.9T")]
    [TestCase(10000000000000, "10T")]
    [TestCase(9999999999999, "9.99T")]
    [TestCase(1000000000000, "1T")]
    [TestCase(999999999999, "999B")]
    [TestCase(100000000000, "100B")]
    [TestCase(99999999999, "99.9B")]
    [TestCase(10000000000, "10B")]
    [TestCase(9999999999, "9.99B")]
    [TestCase(1000000000, "1B")]
    [TestCase(999999999, "999M")]
    [TestCase(100000000, "100M")]
    [TestCase(99999999, "99.9M")]
    [TestCase(10000000, "10M")]
    [TestCase(9999999, "9.99M")]
    [TestCase(1000000, "1M")]
    [TestCase(999999, "999K")]
    [TestCase(100000, "100K")]
    [TestCase(99999, "99.9K")]
    [TestCase(10000, "10K")]
    [TestCase(9999, "9.99K")]
    [TestCase(1000, "1K")]
    [TestCase(999, "999")]
    [TestCase(100, "100")]
    [TestCase(99, "99")]
    [TestCase(10, "10")]
    [TestCase(9, "9")]
    [TestCase(1, "1")]
    [TestCase(0, "0")]
    public void test_long_to_compact_string(long number, string expected)
    {
        var actual = number.ToCompactString();
        Assert.AreEqual(expected, actual);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.