将子字符串直接解析为double

本文关键字:double 字符串 | 更新日期: 2023-09-27 18:29:27

如果我有一个像1 2 3这样的字符串,并且我确定了包含double的子字符串的位置,我如何直接从子字符串中解析它而不创建临时字符串?

例如,我可以执行System.Double.Parse(str.Substring(0, 1)),但这将创建一个缓慢且不必要的临时字符串。是否可以直接从原始字符串的一部分解析double?

编辑

埃里克·利珀特(Eric Lippert)对我在这里的动机提出了质疑,他说"小绳子很便宜"。这样做的动机来自于我对int的解析做了同样的事情,并看到了巨大的性能改进,因为显然,小字符串并不那么便宜。

下面是一个函数,它通过临时字符串对int序列进行lexes处理:

let lex f (s: string) =
  let rec inside i0 (s: string, i) =
    if i = s.Length then
      f (s.Substring(i0, i-i0) |> System.Int32.Parse)
    else
      let c = s.[i]
      if '0'<=c && c<='9' then
        inside i0 (s, i+1)
      else
        f (s.Substring(i0, i-i0) |> System.Int32.Parse)
        outside (s, i)
  and outside (s: string, i) =
    if i < s.Length then
      let c = s.[i]
      if '0'<=c && c<='9' then
        inside i (s, i)
      else
        outside (s, i+1)
  outside (s, 0)

从一个字符串到lex 15625000 int需要2.4秒。

这里有一个避免临时字符串的版本:

let lex f (s: string) =
  let rec inside n (s: string, i) =
    if i = s.Length then f n else
      let c = s.[i]
      if '0'<=c && c<='9' then
        inside (10*n + int c - int '0') (s, i+1)
      else
        f n
        outside (s, i)
  and outside (s: string, i) =
    if i < s.Length then
      let c = s.[i]
      if '0'<=c && c<='9' then
        inside 0 (s, i)
      else
        outside (s, i+1)
  outside (s, 0)

这需要0.255秒,比使用临时字符串的解决方案快9倍多!

我看不出为什么lexing float会有什么不同。因此,不提供从子字符串解析浮点值的能力。NET在表上留下了一个数量级的性能。我做了很多科学计算,经常需要处理大量数据,尤其是在刚开始的时候,所以我真的不想像这样把性能抛到九霄云外。

将子字符串直接解析为double

是的,我认为这是完全可行的。您可以编写自己的函数来进行解析,甚至可以基于Double.Parse()的实际源代码进行解析。这段代码看起来并不庞大和可怕,我认为您可以根据自己的需要对其进行更多的优化。

您可以逐位解析字符串,如下所示:

static double CustomConvertToDouble(string input, int startIndex, int length)
{
    double result = 0d;
    int lastDigitIndex = startIndex + length - 1;
    int power = 0;
    for (int i = lastDigitIndex; i >= startIndex; i--)
    {
        int digit = (input[i] - '0');
        result += (Math.Pow(10, power++)) * digit;
    }
    return result;
}

用法:

string tmp = "1 2 3";
double result = CustomConvertToDouble(tmp, 0, 1);
Console.WriteLine(result); // 1

你可以对此进行扩展,将小数点等因素考虑在内。

但我真的怀疑正常的方式是否会成为性能瓶颈,我很想知道你为什么要麻烦。如果这段代码真的对性能很关键,也许最好的方法是用另一种语言编写它?

如果你只寻找个位数,这很容易:

let readDigit s i =
    let getDigit x =
        if '0' <= x && x <= '9'
        then byte x - 48uy // byte value of '0'
        else failwith "Not a digit"
    s |> Seq.item i |> getDigit |> double

该F#实现利用string实现char seq,并且char值可以转换为byte值。

不过,我怀疑它是否比使用Double.Parse(str.Substring(0, 1))更快。

for (int x = 0; x < input.Length; x++)
{
    if(input[x] != ' ')
        Console.WriteLine(Double.Parse(input[x].ToString()));
}

不创建任何额外的可枚举对象,但Double.Parse只例外字符串,因此需要一个toString。

这是你能做的最好的

static void Main(string[] args)
{
    string input = "1 2 3";
    double[] output = input.Split(new char[] {' '},StringSplitOptions.RemoveEmptyEntries).Select(x => double.Parse(x)).ToArray();
}