Существует ли алгоритм для определения «хорошего» форматирования чисел для последовательности чисел произвольного порядка? - PullRequest
1 голос
/ 24 мая 2019

В настоящее время я использую реализацию расширенного алгоритма Уилкинсона для генерации последовательности значений тиков по оси.Для этого алгоритму присваивается диапазон значений [min, max] и число n желаемых значений отметок, затем он выводит массив равномерно распределенных значений в интервале [min, max].Что мне нужно сделать, это создать метки String из этих значений, НО в зависимости от порядка величины этих значений. Я бы хотел переключиться между научной и десятичной нотациями.

Например, для последовательности {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 .0001', '86 .0002 ', '86 .0002' для {86.0001, 86.00015, 86.0002, 86.00025}.

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 Ответ

0 голосов
/ 26 мая 2019

Первая проблема при получении двойных чисел ticks заключается в округлении их с наименьшим количеством цифр, которые делают их различимыми. Это то, что делает функция ниже ScaleForTicks. If находит наибольшую степень 10, которая может масштабировать все ticks до целых чисел, сохраняя их различимыми. Для ticks >= 0 масштабирование означает деление на степень 10, а для ticks < 1 это умножение на степень 10. Как только ticks масштабируется до целого числа, мы округляем их до 0 десятичных знаков. Это дает нам наши базовые ярлыки. Они по-прежнему требуют дополнительной обработки в зависимости от применяемой мощности 10.

В вопросе не говорилось, сколько последовательных нулей допустимо иметь на этикетке. Итак, я добавил параметр maxZeroDigits в функцию LabelsForTicks. Таким образом, метка не будет отображаться с научной нотацией, если она содержит 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
...