15

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.

Community
  • 1
  • 1
RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • Duplicate of http://stackoverflow.com/questions/2134161/format-number-like-stack-overflow-rounded-to-thousands-with-k-suffix – Steve May 12 '15 at 02:26
  • @Steve can't control rounding in that question – RPM1984 May 12 '15 at 02:32
  • How I'm looking at it right now you would have to define some constant and have some conditional checking to verify is a number is between a certain range and then apply the correct "mapping" to display the value correctly. – Kevin Avignon May 12 '15 at 02:45
  • @KevinAvignon yeah, definetely going to need some conditionals to see which range it falls into, but im stuck on figuring out how to do the rounding – RPM1984 May 12 '15 at 03:16
  • It seems like the code in the link Steve posted always rounds up so if you -500 (or a number relative to the one you are trying to round on) from the number it will appear as though its rounding down, no? – Rhexis May 12 '15 at 03:18
  • @Rhexis - yep, nothing i've found has enabled me to control the rounding. That's why i feel like Math.Floor needs to play a part somehow. – RPM1984 May 12 '15 at 03:20
  • The question I posted doesn't exactly match yours, but should be close enough to you to get a start. Also, Math.Round has an overload where you specify the rounding direction. – Steve May 12 '15 at 03:20
  • @Steve ive read the question, and alhough it provides some pointers, doesn't help with my rounding issue. Side note if something is "close enough" i don't think it should be closed as duplicate. Essentially, what i'm asking in this question has not been specifically been asked before on SO. – RPM1984 May 12 '15 at 03:22

6 Answers6

14

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");
      }
   }
Community
  • 1
  • 1
RenniePet
  • 11,420
  • 7
  • 80
  • 106
  • Can you provide a bit more info, like how this can be combined with the other answers? Not sure how i can use this – RPM1984 May 12 '15 at 03:15
  • I believe that @RenniePet has the answer. I do think though that you only need 3 if's in 'FormatNumber'. Using your code I got the desired output. (see here: http://pastebin.com/8qXHMvQG) – Rhexis May 12 '15 at 04:03
  • @Rhexis: Good idea. Can't be bothered changing my answer though - it was the bit about getting max 3 significant digits that caught my interest. I could remember learning how to do that long, long ago. (40 years ago, if you can believe that.) – RenniePet May 12 '15 at 04:18
  • @Rhexis: OK, now I've added your suggested improvement - thank you very much. – RenniePet May 12 '15 at 11:02
  • @RenniePet your code works with my tests, i'll go with this one. thanks! – RPM1984 May 12 '15 at 23:53
  • How do I use this with decimal inputs? Math.Log10 doesn't take a decimal. – Smartie Apr 25 '16 at 12:51
12

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);
    }
CS Pei
  • 10,869
  • 1
  • 27
  • 46
5

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;
    }
TMS
  • 126
  • 4
  • Made a quick change to handle negative values. – TMS May 12 '15 at 03:36
  • Shouldn't that be Math.Truncate(value / pow) * pow; ? – RenniePet May 12 '15 at 03:55
  • Nope, that function is a sig fig calculation. I am moving the decimal right x places, dropping the fractional portion, and moving the decimal back. I'll use the example of 1.2580 with a count of 2 decimals: pow = 10^2 = 100, Truncate(1.2580 * 100) = 125, 125.80 / 100 = 1.25 – TMS May 12 '15 at 13:16
2

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");
}
RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • Please take another look at the my answer, with improvements suggested by @Rhexis. Instead of thinking of "rounding" up or down, you should be thinking "max 3 significant digits". The code to do that is a bit cryptic, but once you have it there's no need for a lot of the "magic numbers" you're using - that's the kind of thing that often goes wrong when a program needs to be modified in the future. – RenniePet May 12 '15 at 11:21
  • @RenniePet i've got all these unit tests passing with my current code, so i'll plug yours in and see if they still pass. Stay tuned :) – RPM1984 May 12 '15 at 23:10
  • That's great, glad I could help. But most of the honor should actually go to @Rhexis, I only did the significant digits part, it was Rhexis who fixed the formatting part. – RenniePet May 13 '15 at 00:20
1

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
Da man
  • 11
  • 1
0

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