it-roy-ru.com

Как мне разобрать строку с десятичной точкой в ​​двойном?

Я хочу разобрать строку типа "3.5" в двойную. Тем не мение, 

double.Parse("3.5") 

дает 35 и 

double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint) 

выбрасывает FormatException.

Теперь локаль моего компьютера установлена ​​на немецкий язык, где запятая используется в качестве десятичного разделителя. Возможно, придется что-то с этим делать, и double.Parse() ожидает "3,5" в качестве входных данных, но я не уверен.

Как я могу разобрать строку, содержащую десятичное число, которое может или не может быть отформатировано, как указано в моей текущей локали?

194
Legate
double.Parse("3.5", CultureInfo.InvariantCulture)
370
Mehrdad Afshari

Обычно я использую мультикультурную функцию для анализа ввода пользователя, главным образом потому, что если кто-то привык к цифровой клавиатуре и использует культуру, в которой в качестве десятичного разделителя используется запятая, этот человек будет использовать точку цифровой клавиатуры вместо запятой.

public static double GetDouble(string value, double defaultValue)
{
    double result;

    //Try parsing in the current culture
    if (!double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.CurrentCulture, out result) &&
        //Then try in US english
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.GetCultureInfo("en-US"), out result) &&
        //Then in neutral language
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out result))
    {
        result = defaultValue;
    }

    return result;
}

Осторожно, комментарии @nikie верны. В свою защиту я использую эту функцию в контролируемой среде, где я знаю, что культура может быть либо en-US, en-CA, либо fr-CA. Я использую эту функцию, потому что во французском языке мы используем запятую в качестве десятичного разделителя, но любой, кто когда-либо работал в финансах, всегда будет использовать десятичный разделитель на цифровой клавиатуре, но это точка, а не запятая. Так что даже в культуре fr-CA мне нужно проанализировать число, которое будет иметь точку в качестве десятичного разделителя.

65
Pierre-Alain Vigeant

Я не мог написать комментарий, поэтому я пишу здесь:

double.Parse ("3.5", CultureInfo.InvariantCulture)не очень хорошая идея, потому что в Канаде мы пишем 3,5 вместо 3,5, и в результате эта функция дает нам 35.

Я проверил оба на моем компьютере:

double.Parse("3.5", CultureInfo.InvariantCulture) --> 3.5 OK
double.Parse("3,5", CultureInfo.InvariantCulture) --> 35 not OK

Это правильный способ, которымPierre-Alain Vigeantупоминается

public static double GetDouble(string value, double defaultValue)
{
    double result;

    // Try parsing in the current culture
    if (!double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.CurrentCulture, out result) &&
        // Then try in US english
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.GetCultureInfo("en-US"), out result) &&
        // Then in neutral language
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out result))
    {
        result = defaultValue;
    }
    return result;
}
18
Malus Jan

Хитрость в том, чтобы использовать инвариантную культуру, чтобы разбирать точки во всех культурах.

double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);
15
Yakeen
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

Замените запятую точкой перед разбором. Полезно в странах с запятой в качестве десятичного разделителя. Подумайте об ограничении пользовательского ввода (при необходимости) одной запятой или точкой.

10
Baluda

Посмотрите, каждый ответ выше, который предлагает написать замену строки константной строкой, может быть только неправильным. Зачем? Потому что вы не уважаете региональные настройки Windows! Windows гарантирует пользователю свободу устанавливать любой символ разделителя, который он/она хочет. Он/она может открыть панель управления, перейти на панель региона, нажать на кнопку «Дополнительно» и изменить персонажа в любое время. Даже во время вашей программы. Подумай об этом. Хорошее решение должно знать об этом.

Итак, сначала вам нужно спросить себя, откуда этот номер, который вы хотите разобрать. Если он поступает из ввода в .NET Framework, нет проблем, потому что он будет в том же формате. Но, возможно, это было извне, может быть, с внешнего сервера, может быть, из старой БД, которая поддерживает только строковые свойства. Там администратор БД должен был дать правило, в каком формате должны храниться числа. Например, если вы знаете, что это будет БД США в американском формате, вы можете использовать этот фрагмент кода:

CultureInfo usCulture = new CultureInfo("en-US");
NumberFormatInfo dbNumberFormat = usCulture.NumberFormat;
decimal number = decimal.Parse(db.numberString, dbNumberFormat);

Это будет хорошо работать в любой точке мира. И, пожалуйста, не используйте «Convert.ToXxxx». Класс «Преобразование» рассматривается только как основа для преобразований в любом направлении. Кроме того: Вы можете использовать аналогичный механизм и для DateTimes.

9
AndresRohrAtlasInformatik
string testString1 = "2,457";
string testString2 = "2.457";    
double testNum = 0.5;
char decimalSepparator;
decimalSepparator = testNum.ToString()[1];

Console.WriteLine(double.Parse(testString1.Replace('.', decimalSepparator).Replace(',', decimalSepparator)));
Console.WriteLine(double.Parse(testString2.Replace('.', decimalSepparator).Replace(',', decimalSepparator)));
2
Martin

Я думаю, что 100% правильное преобразование невозможно, если значение поступает из пользовательского ввода. например если значение равно 123.456, это может быть группировка или десятичная точка. Если вам действительно нужно 100%, вы должны описать свой формат и сгенерировать исключение, если оно неверно.

Но я улучшил код JanW, поэтому мы немного опередили его до 100%. Идея заключается в том, что если последний разделитель является groupSeperator, это будет больше целочисленный тип, чем двойной.

Добавленный код находится в первом если из GetDouble .

void Main()
{
    List<string> inputs = new List<string>() {
        "1.234.567,89",
        "1 234 567,89",
        "1 234 567.89",
        "1,234,567.89",
        "1234567,89",
        "1234567.89",
        "123456789",
        "123.456.789",
        "123,456,789,"
    };

    foreach(string input in inputs) {
        Console.WriteLine(GetDouble(input,0d));
    }

}

public static double GetDouble(string value, double defaultValue) {
    double result;
    string output;

    // Check if last seperator==groupSeperator
    string groupSep = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
    if (value.LastIndexOf(groupSep) + 4 == value.Count())
    {
        bool tryParse = double.TryParse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out result);
        result = tryParse ? result : defaultValue;
    }
    else
    {
        // Unify string (no spaces, only . )
        output = value.Trim().Replace(" ", string.Empty).Replace(",", ".");

        // Split it on points
        string[] split = output.Split('.');

        if (split.Count() > 1)
        {
            // Take all parts except last
            output = string.Join(string.Empty, split.Take(split.Count()-1).ToArray());

            // Combine token parts with last part
            output = string.Format("{0}.{1}", output, split.Last());
        }
        // Parse double invariant
        result = double.Parse(output, System.Globalization.CultureInfo.InvariantCulture);
    }
    return result;
}
1
Schorsch

Следующий код выполняет работу в любом сценарии. Это немного разбор.

List<string> inputs = new List<string>()
{
    "1.234.567,89",
    "1 234 567,89",
    "1 234 567.89",
    "1,234,567.89",
    "123456789",
    "1234567,89",
    "1234567.89",
};
string output;

foreach (string input in inputs)
{
    // Unify string (no spaces, only .)
    output = input.Trim().Replace(" ", "").Replace(",", ".");

    // Split it on points
    string[] split = output.Split('.');

    if (split.Count() > 1)
    {
        // Take all parts except last
        output = string.Join("", split.Take(split.Count()-1).ToArray());

        // Combine token parts with last part
        output = string.Format("{0}.{1}", output, split.Last());
    }

    // Parse double invariant
    double d = double.Parse(output, CultureInfo.InvariantCulture);
    Console.WriteLine(d);
}
1
JanW

Это сложно без указания того, какой десятичный разделитель искать, но если вы это сделаете, я использую вот что:

    public static double Parse(string str, char decimalSep)
    {
        string s = GetInvariantParseString(str, decimalSep);
        return double.Parse(s, System.Globalization.CultureInfo.InvariantCulture);
    }

    public static bool TryParse(string str, char decimalSep, out double result)
    {
        // NumberStyles.Float | NumberStyles.AllowThousands got from Reflector
        return double.TryParse(GetInvariantParseString(str, decimalSep), NumberStyles.Float | NumberStyles.AllowThousands, System.Globalization.CultureInfo.InvariantCulture, out result);
    }

    private static string GetInvariantParseString(string str, char decimalSep)
    {
        str = str.Replace(" ", "");

        if (decimalSep != '.')
            str = SwapChar(str, decimalSep, '.');

        return str;
    }
    public static string SwapChar(string value, char from, char to)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        StringBuilder builder = new StringBuilder();

        foreach (var item in value)
        {
            char c = item;
            if (c == from)
                c = to;
            else if (c == to)
                c = from;

            builder.Append(c);
        }
        return builder.ToString();
    }

    private static void ParseTestErr(string p, char p_2)
    {
        double res;
        bool b = TryParse(p, p_2, out res);
        if (b)
            throw new Exception();
    }

    private static void ParseTest(double p, string p_2, char p_3)
    {
        double d = Parse(p_2, p_3);
        if (d != p)
            throw new Exception();
    }

    static void Main(string[] args)
    {
        ParseTest(100100100.100, "100.100.100,100", ',');
        ParseTest(100100100.100, "100,100,100.100", '.');
        ParseTest(100100100100, "100.100.100.100", ',');
        ParseTest(100100100100, "100,100,100,100", '.');
        ParseTestErr("100,100,100,100", ',');
        ParseTestErr("100.100.100.100", '.');
        ParseTest(100100100100, "100 100 100 100.0", '.');
        ParseTest(100100100.100, "100 100 100.100", '.');
        ParseTest(100100100.100, "100 100 100,100", ',');
        ParseTest(100100100100, "100 100 100,100", '.');
        ParseTest(1234567.89, "1.234.567,89", ',');    
        ParseTest(1234567.89, "1 234 567,89", ',');    
        ParseTest(1234567.89, "1 234 567.89",     '.');
        ParseTest(1234567.89, "1,234,567.89",    '.');
        ParseTest(1234567.89, "1234567,89",     ',');
        ParseTest(1234567.89, "1234567.89",  '.');
        ParseTest(123456789, "123456789", '.');
        ParseTest(123456789, "123456789", ',');
        ParseTest(123456789, "123.456.789", ',');
        ParseTest(1234567890, "1.234.567.890", ',');
    }

Это должно работать с любой культурой. Он не может правильно проанализировать строки, содержащие более одного десятичного разделителя, в отличие от реализаций, которые заменяют вместо swap.

0
osexpert

Я также улучшил код @JanW ...

Мне это нужно для форматирования результатов медицинских инструментов, и они также отправляют "> 1000", "23.3e02", "350E-02" и "ОТРИЦАТЕЛЬНЫЙ".

private string FormatResult(string vResult)
{
  string output;
  string input = vResult;

  // Unify string (no spaces, only .)
  output = input.Trim().Replace(" ", "").Replace(",", ".");

  // Split it on points
  string[] split = output.Split('.');

  if (split.Count() > 1)
  {
    // Take all parts except last
    output = string.Join("", split.Take(split.Count() - 1).ToArray());

    // Combine token parts with last part
    output = string.Format("{0}.{1}", output, split.Last());
  }
  string sfirst = output.Substring(0, 1);

  try
  {
    if (sfirst == "<" || sfirst == ">")
    {
      output = output.Replace(sfirst, "");
      double res = Double.Parse(output);
      return String.Format("{1}{0:0.####}", res, sfirst);
    }
    else
    {
      double res = Double.Parse(output);
      return String.Format("{0:0.####}", res);
    }
  }
  catch
  {
    return output;
  }
}
0
JacekK

Мои два цента на эту тему, пытаясь обеспечить общий метод двойного преобразования:

private static double ParseDouble(object value)
{
    double result;

    string doubleAsString = value.ToString();
    IEnumerable<char> doubleAsCharList = doubleAsString.ToList();

    if (doubleAsCharList.Where(ch => ch == '.' || ch == ',').Count() <= 1)
    {
        double.TryParse(doubleAsString.Replace(',', '.'),
            System.Globalization.NumberStyles.Any,
            CultureInfo.InvariantCulture,
            out result);
    }
    else
    {
        if (doubleAsCharList.Where(ch => ch == '.').Count() <= 1
            && doubleAsCharList.Where(ch => ch == ',').Count() > 1)
        {
            double.TryParse(doubleAsString.Replace(",", string.Empty),
                System.Globalization.NumberStyles.Any,
                CultureInfo.InvariantCulture,
                out result);
        }
        else if (doubleAsCharList.Where(ch => ch == ',').Count() <= 1
            && doubleAsCharList.Where(ch => ch == '.').Count() > 1)
        {
            double.TryParse(doubleAsString.Replace(".", string.Empty).Replace(',', '.'),
                System.Globalization.NumberStyles.Any,
                CultureInfo.InvariantCulture,
                out result);
        }
        else
        {
            throw new ParsingException($"Error parsing {doubleAsString} as double, try removing thousand separators (if any)");
        }
    }

    return result;
}

Работает как положено с:

  • 1,1
  • 1,1
  • 1000000000
  • 1.000.000.000
  • 1,000,000,000.99
  • 1.000.000.000,99
  • 5,000,111.3
  • 5.000.111,3
  • 0.99,000,111,88
  • 0,99.000.111.88

Преобразование по умолчанию не реализовано, поэтому он не сможет выполнить синтаксический анализ 1.3,14, 1,3.14 или подобных случаев.

0
eduherminio
        var doublePattern = @"(?<integer>[0-9]+)(?:\,|\.)(?<fraction>[0-9]+)";
        var sourceDoubleString = "03444,44426";
        var match = Regex.Match(sourceDoubleString, doublePattern);

        var doubleResult = match.Success ? double.Parse(match.Groups["integer"].Value) + (match.Groups["fraction"].Value == null ? 0 : double.Parse(match.Groups["fraction"].Value) / Math.Pow(10, match.Groups["fraction"].Value.Length)): 0;
        Console.WriteLine("Double of string '{0}' is {1}", sourceDoubleString, doubleResult);
0
Alexander

Вместо того, чтобы указывать языковой стандарт во всех синтаксических анализах, я предпочитаю устанавливать языковой стандарт для всего приложения, хотя, если строковые форматы не согласованы в приложении, это может не сработать.

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-PT");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("pt-PT");

Если вы определите это в начале вашего приложения, все двойные синтаксические разборки будут ожидать запятую в качестве десятичного разделителя. Вы можете установить соответствующий языковой стандарт, чтобы десятичный разделитель и разделитель тысяч подходили для строк, которые вы анализируете.

0
BlunT