23

I would like to print my very small numbers in C# in a human friendly way, such as:

30µ for 3E-5 or 456.789n for 0.000000456789.

I know of the Humanize_number() function from BSD in C, but only compatible with bit ints, not floats and doubles. Is there the equivalent in C# that supports those?

Also, it should keep a certain amount of precision when displaying numbers, like:

0.003596 should be displayed as 3.596µ, not 3.6µ (or worse, ).

The possible answer here: Formatting Large Numbers with .NET but adapted for negative log10 is truncating the numbers to 1 digit after the comma. That's far from complete in my opinion.

Examples of how I'd like to present things:

3000        3K
3300        3.3K
3333        3.333K
30000       30k
300000      300k
3000000     3M
3000003     3.000003M // or 3M if I specify "4 digits precision"
0.253       253m
0.0253      25.3m
0.00253     2.53m
-0.253003   -253.003m

I couldn't formulate my question to find relevant answers in SO, so if the question has been already answered, fire away!

Community
  • 1
  • 1
Gui13
  • 12,993
  • 17
  • 57
  • 104
  • 3
    [No, you aren't dreaming.](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be-removed-from-posts/93989#93989) – Daniel Hilgarth Apr 18 '13 at 13:02
  • I'm sorry, but the http://stackoverflow.com/questions/1555397/formatting-large-numbers-with-net answer is not really helping. I'd like to keep the maximum of precision in the things I display. – Gui13 Apr 18 '13 at 13:11
  • 6
    Doesn't look like it'd be terribly difficult to port the code for `humanize_number`: http://www.opensource.apple.com/source/libutil/libutil-20/humanize_number.c – Jim Mischel Apr 18 '13 at 13:13
  • @Jim I'm not sure of that, the original is only for big ints, I'd like it to work on small numbers and floats as well. I'll make my question more specific. – Gui13 Apr 18 '13 at 13:24
  • What are you using to determine the precision of the number? The string representation of the number? The underlying float/double precision? – mbeckish Apr 18 '13 at 13:32
  • I guess SI units are the base: if the number has more than 3 digits in front of the decimal point, you should go down a range of 1000. – Gui13 Apr 18 '13 at 13:35
  • Why all the upvotes on this duplicate question? – leppie Apr 18 '13 at 13:59
  • 1
    @leppie - Isn't it really the same idea? 1) Use log to determine the order of magnitude. 2) Write some custom case statements to choose the final order of magnitude and suffix. – mbeckish Apr 18 '13 at 14:27
  • 1
    @leppie - Sorry, I misread your comment. I thought you were asking why the duplicate comment was upvoted. – mbeckish Apr 18 '13 at 14:29
  • Why is `3.333k` considered more readable than `3,333`...? – Alex Apr 18 '13 at 14:48
  • That's to confom to my "rule" of not having more than 3 digits for the integer part of the representation. – Gui13 Apr 18 '13 at 14:50
  • @leppie: that's far from a duplicate question IMO. The question is about formatting non-integer numbers, both big and small. The suggested duplicate is just for ints and the method is not completely accurate. See ja72's answer here and compare it to the one given in 1555397. – Gui13 Apr 19 '13 at 07:02

4 Answers4

9

Try this:

static class Extensions
{
    static string[] prefixes= { "f", "a", "p", "n", "μ", "m", string.Empty, "k", "M", "G", "T", "P", "E" };

    public static string Nice(this double x, int significant_digits)
    {
        //Check for special numbers and non-numbers
        if(double.IsInfinity(x)||double.IsNaN(x)||x==0||significant_digits<=0)
        {
            return x.ToString();
        }
        // extract sign so we deal with positive numbers only
        int sign=Math.Sign(x);
        x=Math.Abs(x);
        // get scientific exponent, 10^3, 10^6, ...
        int sci= x==0? 0 : (int)Math.Floor(Math.Log(x, 10)/3)*3;
        // scale number to exponent found
        x=x*Math.Pow(10, -sci);
        // find number of digits to the left of the decimal
        int dg= x==0? 0 : (int)Math.Floor(Math.Log(x, 10))+1;
        // adjust decimals to display
        int decimals=Math.Min(significant_digits-dg, 15);
        // format for the decimals
        string fmt=new string('0', decimals);
        if(sci==0)
        {
            //no exponent
            return string.Format("{0}{1:0."+fmt+"}",
                sign<0?"-":string.Empty,
                Math.Round(x, decimals));
        }
        // find index for prefix. every 3 of sci is a new index
        int index=sci/3+6;
        if(index>=0&&index<prefixes.Length)
        {
            // with prefix
            return string.Format("{0}{1:0."+fmt+"}{2}",
                sign<0?"-":string.Empty,
                Math.Round(x, decimals),
                prefixes[index]);
        }
        // with 10^exp format
        return string.Format("{0}{1:0."+fmt+"}·10^{2}",
            sign<0?"-":string.Empty,
            Math.Round(x, decimals),
            sci);
    }

    // test code
    static void Main(string[] args)
    {
        double x=Math.PI/10e20;
        do
        {
            Console.WriteLine(string.Format( "\t{0,20} = {1}", x, x.Nice(4)));
            x*=10;
        } while(x<=Math.PI*10e20);
    }
}

Test output with four significant digits:

    3.14159265358979E-19 = 314.2·10^-2
     1.5707963267949E-18 = 1.571f
    7.85398163397448E-18 = 7.854f
    3.92699081698724E-17 = 39.27f
    1.96349540849362E-16 = 196.3f
     9.8174770424681E-16 = 981.7f
    4.90873852123405E-15 = 4.909a
    2.45436926061703E-14 = 24.54a
    1.22718463030851E-13 = 122.7a
    6.13592315154256E-13 = 613.6a
    3.06796157577128E-12 = 3.068p
    1.53398078788564E-11 = 15.34p
     7.6699039394282E-11 = 76.70p
     3.8349519697141E-10 = 383.5p
    1.91747598485705E-09 = 1.917n
    9.58737992428526E-09 = 9.587n
    4.79368996214263E-08 = 47.94n
    2.39684498107131E-07 = 239.7n
    1.19842249053566E-06 = 1.198µ
    5.99211245267829E-06 = 5.992µ
    2.99605622633914E-05 = 29.96µ
    0.000149802811316957 = 149.8µ
    0.000749014056584786 = 749.0µ
     0.00374507028292393 = 3.745m
      0.0187253514146196 = 18.73m
      0.0936267570730982 = 93.63m
       0.468133785365491 = 468.1m
        2.34066892682745 = 2.341
        11.7033446341373 = 11.70
        58.5167231706864 = 58.52
        292.583615853432 = 292.6
        1462.91807926716 = 1.463k
         7314.5903963358 = 7.315k
         36572.951981679 = 36.57k
        182864.759908395 = 182.9k
        914323.799541975 = 914.3k
        4571618.99770987 = 4.572M
        22858094.9885494 = 22.86M
        114290474.942747 = 114.3M
        571452374.713734 = 571.5M
        2857261873.56867 = 2.857G
        14286309367.8434 = 14.29G
        71431546839.2168 = 71.43G
        357157734196.084 = 357.2G
        1785788670980.42 = 1.786T
         8928943354902.1 = 8.929T
        44644716774510.5 = 44.64T
         223223583872552 = 223.2T
    1.11611791936276E+15 = 1.116P
    5.58058959681381E+15 = 5.581P
    2.79029479840691E+16 = 27.90P
    1.39514739920345E+17 = 139.5P
    6.97573699601726E+17 = 697.6P
    3.48786849800863E+18 = 3.488E
    1.74393424900432E+19 = 17.44E
    8.71967124502158E+19 = 87.20E
    4.35983562251079E+20 = 436.0E
     2.1799178112554E+21 = 2.180·10^21 
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
2

as you want the decimal to be displayed as sign and not as a lot of 0's you could as well do something like:

class Program
{
    static void Main(string[] args)
    {
        //these are your "unit precedessors"
        char[] exponentsbig = new char[] {' ', 'k', 'M', 'G', 'T', 'P', 'E' };
        char[] exponentssmall = new char[] { ' ', 'm', 'µ', 'n', 'p', 'a', 'f' };

        //some example numbers
        long[] numbersBig = new long[] { 3000, 3003, 30000, 300000, 300003, 1594900000000000 };
        double[] numbersSmall = new double[] { 0.0002, 0.245, 0.245003, 0.000004578 };
        //some helper vars
        int counter = 0;
        bool edited = false;
        //let's have a look at what we produce;)
        string output = "";

        //Big  numbers incoming!!
        for (int i = 0; i < numbersBig.Length; i++)
        {
            counter=0;
            double myNumber = Convert.ToDouble(numbersBig[i]);
            do
            {
                edited = false;
                //something to prevent unnecessary unit-adding and making sure you still divide by 1000
                if (myNumber/1000>1 )
                {
                    counter++;
                    myNumber /= 1000;
                    edited = true;
                }
            } while (edited);
            output += numbersBig[i] + " " + myNumber + exponentsbig[counter] + "\n";
        }

        //small  numbers incoming!!
        for (int i = 0; i < numbersSmall.Length; i++)
        {
            counter = 0;
            double myNumber = numbersSmall[i];
            do
            {
                edited = false;
                //this will go to 3 digits after comma. you can make the compared smaller 
                //to be more exact after the comma, but keep in mind you lose steps then
                if (myNumber < 1)
                {
                    counter++;
                    myNumber *= 1000;
                    edited = true;
                }
            } while (edited);
            output += numbersSmall[i] + " " + myNumber + exponentssmall[counter] + "\n";
        }
        //see what we did
        Console.Write(output);
        Console.ReadKey();

    }
}
Vogel612
  • 5,620
  • 5
  • 48
  • 73
0

Could you use DllImport to use the Humanize_Number function?? See here for details :

Dynamically loading a dll in C#

Community
  • 1
  • 1
Bertie
  • 733
  • 5
  • 16
  • I'd rather know if there's an "embedded" solution. I used the BSD function under linux in another life :) – Gui13 Apr 18 '13 at 13:19
0

Why not multiply by 10^(count numbers after decimal)? You can use the same count of numbers after the decimal to figure out which unit to display. It's much better than importing an entire library.

Captain Kenpachi
  • 6,960
  • 7
  • 47
  • 68