在字符串中的已知起始位置解析/读取双精度值

本文关键字:读取 双精度 位置 字符串 | 更新日期: 2023-09-27 17:56:52

有什么东西可以从字符串中的已知起始位置读取双精度吗?

我想避免分配double.Parse(s.Substring(...))此外,数字样式和文化的分裂也并非平凡。

感觉框架中一定有什么东西,但我找不到它。

签名如下所示:

var d = double.Parse(text, startPos, numberstyles, formatProvider, out endPos);

在字符串中的已知起始位置解析/读取双精度值

这是我写的,是的,它分配:

using System;
using System.Globalization;
internal static class DoubleParser
{
    private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite |
                                                       NumberStyles.AllowTrailingWhite |
                                                       NumberStyles.AllowLeadingSign |
                                                       NumberStyles.AllowTrailingSign |
                                                       NumberStyles.AllowParentheses |
                                                       NumberStyles.AllowDecimalPoint |
                                                       NumberStyles.AllowThousands |
                                                       NumberStyles.AllowExponent |
                                                       NumberStyles.AllowCurrencySymbol |
                                                       NumberStyles.AllowHexSpecifier);
    private const string LeadingSignNotAllowed = "Leading sign not allowed";
    private const string ExponentNotAllowed = "Exponent not allowed";
    private const string DecimalPointNotAllowed = "Decimal point not allowed";
    internal static double Parse(
        string text,
        int start,
        NumberStyles style,
        IFormatProvider provider,
        out int endPos)
    {
        if (!IsValidFloatingPointStyle(style))
        {
            throw new ArgumentException("Invalid NumberStyles", nameof(style));
        }
        if (style.HasFlag(NumberStyles.AllowHexSpecifier))
        {
            throw new ArgumentException("Hex not supported", nameof(style));
        }
        double result;
        if (TryParse(text, start, style, provider, out result, out endPos))
        {
            return result;
        }
        var message = $"Expected to find a double starting at index {start}'r'n" +
                      $"String: {text}'r'n" +
                      $"        {new string(' ', start)}^";
        throw new FormatException(message);
    }
    internal static bool TryParse(
        string text,
        int start,
        NumberStyles style,
        IFormatProvider provider,
        out double result,
        out int endPos)
    {
        result = 0;
        endPos = start;
        if (string.IsNullOrEmpty(text))
        {
            return false;
        }
        if (!IsValidFloatingPointStyle(style))
        {
            return false;
        }
        if (style.HasFlag(NumberStyles.AllowHexSpecifier))
        {
            return false;
        }
        if (style.HasFlag(NumberStyles.AllowLeadingWhite))
        {
            ReadWhite(text, ref endPos);
        }
        if (char.IsWhiteSpace(text[endPos]))
        {
            return false;
        }
        if (TryParseDigits(text, endPos, style, provider, out result, out endPos))
        {
            return true;
        }
        if (provider == null)
        {
            provider = CultureInfo.CurrentCulture;
        }
        var format = NumberFormatInfo.GetInstance(provider);
        if (TryRead(text, ref endPos, format.NaNSymbol))
        {
            result = double.NaN;
            return true;
        }
        if (TryRead(text, ref endPos, format.PositiveInfinitySymbol))
        {
            result = double.PositiveInfinity;
            return true;
        }
        if (TryRead(text, ref endPos, format.NegativeInfinitySymbol))
        {
            result = double.NegativeInfinity;
            return true;
        }
        endPos = start;
        return false;
    }
    // Try parse a double from digits ignoring +-Inf and NaN
    private static bool TryParseDigits(
        string text,
        int start,
        NumberStyles style,
        IFormatProvider provider,
        out double result,
        out int end)
    {
        end = start;
        var format = NumberFormatInfo.GetInstance(provider);
        Sign sign;
        if (TryReadSign(text, ref end, format, out sign))
        {
            if (!style.HasFlag(NumberStyles.AllowLeadingSign))
            {
                result = 0;
                return false;
            }
        }
        TryReadDigits(text, ref end);
        if (TryRead(text, ref end, format.NumberDecimalSeparator))
        {
            if (!style.HasFlag(NumberStyles.AllowDecimalPoint))
            {
                result = 0;
                return false;
            }
            TryReadDigits(text, ref end);
        }
        if (TryReadExponent(text, ref end))
        {
            if (!style.HasFlag(NumberStyles.AllowExponent))
            {
                result = 0;
                return false;
            }
            TryReadSign(text, ref end, format, out sign);
            if (TryReadDigits(text, ref end))
            {
                return TryParseSubString(text, start, ref end, style, provider, out result);
            }
            // This is a tricky spot we read digits followed by (sign) exponent 
            // then no digits were thrown. I choose to return the double here.
            // Both alternatives will be wrong in some situations.
            // returning false here would make it impossible to parse 1.2eV
            var backStep = sign == Sign.None
                ? 1
                : 2;
            end -= backStep;
            return TryParseSubString(text, start, ref end, style, provider, out result);
        }
        return TryParseSubString(text, start, ref end, style, provider, out result);
    }
    private static bool TryParseSubString(
        string text,
        int start,
        ref int end,
        NumberStyles style,
        IFormatProvider provider,
        out double result)
    {
        var s = text.Substring(start, end - start);
        var success = double.TryParse(s, style, provider, out result);
        if (!success)
        {
            end = start;
        }
        return success;
    }
    private static bool TryReadSign(string s,
        ref int pos,
        NumberFormatInfo format,
        out Sign sign)
    {
        if (TryRead(s, ref pos, format.PositiveSign))
        {
            sign = Sign.Positive;
            return true;
        }
        if (TryRead(s, ref pos, format.NegativeSign))
        {
            sign = Sign.Negative;
            return true;
        }
        sign = Sign.None;
        return false;
    }
    private static bool TryReadExponent(
        string s,
        ref int pos)
    {
        if (TryRead(s, ref pos, "E"))
        {
            return true;
        }
        if (TryRead(s, ref pos, "e"))
        {
            return true;
        }
        return false;
    }
    private static bool TryReadDigits(string s, ref int pos)
    {
        var start = pos;
        while (pos < s.Length && char.IsDigit(s[pos]))
        {
            pos++;
        }
        return pos != start;
    }
    private static bool TryRead(string s, ref int pos, string toRead)
    {
        if (pos == s.Length)
        {
            return false;
        }
        int start = pos;
        while (pos < s.Length &&
               pos - start < toRead.Length)
        {
            if (s[pos] != toRead[pos - start])
            {
                pos = start;
                return false;
            }
            pos++;
        }
        return true;
    }
    private static void ReadWhite(string s, ref int pos)
    {
        while (pos < s.Length && Char.IsWhiteSpace(s[pos]))
        {
            pos++;
        }
    }
    private static bool IsValidFloatingPointStyle(NumberStyles style)
    {
        // Check for undefined flags
        return (style & InvalidNumberStyles) == 0;
    }
}

可悲的是,它分配了一个子字符串。IIRC 下一版本的 C# 将为子字符串提供更便宜的分配,在此之前,所有令人讨厌的优化代码都将被浪费:)它处理我能想到的测试用例。只要它是有效的双精度,它就会读取。这个问题在一般情况下是不可能解决的,但它适用于我需要它的东西。

也许它对某人有用。