使大型和小型数字易于阅读

23

我想在C#中以人性化的方式打印非常小的数字,例如:

30µ 代表 3E-5 或者 456.789n 代表 0.000000456789

我知道在C中有来自BSD的Humanize_number()函数,但它仅与比特整数兼容,不支持浮点数和双精度浮点数。是否在C#中存在相应的函数支持这些内容?

此外,在显示数字时,应该保留一定的精度,例如:

0.003596 应该显示为 3.596µ 而不是 3.6µ(更糟糕的是,)。

这里可能的答案为:Formatting Large Numbers with .NET,但需要适用于负log10,它会将数字截断到小数点后1位。在我看来,这远远不够完整。

以下是我希望展示的一些示例:

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

我无法用清晰的语言表达我的问题以便在SO中找到相关的答案,所以如果这个问题已经有了答案,请直接回答!


3
不,你没有做梦。 - Daniel Hilgarth
6
看起来移植 humanize_number 代码并不是特别困难:http://www.opensource.apple.com/source/libutil/libutil-20/humanize_number.c - Jim Mischel
为什么这个重复的问题会有那么多赞? - leppie
1
@leppie - 这不是同样的想法吗?1)使用日志确定数量级。2)编写一些自定义案例语句来选择最终的数量级和后缀。 - mbeckish
1
@leppie - 抱歉,我误读了你的评论。我以为你在问为什么重复的评论被点赞了。 - mbeckish
显示剩余7条评论
4个回答

9

试试这个:

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

测试输出结果,保留四位有效数字:

    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 

1
警告:当X为0时,您具有负的dg和sci,这会引发异常。我编辑了您的代码以考虑到这一点。 - Gui13
我对代码进行了修改,添加了对零、NaN和Inf的检查。 - John Alexiou
我应该在一开始就检查 significant_digits>0 - John Alexiou
1
我修改了你的代码,使其更易于阅读:https://gist.github.com/bboyle1234/df47f661b531efd7386f0dbcdfbeee6f - bboyle1234

2

如果你希望将小数显示为符号而不是许多0,那么你可以这样做:

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

    }
}

0

我更想知道是否有“嵌入式”解决方案。我在另一个生活中在Linux下使用了BSD函数 :) - Gui13

0
为什么不乘以10^(小数点后的数字个数)呢?您可以使用相同数量的小数位来确定要显示的单位。这比导入整个库要好得多。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接