是否有一种算法可以找出任意数量级的数字序列的“好看”的数字格式?

4

我目前正在使用扩展威尔金森算法的实现来生成轴刻度值序列。为此,该算法给定值范围[min,max]和所需刻度线值的数量n,然后输出在区间[min,max]中均匀分布的值数组。我需要做的是,从这些值创建字符串标签,但根据这些值的数量级,我想在科学计数法和十进制计数法之间切换。

例如对于序列{0.00001,0.000015,0.00002,0.000025},我想使用科学计数法{'1.0e-05','1.5e-05','2.0e-05','2.5e-05'}。 对于序列{0,8,16,24,32},我想以十进制表示。 我不希望有不必要的尾随零,比如0.001000或1.500e-05,但是在上面的科学计数法例子中,当其他数字需要使用更多小数位时,我希望有一个尾随零。例如'1.00e-05'和'1.05e-05'。但等等,例如对于{20.0000001,20.0000002,20.0000003},有趣的部分当然是每个值的0.0000001非常小,但20仍然很重要,类似于'20+1.0e-07'可能是可取的,因为数零很繁琐。 标签中混合使用科学和十进制也不受欢迎,例如{8000,9000,1.0e04,1.1e04}是不好的。
目标是拥有一致的标签,让人们区分数值并且易于阅读,这样非常小或非常大的值可以用科学计数法表示,以节省显示空间。因此,要使用的表示形式不取决于单个值本身,而是必须考虑整个序列。 是否有可用的软件包或一些研究论文涉及这个问题? 我尝试过自己实现,但效果不佳,有时候会为不同的数字输出相同的字符串,例如{86.0001, 86.00015, 86.0002, 86.00025}会输出'86.0001', '86.0001', '86.0002', '86.0002'。
protected String[] labelsForTicks(double[] ticks){
   String str1 = String.format(Locale.US, "%.4g", ticks[0]);
   String str2 = String.format(Locale.US, "%.4g", ticks[ticks.length-1]);
   String[] labels = new String[ticks.length];
   if(str1.contains("e") || str2.contains("e")){
      for(int i=0; i<ticks.length; i++){
         String l = String.format(Locale.US, "%.4e", ticks[i]);
         String[] Esplit = l.split("e", -2);
         String[] dotsplit = Esplit[0].split("\\.",-2);
         dotsplit[1] = ('#'+dotsplit[1])
               .replaceAll("0", " ")
               .trim()
               .replaceAll(" ", "0")
               .replaceAll("#", "");
         dotsplit[1] = dotsplit[1].isEmpty() ? "0":dotsplit[1];
         l = dotsplit[0]+'.'+dotsplit[1]+'e'+Esplit[1];
         labels[i] = l;
      }
   } else {
      for(int i=0; i<ticks.length; i++){
         String l = String.format(Locale.US, "%.4f", ticks[i]);
         if(l.contains(".")){
            String[] dotsplit = l.split("\\.",-2);
            dotsplit[1] = ('#'+dotsplit[1])
                  .replaceAll("0", " ")
                  .trim()
                  .replaceAll(" ", "0")
                  .replaceAll("#", "");
            if(dotsplit[1].isEmpty()){
               l = dotsplit[0];
            } else {
               l = dotsplit[0]+'.'+dotsplit[1];
            }
         }
         labels[i] = l;
      }
   }
   return labels;
}

它试图决定是否使用科学计数法或十进制计数法,使用字符串格式化中的'g'选项对序列中的第一个和最后一个值进行操作,然后尝试去掉不必要的零。
1个回答

2
接收“ticks”双精度数的第一个问题是将它们舍入为最小数量的数字,使它们不同。以下函数“ScaleForTicks”实现此功能。如果找到了可以将所有“ticks”缩放为整数并保持它们不同的最大10的幂,则进行缩放。对于“ticks≥0”,缩放意味着除以10的幂,而对于“ticks<1”,它意味着乘以10的幂。一旦“ticks”被缩放为整数,我们将其四舍五入为0位小数。这给我们提供了基本标签。根据应用的10的幂,它们仍需要进行额外处理。
问题没有说明标签中可以有多少个连续的0。因此,我向“LabelsForTicks”函数添加了“maxZeroDigits”参数。因此,如果标签包含“maxZeroDigits”或更少个连续的0,则不会显示科学计数法。否则,将使用科学计数法。

另一个困难是由问题中的20.0000001 20.0000002 20.0000003所说明的。问题在于提取所有标签的公共偏移量,以显示实际的小变化1.0e-07 2.0e-07 3.0e-07。该问题通过从缩放后获得的整数标签集中提取公共偏移量来解决。参数maxZeroDigits用于确定是否要以科学计数法格式化偏移量。

该问题要求完全格式化的标签,包括可选偏移量、标签和可选指数。由于所有标签的偏移量和指数都相同,它们可以作为单独的部分返回。这就是下面的LabelsForTicks函数所做的。对于n个刻度,返回数组的前n个元素是没有偏移量和指数的格式化标签。返回数组的下两个元素是偏移量的标签和指数。返回数组的最后一个元素是标签的指数。不同的部分可以组装起来得到完全格式化的标签,也可以分别使用,例如在图形轴上指示乘法因子(x10^2)或标签的偏移量(+1.34e+04)

以下是代码。

static string[] LabelsForTicks(double[] ticks, int maxZeroDigits)
{
    int scale = ScaleForTicks(ticks);

    string[] labels = new string[ticks.Length + 3];

    if (scale >= 0)
    {
        if (scale >= maxZeroDigits + 1)
        {
            for (int i = 0; i < ticks.Length; i++)
                labels[i] = ((long)Math.Round(ticks[i] / Math.Pow(10, scale))).ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            for (int i = 0; i < ticks.Length; i++)
                labels[i] = ((long)ticks[i]).ToString(CultureInfo.InvariantCulture);
        }
    }
    else
    {
        for (int i = 0; i < ticks.Length; i++)
            labels[i] = ((long)Math.Round(ticks[i] * Math.Pow(10, -scale))).ToString(CultureInfo.InvariantCulture);
    }

    // Find common offset.
    char[] mask = labels[0].ToCharArray();
    for (int i = 1; i < ticks.Length; i++)
    {
        for (int j = 0; j < labels[0].Length; j++)
            if (mask[j] != labels[i][j])
                mask[j] = 'x';
    }
    int k = mask.Length - 1;
    while (k >= 0 && mask[k] != 'x') k--;
    for (; k > 0; k--)
    {
        if (!(mask[k] == 'x' || mask[k] != '0'))
        {
            k++;
            break;
        }
    }

    // If there is an offset, and it contains a sequence of more than maxZeroDigits.
    string common = new string(mask, 0, k);
    if (common.Contains(new string('0', maxZeroDigits + 1)))
    {
        // Remove common offset from all labels.
        for (int i = 0; i < ticks.Length; i++)
            labels[i] = labels[i].Substring(k);
        // Add ofsset as the second-to-last label.
        labels[ticks.Length] = common + new string('0', labels[0].Length);
        // Reduce offset.
        string[] offset = LabelForNumber(Convert.ToDouble(labels[ticks.Length]) * Math.Pow(10, scale), maxZeroDigits);
        labels[ticks.Length] = offset[0];
        labels[ticks.Length + 1] = offset[1];
    }

    if (scale < 0)
    {
        int leadingDecimalDigits = (-scale) - labels[0].Length;
        if (leadingDecimalDigits <= maxZeroDigits)
        {
            string zeros = new string('0', leadingDecimalDigits);
            for (int i = 0; i < ticks.Length; i++)
                labels[i] = "0." + zeros + labels[i];
            scale = 0;
        }
        else
        {
            // If only one digit, append "0".
            if (labels[0].Length == 1)
            {
                scale -= 1;
                for (int i = 0; i < ticks.Length; i++)
                    labels[i] = labels[i] + "0";
            }
            // Put decimal point immediately after the first digit.
            scale += labels[0].Length - 1;
            for (int i = 0; i < ticks.Length; i++)
                labels[i] = labels[i][0] + "." + labels[i].Substring(1);
        }
    }
    else if (scale > maxZeroDigits)
    {
        // If only one digit, append "0".
        if (labels[0].Length == 1)
        {
            for (int i = 0; i < ticks.Length; i++)
                labels[i] = labels[i] + "0";
        }
        // Put decimal point immediately after the first digit.
        scale += labels[0].Length - 1;
        for (int i = 0; i < ticks.Length; i++)
            labels[i] = labels[i][0] + "." + labels[i].Substring(1);
    }

    // Add exponent as last labels.
    if (scale < 0 || scale > maxZeroDigits)
    {
        string exponent;
        if (scale < 0)
        {
            exponent = (-scale).ToString();
            if (exponent.Length == 1) exponent = "0" + exponent;
            exponent = "-" + exponent;
        }
        else
        {
            exponent = scale.ToString();
            if (exponent.Length == 1) exponent = "0" + exponent;
            exponent = "+" + exponent;
        }
        labels[ticks.Length + 2] = "e" + exponent;
    }

    return labels;
}

static int ScaleForTicks(double[] ticks)
{
    int scale = -1 + (int)Math.Ceiling(Math.Log10(ticks.Last()));

    int bound = Math.Max(scale - 15, 0);

    while (scale >= bound)
    {
        double t1 = Math.Round(ticks[0] / Math.Pow(10, scale));
        bool success = true;
        for (int i = 1; i < ticks.Length; i++)
        {
            double t2 = Math.Round(ticks[i] / Math.Pow(10, scale));
            if (t1 == t2)
            {
                success = false;
                break;
            }
            t1 = t2;
        }
        if (success)
            return scale;

        scale--;
    }

    bound = Math.Min(-1, scale - 15);

    while (scale >= bound)
    {
        double t1 = Math.Round(ticks[0] * Math.Pow(10, -scale));
        bool success = true;
        for (int i = 1; i < ticks.Length; i++)
        {
            double t2 = Math.Round(ticks[i] * Math.Pow(10, -scale));
            if (t1 == t2)
            {
                success = false;
                break;
            }
            t1 = t2;
        }
        if (success)
            return scale;

        scale--;
    }

    return scale;
}

static string[] LabelForNumber(double number, int maxZeroDigits)
{
    int scale = ScaleNumber(number);

    string[] labels = new string[2];

    if (scale >= 0)
    {
        if (scale >= maxZeroDigits + 1)
            labels[0] = ((long)Math.Round(number / Math.Pow(10, scale))).ToString(CultureInfo.InvariantCulture);
        else
            labels[0] = ((long)number).ToString(CultureInfo.InvariantCulture);
    }
    else
    {
        labels[0] = ((long)Math.Round(number * Math.Pow(10, -scale))).ToString(CultureInfo.InvariantCulture);
    }

    if (scale < 0)
    {
        int leadingDecimalDigits = (-scale) - labels[0].Length;
        if (leadingDecimalDigits <= maxZeroDigits)
        {
            string zeros = new string('0', leadingDecimalDigits);
            labels[0] = "0." + zeros + labels[0].TrimEnd(new char[] { '0' });
            scale = 0;
        }
        else
        {
            // Put decimal point immediately after the first digit.
            scale += labels[0].Length - 1;
            labels[0] = labels[0][0] + "." + labels[0].Substring(1);
            labels[0] = labels[0].TrimEnd(new char[] { '0' });
            // If only one digit, append "0".
            if (labels[0].Length == 2)
                labels[0] = labels[0] + "0";
        }
    }
    else if (scale > maxZeroDigits)
    {
        // Put decimal point immediately after the first digit.
        scale -= labels[0].Length - 1;
        labels[0] = labels[0][0] + "." + labels[0].Substring(1);
        labels[0] = labels[0].TrimEnd(new char[] { '0' });
        // If only one digit, append "0".
        if (labels[0].Length == 2)
            labels[0] = labels[0] + "0";
    }

    // Add exponent as last labels.
    if (scale < 0 || scale > maxZeroDigits)
    {
        string exponent;
        if (scale < 0)
        {
            exponent = (-scale).ToString();
            if (exponent.Length == 1) exponent = "0" + exponent;
            exponent = "-" + exponent;
        }
        else
        {
            exponent = scale.ToString();
            if (exponent.Length == 1) exponent = "0" + exponent;
            exponent = "+" + exponent;
        }
        labels[1] = "e" + exponent;
    }

    return labels;
}

static int ScaleNumber(double number)
{
    int scale = (int)Math.Ceiling(Math.Log10(number));

    int bound = Math.Max(scale - 15, 0);

    while (scale >= bound)
    {
        if (Math.Round(number / Math.Pow(10, scale)) == number / Math.Pow(10, scale))
            return scale;
        scale--;
    }

    bound = Math.Min(-1, scale - 15);

    while (scale >= bound)
    {
        if (Math.Round(number * Math.Pow(10, -scale)) == number * Math.Pow(10, -scale))
            return scale;
        scale--;
    }

    return scale;
}

这里有几个例子,maxZeroDigits 分别设置为 3 和 2。
Ticks: 1 2 3 4 
MaxZeroDigits: 3
Labels: 1 2 3 4 
Exponent: 
Offset: 

Ticks: 10 11 12 13 
MaxZeroDigits: 3
Labels: 10 11 12 13 
Exponent: 
Offset: 

Ticks: 100 110 120 130 
MaxZeroDigits: 3
Labels: 100 110 120 130 
Exponent: 
Offset: 

Ticks: 1000 1100 1200 1300 
MaxZeroDigits: 3
Labels: 1000 1100 1200 1300 
Exponent: 
Offset: 

Ticks: 10000 11000 12000 13000 
MaxZeroDigits: 3
Labels: 10000 11000 12000 13000 
Exponent: 
Offset: 

Ticks: 100000 110000 120000 130000 
MaxZeroDigits: 3
Labels: 1.0 1.1 1.2 1.3 
Exponent: e+05
Offset: 

Ticks: 1.8E+15 1.9E+15 2E+15 2.1E+15 
MaxZeroDigits: 3
Labels: 1.8 1.9 2.0 2.1 
Exponent: e+15
Offset: 

Ticks: 1.8E+35 1.9E+35 2E+35 2.1E+35 
MaxZeroDigits: 3
Labels: 1.8 1.9 2.0 2.1 
Exponent: e+35
Offset: 

Ticks: 2000.000001 2000.0000015 2000.000002 2000.0000025 
MaxZeroDigits: 3
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-06
Offset: 2000

Ticks: 20000.00000105 20000.0000011 20000.00000115 20000.0000012 
MaxZeroDigits: 3
Labels: 1.05 1.10 1.15 1.20 
Exponent: e-06
Offset: 2.0e+04

Ticks: 2.000001 2.000002 2.000003 2.000004 
MaxZeroDigits: 3
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-06
Offset: 2

Ticks: 20.000001 20.000002 20.000003 20.000004 
MaxZeroDigits: 3
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-06
Offset: 20

Ticks: 200.000001 200.0000015 200.000002 200.0000025 
MaxZeroDigits: 3
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-06
Offset: 200

Ticks: 200000.000001 200000.000002 200000.000003 200000.000004 
MaxZeroDigits: 3
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-06
Offset: 2.0e+05

Ticks: 2.0000001E+35 2.0000002E+35 2.0000003E+35 2.0000004E+35 
MaxZeroDigits: 3
Labels: 1.0 2.0 3.0 4.0 
Exponent: e+29
Offset: 2.0e+35

Ticks: 0.1 0.15 0.2 0.25 
MaxZeroDigits: 3
Labels: 0.10 0.15 0.20 0.25 
Exponent: 
Offset: 

Ticks: 0.01 0.015 0.02 0.025 
MaxZeroDigits: 3
Labels: 0.010 0.015 0.020 0.025 
Exponent: 
Offset: 

Ticks: 0.001 0.0015 0.002 0.0025 
MaxZeroDigits: 3
Labels: 0.0010 0.0015 0.0020 0.0025 
Exponent: 
Offset: 

Ticks: 0.0001 0.00015 0.0002 0.00025 
MaxZeroDigits: 3
Labels: 0.00010 0.00015 0.00020 0.00025 
Exponent: 
Offset: 

Ticks: 1E-05 1.5E-05 2E-05 2.5E-05 
MaxZeroDigits: 3
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-05
Offset: 

Ticks: 1E-06 1.5E-06 2E-06 2.5E-06 
MaxZeroDigits: 3
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-06
Offset: 

Ticks: 1.8E-13 1.9E-13 2E-13 2.1E-13 
MaxZeroDigits: 3
Labels: 1.8 1.9 2.0 2.1 
Exponent: e-13
Offset: 

Ticks: 1.8E-33 1.9E-33 2E-33 2.1E-33 
MaxZeroDigits: 3
Labels: 1.8 1.9 2.0 2.1 
Exponent: e-33
Offset: 

Ticks: 2.0000001E-33 2.0000002E-33 2.0000003E-33 2.0000004E-33 
MaxZeroDigits: 3
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-40
Offset: 2.0e-33

Ticks: 2.00000000015E-30 2.0000000002E-30 2.00000000025E-30 2.0000000003E-30 
MaxZeroDigits: 3
Labels: 1.5 2.0 2.5 3.0 
Exponent: e-40
Offset: 2.0e-30

Ticks: 0.0010000010001 0.0010000010002 0.0010000010003 0.0010000010004 
MaxZeroDigits: 3
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-13
Offset: 0.001000001

Ticks: 0.0010000010001 0.00100000100015 0.0010000010002 0.00100000100025 
MaxZeroDigits: 3
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-13
Offset: 0.001000001

Ticks: 1000001000.1 1000001000.2 1000001000.3 1000001000.4 
MaxZeroDigits: 3
Labels: 0.1 0.2 0.3 0.4 
Exponent: 
Offset: 1000001000

Ticks: 1 2 3 4 
MaxZeroDigits: 2
Labels: 1 2 3 4 
Exponent: 
Offset: 

Ticks: 10 11 12 13 
MaxZeroDigits: 2
Labels: 10 11 12 13 
Exponent: 
Offset: 

Ticks: 100 110 120 130 
MaxZeroDigits: 2
Labels: 100 110 120 130 
Exponent: 
Offset: 

Ticks: 1000 1100 1200 1300 
MaxZeroDigits: 2
Labels: 1000 1100 1200 1300 
Exponent: 
Offset: 

Ticks: 10000 11000 12000 13000 
MaxZeroDigits: 2
Labels: 1.0 1.1 1.2 1.3 
Exponent: e+04
Offset: 

Ticks: 100000 110000 120000 130000 
MaxZeroDigits: 2
Labels: 1.0 1.1 1.2 1.3 
Exponent: e+05
Offset: 

Ticks: 1.8E+15 1.9E+15 2E+15 2.1E+15 
MaxZeroDigits: 2
Labels: 1.8 1.9 2.0 2.1 
Exponent: e+15
Offset: 

Ticks: 1.8E+35 1.9E+35 2E+35 2.1E+35 
MaxZeroDigits: 2
Labels: 1.8 1.9 2.0 2.1 
Exponent: e+35
Offset: 

Ticks: 2000.000001 2000.0000015 2000.000002 2000.0000025 
MaxZeroDigits: 2
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-06
Offset: 2.0e+03

Ticks: 20000.00000105 20000.0000011 20000.00000115 20000.0000012 
MaxZeroDigits: 2
Labels: 1.05 1.10 1.15 1.20 
Exponent: e-06
Offset: 2.0e+04

Ticks: 2.000001 2.000002 2.000003 2.000004 
MaxZeroDigits: 2
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-06
Offset: 2

Ticks: 20.000001 20.000002 20.000003 20.000004 
MaxZeroDigits: 2
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-06
Offset: 20

Ticks: 200.000001 200.0000015 200.000002 200.0000025 
MaxZeroDigits: 2
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-06
Offset: 200

Ticks: 200000.000001 200000.000002 200000.000003 200000.000004 
MaxZeroDigits: 2
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-06
Offset: 2.0e+05

Ticks: 2.0000001E+35 2.0000002E+35 2.0000003E+35 2.0000004E+35 
MaxZeroDigits: 2
Labels: 1.0 2.0 3.0 4.0 
Exponent: e+29
Offset: 2.0e+35

Ticks: 0.1 0.15 0.2 0.25 
MaxZeroDigits: 2
Labels: 0.10 0.15 0.20 0.25 
Exponent: 
Offset: 

Ticks: 0.01 0.015 0.02 0.025 
MaxZeroDigits: 2
Labels: 0.010 0.015 0.020 0.025 
Exponent: 
Offset: 

Ticks: 0.001 0.0015 0.002 0.0025 
MaxZeroDigits: 2
Labels: 0.0010 0.0015 0.0020 0.0025 
Exponent: 
Offset: 

Ticks: 0.0001 0.00015 0.0002 0.00025 
MaxZeroDigits: 2
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-04
Offset: 

Ticks: 1E-05 1.5E-05 2E-05 2.5E-05 
MaxZeroDigits: 2
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-05
Offset: 

Ticks: 1E-06 1.5E-06 2E-06 2.5E-06 
MaxZeroDigits: 2
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-06
Offset: 

Ticks: 1.8E-13 1.9E-13 2E-13 2.1E-13 
MaxZeroDigits: 2
Labels: 1.8 1.9 2.0 2.1 
Exponent: e-13
Offset: 

Ticks: 1.8E-33 1.9E-33 2E-33 2.1E-33 
MaxZeroDigits: 2
Labels: 1.8 1.9 2.0 2.1 
Exponent: e-33
Offset: 

Ticks: 2.0000001E-33 2.0000002E-33 2.0000003E-33 2.0000004E-33 
MaxZeroDigits: 2
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-40
Offset: 2.0e-33

Ticks: 2.00000000015E-30 2.0000000002E-30 2.00000000025E-30 2.0000000003E-30 
MaxZeroDigits: 2
Labels: 1.5 2.0 2.5 3.0 
Exponent: e-40
Offset: 2.0e-30

Ticks: 0.0010000010001 0.0010000010002 0.0010000010003 0.0010000010004 
MaxZeroDigits: 2
Labels: 1.0 2.0 3.0 4.0 
Exponent: e-13
Offset: 0.001000001

Ticks: 0.0010000010001 0.00100000100015 0.0010000010002 0.00100000100025 
MaxZeroDigits: 2
Labels: 1.0 1.5 2.0 2.5 
Exponent: e-13
Offset: 0.001000001

Ticks: 1000001000.1 1000001000.2 1000001000.3 1000001000.4 
MaxZeroDigits: 2
Labels: 0.1 0.2 0.3 0.4 
Exponent: 
Offset: 1.000001e-03

谢谢你的努力,这比我要求的更多了 :) - hageldave

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