Format Number like Stack Overflow (rounded to thousands with K suffix)

Viewed 37006

How to format numbers like SO with C#?

10, 500, 5k, 42k, ...

15 Answers

Like this: (EDIT: Tested)

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");
}

Examples:

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

Note that this will give strange values for numbers >= 108.
For example, 12345678 becomes 12.3KK.

The code below is tested up to int.MaxValue This is not the most beautiful code but is most efficient. But you can use it as:

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

etc...

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");
    } 
}

You can crate a CustomFormater like this:

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();
    }
}

And use it like this:

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

edit: Renamed CurrencyFormatter to KiloFormatter

I'm a bit late, but came up with this:

`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();
}`

In the ZeroesAndLetters list just add the numbers you need.

15, 12, 9 and ... are the number of zeroes, and Q, T, B and ... are shortened names.

Solution for C#8+

 public static string ToKiloFormat(this int num)
    {
        return num switch
        {
            >= 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")
        };
    }

modified from a previous response

Something like this:

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

If the number is bigger than some threshold, divide it by 1000 and then format it to however many decimal places you need.

int input = 12392; // for example

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

This is moderately tested solution with rounding. It preserves meaningfulChars digits after comma or dot and uses CultureInfo to determine the decimal delimiter.

As I was writing it I came to understanding that rounding is not needed in most cases. So if you need no rounding you'd better use something else or expand this solution.

I tried to ensure the precision will not got eaten by floating point arithmetics but I presume it may occur for really big numbers.

Also I believe this can be improved with IFormatProvider or similar intrinsic mechanism from .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();
    }

}

Here is an example with truncation of the number and a max length of 6 characters for numbers smaller than 1 billion.

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);
    }
}

Output

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
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' });
}
Related