是否有一种算法可以为任意数量级的数字序列找出 'nice' 数字格式?
Is there an algorithm to figure out a 'nice' number formatting for a sequence of numbers of arbitrary order of magnitude?
我目前正在使用 Extended Wilkinson Algorithm 的实现来生成一系列轴刻度值。为此,给算法一个取值范围 [min,max] 和 n 个所需的刻度线值,然后它在区间 [min,max] 中输出一个均匀 spaced 值的数组。我需要做的是,根据这些值创建字符串标签,但根据这些值的数量级,我想在科学计数法和十进制计数法之间切换。
例如,对于序列 {0.00001, 0.000015, 0.00002, 0.000025} 我想使用科学记数法 {'1.0e-05','1.5e-05','2.0e-05','2.5 e-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} 不好。
我们的目标是有一个一致的标签,让人们区分值并且可以很好地阅读,以便非常小或非常大的值以科学记数法表示,以节省显示 space。
因此,用于序列的表示不取决于单个值本身,而是必须考虑整个序列。
是否有可用的软件包或一些研究论文与此有关?
我尝试自己实现一些东西,但这不是很好,有时它会为不同的数字输出相同的字符串,例如{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' 选项来决定是使用科学计数法还是使用十进制计数法,然后尝试去除不必要的零。
收到 ticks
双打时的第一个问题是用使它们不同的最小数字舍入它们。这就是下面的函数 ScaleForTicks
所做的。如果找到 10 的最大幂,可以将所有 ticks
缩放为整数,同时保持它们不同。对于ticks >= 0
,缩放意味着除以10的幂,对于ticks < 1
,它意味着乘以10的幂。一旦ticks
被缩放为整数,我们将它们四舍五入为0 位小数。这给了我们基本标签。它们仍然需要额外的处理,具体取决于所应用的 10 的幂。
问题没有说明标签中可以接受多少个连续的 0。因此,我将 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
我目前正在使用 Extended Wilkinson Algorithm 的实现来生成一系列轴刻度值。为此,给算法一个取值范围 [min,max] 和 n 个所需的刻度线值,然后它在区间 [min,max] 中输出一个均匀 spaced 值的数组。我需要做的是,根据这些值创建字符串标签,但根据这些值的数量级,我想在科学计数法和十进制计数法之间切换。
例如,对于序列 {0.00001, 0.000015, 0.00002, 0.000025} 我想使用科学记数法 {'1.0e-05','1.5e-05','2.0e-05','2.5 e-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} 不好。
我们的目标是有一个一致的标签,让人们区分值并且可以很好地阅读,以便非常小或非常大的值以科学记数法表示,以节省显示 space。
因此,用于序列的表示不取决于单个值本身,而是必须考虑整个序列。 是否有可用的软件包或一些研究论文与此有关?
我尝试自己实现一些东西,但这不是很好,有时它会为不同的数字输出相同的字符串,例如{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' 选项来决定是使用科学计数法还是使用十进制计数法,然后尝试去除不必要的零。
收到 ticks
双打时的第一个问题是用使它们不同的最小数字舍入它们。这就是下面的函数 ScaleForTicks
所做的。如果找到 10 的最大幂,可以将所有 ticks
缩放为整数,同时保持它们不同。对于ticks >= 0
,缩放意味着除以10的幂,对于ticks < 1
,它意味着乘以10的幂。一旦ticks
被缩放为整数,我们将它们四舍五入为0 位小数。这给了我们基本标签。它们仍然需要额外的处理,具体取决于所应用的 10 的幂。
问题没有说明标签中可以接受多少个连续的 0。因此,我将 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