我有一个UTF-8格式的数据文件,其中包含数千个浮点数。在设计时,开发人员决定省略指数表示法中的'e'以节省空间。因此数据看起来像:
1.85783+16 0.000000+0 1.900000+6-3.855418-4 1.958263+6 7.836995-4
-2.000000+6 9.903130-4 2.100000+6 1.417469-3 2.159110+6 1.655700-3
2.200000+6 1.813662-3-2.250000+6-1.998687-3 2.300000+6 2.174219-3
2.309746+6 2.207278-3 2.400000+6 2.494469-3 2.400127+6 2.494848-3
-2.500000+6 2.769739-3 2.503362+6 2.778185-3 2.600000+6 3.020353-3
2.700000+6 3.268572-3 2.750000+6 3.391230-3 2.800000+6 3.512625-3
2.900000+6 3.750746-3 2.952457+6 3.872690-3 3.000000+6 3.981166-3
3.202512+6 4.437824-3 3.250000+6 4.542310-3 3.402356+6 4.861319-3
问题是float.Parse()不适用于这种格式。我的中间解决方案是,
protected static float ParseFloatingPoint(string data)
{
int signPos;
char replaceChar = '+';
// Skip over first character so that a leading + is not caught
signPos = data.IndexOf(replaceChar, 1);
// Didn't find a '+', so lets see if there's a '-'
if (signPos == -1)
{
replaceChar = '-';
signPos = data.IndexOf('-', 1);
}
// Found either a '+' or '-'
if (signPos != -1)
{
// Create a new char array with an extra space to accomodate the 'e'
char[] newData = new char[EntryWidth + 1];
// Copy from string up to the sign
for (int i = 0; i < signPos; i++)
{
newData[i] = data[i];
}
// Replace the sign with an 'e + sign'
newData[signPos] = 'e';
newData[signPos + 1] = replaceChar;
// Copy the rest of the string
for (int i = signPos + 2; i < EntryWidth + 1; i++)
{
newData[i] = data[i - 1];
}
return float.Parse(new string(newData), NumberStyles.Float, CultureInfo.InvariantCulture);
}
else
{
return float.Parse(data, NumberStyles.Float, CultureInfo.InvariantCulture);
}
}
我无法调用简单的String.Replace(),因为它将替换任何前导的负号。我可以使用子串,但后来我正在制作很多额外的字符串而且我对性能感到担忧。
有没有人对此有更优雅的解决方案?
答案 0 :(得分:3)
string test = "1.85783-16";
char[] signs = { '+', '-' };
int decimalPos = test.IndexOf('.');
int signPos = test.LastIndexOfAny(signs);
string result = (signPos > decimalPos) ?
string.Concat(
test.Substring(0, signPos),
"E",
test.Substring(signPos)) : test;
float.Parse(result).Dump(); //1.85783E-16
我在这里使用的想法确保小数位于符号之前(因此如果指数丢失则避免任何问题)以及使用LastIndexOf()从后面工作(确保我们有指数,如果存在) 。如果有可能是前缀“+”,则第一个如果需要包含|| signPos < decimalPos
。
其他结果:
"1.85783" => "1.85783"; //Missing exponent is returned clean
"-1.85783" => "-1.85783"; //Sign prefix returned clean
"-1.85783-3" => "-1.85783e-3" //Sign prefix and exponent coexist peacefully.
根据评论,这个方法的测试显示只有5%的性能命中(在避免String.Format()之后,我应该记住它是非常糟糕的)。我认为代码更清晰:只做出一个决定。
答案 1 :(得分:2)
就速度而言,你原来的解决方案是我迄今为止尝试过的最快的(@ Godeke是非常接近的第二个)。 @ Godeke有很多可读性,只是性能下降很小。添加一些稳健性检查,他可能是长期的方法。在稳健性方面,您可以将其添加到您的内容中:
static char[] signChars = new char[] { '+', '-' };
static float ParseFloatingPoint(string data)
{
if (data.Length != EntryWidth)
{
throw new ArgumentException("data is not the correct size", "data");
}
else if (data[0] != ' ' && data[0] != '+' && data[0] != '-')
{
throw new ArgumentException("unexpected leading character", "data");
}
int signPos = data.LastIndexOfAny(signChars);
// Found either a '+' or '-'
if (signPos > 0)
{
// Create a new char array with an extra space to accomodate the 'e'
char[] newData = new char[EntryWidth + 1];
// Copy from string up to the sign
for (int ii = 0; ii < signPos; ++ii)
{
newData[ii] = data[ii];
}
// Replace the sign with an 'e + sign'
newData[signPos] = 'e';
newData[signPos + 1] = data[signPos];
// Copy the rest of the string
for (int ii = signPos + 2; ii < EntryWidth + 1; ++ii)
{
newData[ii] = data[ii - 1];
}
return Single.Parse(
new string(newData),
NumberStyles.Float,
CultureInfo.InvariantCulture);
}
else
{
Debug.Assert(false, "data does not have an exponential? This is odd.");
return Single.Parse(data, NumberStyles.Float, CultureInfo.InvariantCulture);
}
}
我的X5260的基准测试(包括了解各个数据点的时间):
Code Average Runtime Values Parsed
--------------------------------------------------
Nothing (Overhead) 13 ms 0
Original 50 ms 150000
Godeke 60 ms 150000
Original Robust 56 ms 150000
答案 2 :(得分:1)
感谢Godeke对您的改进进行改进。
我最后更改了解析函数的参数以获取char []而不是字符串,并使用您的基本前提来提出以下内容。
protected static float ParseFloatingPoint(char[] data)
{
int decimalPos = Array.IndexOf<char>(data, '.');
int posSignPos = Array.LastIndexOf<char>(data, '+');
int negSignPos = Array.LastIndexOf<char>(data, '-');
int signPos = (posSignPos > negSignPos) ? posSignPos : negSignPos;
string result;
if (signPos > decimalPos)
{
char[] newData = new char[data.Length + 1];
Array.Copy(data, newData, signPos);
newData[signPos] = 'E';
Array.Copy(data, signPos, newData, signPos + 1, data.Length - signPos);
result = new string(newData);
}
else
{
result = new string(data);
}
return float.Parse(result, NumberStyles.Float, CultureInfo.InvariantCulture);
}
我将函数的输入从string更改为char [],因为我想远离ReadLine()。我假设这会比创建大量字符串更好。相反,我从数据文件中获取固定数量的字节(因为它总是11个字符宽度数据),将byte []转换为char [],然后执行上述处理以转换为float。
答案 3 :(得分:0)
答案 4 :(得分:0)
为什么不编写一个简单的脚本来重新格式化数据文件一次然后使用float.Parse()
?
你说“成千上万”的浮点数,所以即使一个非常天真的方法也会很快完成(如果你说“万亿”我会更犹豫),而你只需要运行一次的代码(几乎)从不对性能至关重要。当然,运行然后将问题发布到SO需要花费更少的时间,并且错误的机会更少。