Question

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!

Was it helpful?

Solution

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 

OTHER TIPS

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

    }
}

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

Dynamically loading a dll in C#

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top