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

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

double.Parse("3.5") 

дает 35 и

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

бросает FormatException,

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

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

20 ответов

double.Parse("3.5", CultureInfo.InvariantCulture)

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

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 мне нужно проанализировать число, которое будет иметь точку в качестве десятичного разделителя.

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

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

Это правильный путь, о котором упоминал Пьер-Ален Вижан

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;
}
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

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

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

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

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

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

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

double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);
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)));

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

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 или подобные случаи.

Я думаю, что 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;
}
        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);

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

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);
}

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

Сначала удалите все, что не является числом, запятой, точкой или знаком минус.

string stringAmount = Regex.Replace(originalString, @"[^0-9\.\-,]", "");

Затем мы разбиваем число на целое и десятичное число.

string[] decimalParsed = Regex.Split(stringAmount, @"(?:\.|,)(?=\d{2}$)");

(Это выражение Regex выбирает запятую или точку, которая является двумя числами от конца строки.)

Теперь берем целое число и убираем из него запятые и точки.

string wholeAmount = decimalParsed[0].Replace(",", "").Replace(".", "");

if (wholeAmount.IsNullOrEmpty())
        wholeAmount = "0";

Теперь обрабатываем десятичную часть, если таковая имеется.

string decimalAmount = "00";

if (decimalParsed.Length == 2)
    {
        decimalAmount = decimalParsed[1];
    }

Наконец, мы можем сложить целое и десятичное и проанализировать Double.

double amount = $"{wholeAmount}.{decimalAmount}".ToDouble();

Это будет обрабатывать 200,00, 1 000,00, 1,000, 1.000,33, 2,000,000,78 и т. Д.

Я разрабатываю приложение .Net Maui, которое работает на Windows, Mac, Android и iPhone. У меня есть 3 двойных значения, которые я анализирую и сохраняю, используя '.' (например, "32,5") во всех случаях: широта, долгота и высота. У меня так получилось, что Android и iPhone настроены на испанский язык, и я заметил, что Android проанализировал «.». струна в порядке. Однако iPhone отказывался правильно анализировать его, если я не заменил «.» на «.». В противном случае результат всегда был огромным числом.

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

                           case "Lat":
                        waypoint.Lat = ParseDouble(xmlVal, 90);
                        break;
                     case "Lon":
                        waypoint.Lon = ParseDouble(xmlVal, 180);
                        break;
                     case "Alt":
                        waypoint.Alt = ParseDouble(xmlVal, 32000);

   public static double ParseDouble(string val, double limit)
   {
      double result;
      if (double.TryParse(val, out result))
      {
         if (Math.Abs(result) <= limit)
            return result;
         else if (double.TryParse(val.Replace('.', ','), out result))
         {
            if (Math.Abs(result) <= limit)
               return result;
         }
      }
      return 0;
   }

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

    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.

Я также улучшил код @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;
  }
}

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

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

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

System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;

string _pos = dblstr.Replace(".",
    ci.NumberFormat.NumberDecimalSeparator).Replace(",",
        ci.NumberFormat.NumberDecimalSeparator);

double _dbl = double.Parse(_pos);

Я думаю, что это лучший ответ:

public static double StringToDouble(string toDouble)
{
    toDouble = toDouble.Replace(",", "."); //Replace every comma with dot

    //Count dots in toDouble, and if there is more than one dot, throw an exception.
    //Value such as "123.123.123" can't be converted to double
    int dotCount = 0;
    foreach (char c in toDouble) if (c == '.') dotCount++; //Increments dotCount for each dot in toDouble
    if (dotCount > 1) throw new Exception(); //If in toDouble is more than one dot, it means that toCount is not a double

    string left = toDouble.Split('.')[0]; //Everything before the dot
    string right = toDouble.Split('.')[1]; //Everything after the dot

    int iLeft = int.Parse(left); //Convert strings to ints
    int iRight = int.Parse(right);

    //We must use Math.Pow() instead of ^
    double d = iLeft + (iRight * Math.Pow(10, -(right.Length)));
    return d;
}

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

double val;

if (temp.Text.Split('.').Length > 1)
{
    val = double.Parse(temp.Text.Split('.')[0]);

    if (temp.Text.Split('.')[1].Length == 1)
        val += (0.1 * double.Parse(temp.Text.Split('.')[1]));
    else
        val += (0.01 * double.Parse(temp.Text.Split('.')[1]));
}
else
    val = double.Parse(RR(temp.Text));

Умножьте число, а затем разделите его на то, на что вы умножили его ранее.

Например,

perc = double.Parse("3.555)*1000;
result = perc/1000
Другие вопросы по тегам