RPM1984
RPM1984

Reputation: 73112

String format numbers to millions, thousands with rounding

I'm trying to format a price for display, and I want to display a number with the million (M) or thousands (K) suffix, but only ever display at most 3 values, rounded down.

I found this question which is very close to what I want, but doesn't handle rounding (specifically, always rounding down)

Likewise, with this question you have no control over the rounding.

Sample input/expected output:

1 = 1
10 = 10
100 = 100
1000 = 1K
100000 = 100K
125000 = 125K
125900 = 125K
1000000 = 1M
1250000 = 1.25M
1258000 = 1.25M
10000000 = 10M
10500000 = 10.5M
100000000 = 100M
100100000 = 100M

I essentially only ever want to display 3 values.

I can't see how i can use the "," custom specifier and specify rounding.

My initial thinking suggests I need to use a combination of the above, Math.Floor and some .ToString() formatting magic, but i'm not really sure where to start.

Can someone help me out?

Thanks in advance.

Upvotes: 16

Views: 22424

Answers (6)

Martin Desaulniers
Martin Desaulniers

Reputation: 1

Below is a VBS/VbScript Function to do that. NB => VB and VBA offer direct format call.

' Usage: Return a number using it's greatert thousand symbol (ex: 234,789,145 = 234.8M)

Function Format_Large_Number(nb, nbDigit)

' Safety
If Not IsNumeric(nb) Then FormatLargeNumber = nb : Exit Function
If nbDigit < 1 Then nbDigit = 1
If nbDigit > 3 Then nbDigit = 3

' Initiate var
Dim rtnNb    : rtnNb    = nb
Dim nbFormat : nbFormat = FormatCurrency(rtnNb, 0, vbTrue, vbTrue)
               nbFormat = Trim(Replace(nbFormat, "$", ""))
Dim nbSymbol : nbSymbol = ""
Dim nbDecRnd : nbDecRnd = ""
Dim arrNb    : arrNb    = Split(nbFormat, " ")

' Asign symbol
Select Case UBound(arrNb)
    Case 0
        nbSymbol = ""   ' Hundred
    Case 1
        nbSymbol = "K"  ' Thousand
    Case 2
        nbSymbol = "M"  ' Million
    Case 3
        nbSymbol = "G"  ' Billion
    Case 4
        nbSymbol = "T"  ' Trillion
    Case 5
        nbSymbol = "P"  ' Quadrillion
    Case 6
        nbSymbol = "E"  ' Quintillion
End Select

' Concatenate rtn string
If Ubound(arrNb) > 0 Then
    If nbDigit < 3 Then
        If CInt(arrNb(1)) > 499 Then
            nbDecRnd = CStr(CInt(Left(arrNb(1),nbDigit)) + 1) ' Ceil decimal rtn
        Else
            nbDecRnd = Left(arrNb(1),nbDigit)
        End If
    Else
        nbDecRnd = Left(arrNb(1),nbDigit)
    End If
    
    rtnNb = arrNb(0) & "," & nbDecRnd & nbSymbol    ' All others
Else
    
    rtnNb = arrNb(0) & nbSymbol                     ' Hundred
End If

' Rtn value
Format_Large_Number = rtnNb

End Function

Upvotes: 0

Da man
Da man

Reputation: 11

I know this is an old question but I can't help but notice most of these answers are not far expandable, most of them are very similar lines of code repeated for more values. I needed something that worked on bigger values so instead of repetetive code I tried using math in a smart way. This does require using floating point math (doubles) as the maximum value of an unsigned long is only 1.84e+19. My solution:

public static string GetStringRepresentation(double count)
{
    string tokens = " KMBtqQsSondUDT"; //Infinitely expandable (at least to the limit of double floating point values)
    for (double i = 1; true; i += 1)
    {
        double val = Math.Pow(1000, i);
        if (val > count)
        {
            return $"{count / Math.Pow(1000, i - 1)}{tokens[(int)i - 1]}".Trim();
        }
    }
}

Test code:

Console.WriteLine("                     1: " + GetStringRepresentation(                  1).PadLeft(7, ' '));
Console.WriteLine("                    12: " + GetStringRepresentation(                 12).PadLeft(7, ' '));
Console.WriteLine("                   123: " + GetStringRepresentation(                123).PadLeft(7, ' '));
Console.WriteLine("                  1230: " + GetStringRepresentation(               1230).PadLeft(7, ' '));
Console.WriteLine("                 12340: " + GetStringRepresentation(              12340).PadLeft(7, ' '));
Console.WriteLine("                123450: " + GetStringRepresentation(             123450).PadLeft(7, ' '));
Console.WriteLine("               1230000: " + GetStringRepresentation(            1230000).PadLeft(7, ' '));
Console.WriteLine("              12340000: " + GetStringRepresentation(           12340000).PadLeft(7, ' '));
Console.WriteLine("             123450000: " + GetStringRepresentation(          123450000).PadLeft(7, ' '));
Console.WriteLine("            1230000000: " + GetStringRepresentation(         1230000000).PadLeft(7, ' '));
Console.WriteLine("         1230000000000: " + GetStringRepresentation(      1230000000000).PadLeft(7, ' '));
Console.WriteLine("      1230000000000000: " + GetStringRepresentation(   1230000000000000).PadLeft(7, ' '));
Console.WriteLine("   1230000000000000000: " + GetStringRepresentation(1230000000000000000).PadLeft(7, ' '));

Output:

                     1:       1
                    12:      12
                   123:     123
                  1230:   1,23K
                 12340:  12,34K
                123450: 123,45K
               1230000:   1,23M
              12340000:  12,34M
             123450000: 123,45M
            1230000000:   1,23B
         1230000000000:   1,23t
      1230000000000000:   1,23q
   1230000000000000000:   1,23Q

Upvotes: 1

CS Pei
CS Pei

Reputation: 11047

This is my code with test outputs

1249            1.24K
12499           12.4K
124999          124K
1249999         1.24M
12499999        12.4M
124999999       124M
1249999999      1.24B

The code will output three digits at maximum.

    static string FormatNumber(uint n)
    {
        if (n < 1000)
            return n.ToString();

        if (n < 10000)
            return String.Format("{0:#,.##}K", n - 5);

        if (n < 100000)
            return String.Format("{0:#,.#}K", n - 50);

        if (n < 1000000)
            return String.Format("{0:#,.}K", n - 500);

        if (n < 10000000)
            return String.Format("{0:#,,.##}M", n - 5000);

        if (n < 100000000)
            return String.Format("{0:#,,.#}M", n - 50000);

        if (n < 1000000000)
            return String.Format("{0:#,,.}M", n - 500000);

        return String.Format("{0:#,,,.##}B", n - 5000000);
    }

Upvotes: 12

RenniePet
RenniePet

Reputation: 11658

This should help, combined with one of the formatting techniques in the other questions you've linked to.

  internal long MaxThreeSignificantDigits(long x)
  {
     int i = (int)Math.Log10(x);
     i = Math.Max(0, i - 2);
     i = (int)Math.Pow(10, i);
     return x / i * i;
  }

EDIT:

OK, how about this?

 Console.WriteLine(SO30180672.FormatNumber(1));
 Console.WriteLine(SO30180672.FormatNumber(12));
 Console.WriteLine(SO30180672.FormatNumber(123));
 Console.WriteLine(SO30180672.FormatNumber(1234));
 Console.WriteLine(SO30180672.FormatNumber(12345));
 Console.WriteLine(SO30180672.FormatNumber(123456));
 Console.WriteLine(SO30180672.FormatNumber(1234567));
 Console.WriteLine(SO30180672.FormatNumber(12345678));
 Console.WriteLine(SO30180672.FormatNumber(123456789));

Following is partially copied from here: https://stackoverflow.com/a/23384710/253938

   internal class SO30180672
   {
      internal static string FormatNumber(long num)
      {
         num = MaxThreeSignificantDigits(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("0k");
         if (num >= 100000)
            return (num / 1000D).ToString("0.#k");
         if (num >= 1000)
            return (num / 1000D).ToString("0.##k");
         return num.ToString("#,0");
      }


      internal static long MaxThreeSignificantDigits(long x)
      {
         int i = (int)Math.Log10(x);
         i = Math.Max(0, i - 2);
         i = (int)Math.Pow(10, i);
         return x / i * i;
      }
   }

EDIT 2 - thank you very much to @Rhexis

   internal class SO30180672
   {
      internal static void RunTest()
      {
         Console.WriteLine(FormatNumber(1));
         Console.WriteLine(FormatNumber(10));
         Console.WriteLine(FormatNumber(100));
         Console.WriteLine(FormatNumber(1000));
         Console.WriteLine(FormatNumber(10000));
         Console.WriteLine(FormatNumber(100000));
         Console.WriteLine(FormatNumber(125000));
         Console.WriteLine(FormatNumber(125900));
         Console.WriteLine(FormatNumber(1000000));
         Console.WriteLine(FormatNumber(1250000));
         Console.WriteLine(FormatNumber(1258000));
         Console.WriteLine(FormatNumber(10000000));
         Console.WriteLine(FormatNumber(10500000));
         Console.WriteLine(FormatNumber(100000000));
         Console.WriteLine(FormatNumber(100100000));
      }

      private static string FormatNumber(long num)
      {
         // Ensure number has max 3 significant digits (no rounding up can happen)
         long i = (long)Math.Pow(10, (int)Math.Max(0, Math.Log10(num) - 2));
         num = num / i * i;

         if (num >= 1000000000)
            return (num / 1000000000D).ToString("0.##") + "B";
         if (num >= 1000000)
            return (num / 1000000D).ToString("0.##") + "M";
         if (num >= 1000)
            return (num / 1000D).ToString("0.##") + "K";

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

Upvotes: 16

RPM1984
RPM1984

Reputation: 73112

Thanks for your help everyone, it got me on the right track to figuring it out myself.

public static string FormatPriceValue(this int num)
{
    if (num >= 100000000)
    {
        return ((num >= 10050000 ? num - 500000 : num) / 1000000D).ToString("#M");
    }
    if (num >= 10000000)
    {
        return ((num >= 10500000 ? num - 50000 : num) / 1000000D).ToString("0.#M");
    }
    if (num >= 1000000)
    {
        return ((num >= 1005000 ? num-5000 : num) / 1000000D).ToString("0.##M");
    }
    if (num >= 100000)
    {
        return ((num >= 100500 ? num - 500 : num) / 1000D).ToString("0.k");
    }
    if (num >= 10000)
    {
        return ((num >= 10550 ? num - 50 : num) / 1000D).ToString("0.#k");
    }

    return num >= 1000 ? ((num >= 1005 ? num - 5 : num) / 1000D).ToString("0.##k") : num.ToString("#,0");
}

Upvotes: 2

TMS
TMS

Reputation: 126

Since the format essentially changes based on the range you'll most likely need some conditional formatting similar to below. I have only tested the sample set provided, so make sure this works for the full range of expected values.

class Program
{
    static void Main(String[] args)
    {
        Console.WriteLine(RoundAndFormat(1));
        Console.WriteLine(RoundAndFormat(10));
        Console.WriteLine(RoundAndFormat(100));
        Console.WriteLine(RoundAndFormat(1000));
        Console.WriteLine(RoundAndFormat(100000));
        Console.WriteLine(RoundAndFormat(125000));
        Console.WriteLine(RoundAndFormat(125900));
        Console.WriteLine(RoundAndFormat(1000000));
        Console.WriteLine(RoundAndFormat(1250000));
        Console.WriteLine(RoundAndFormat(1258000));
        Console.WriteLine(RoundAndFormat(10000000));
        Console.WriteLine(RoundAndFormat(10500000));
        Console.WriteLine(RoundAndFormat(100000000));
        Console.WriteLine(RoundAndFormat(100100000));

        Console.ReadLine();
    }

    public static String RoundAndFormat(Int32 value)
    {
        var result = String.Empty;
        var negative = value < 0;
        if (negative) value = value * -1;

        if (value < 1000)
        {
            result = value.ToString();
        }
        else if (value < 1000000)
        {
            result = RoundDown(value / 1000.0, 0) + "K";
        }
        else if (value < 100000000)
        {
            result = RoundDown(value / 1000000.0, 2) + "M";
        }
        else if (value < 10000000000)
        {
            result = RoundDown(value / 1000000.0, 0) + "M";
        }

        if (negative) return "-" + result;
        return result;
    }

    public static Double RoundDown(Double value, Int32 digits)
    {
        var pow = Math.Pow(10, digits);
        return Math.Truncate(value * pow) / pow;
    }

Upvotes: 5

Related Questions