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


231

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

double.Parse("3.5") 

дает 35 и

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

бросает FormatException .

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

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


Десятичная запятая, безусловно, будет влиять на вывод.
ChrisF

12
Не забывайте о методе double.TryParse (), если он подходит для вашей ситуации.
Кайл Гагнет

Ответы:


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

Мне нравится использовать XmlConvertкласс ... есть ли у вас идеи, лучше ли это, хуже и / или отличается от использования CultureInfo.InvariantCulture?
ChrisW

1
Ну, на XmlConvertсамом деле не предназначен для анализа одного двойного значения в коде. Я предпочитаю использовать double.Parseили Convert.ToDoubleэто делает мое намерение очевидным.
Мехрдад Афшари

4
Это означает, что doulble.Parse использует культуру по умолчанию, которая не может содержать точку в качестве десятичной точки ??
Ахмед Саид

3
при преобразовании 12345678.12345678 он также преобразует 12345678.123457
PUG

4
у меня не сработало: пропускает запятые и возвращает и int как double
fnc12

75

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

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


18
Я не уверен, что это хорошая идея. Вы не можете надежно проанализировать двойное число, если не знаете культуру: в Германии двойные значения могут содержать символы «.», Но там они считаются разделителями тысяч. Таким образом, в случае Legate, GetDouble ("3.5") вернул бы 35 в немецком языке и 3.5 в среде en-us.
Ники

Нет, Пьер Ален прав, как написано. Если ваша культура говорит, что «точка - это разделитель тысяч», то «3,5» рассматривается как «35», и это хорошо. Однако, если вы придерживаетесь правил о том, что для «точки» нет правил, то символ анализируется как десятичная точка, и это также работает. Я бы не стал пробовать культуру enUS, но это личный выбор.
xryl669

Если вы используете numpad в культуре с запятой, то точка будет установлена ​​через запятую.
CrazyBaran

Десятичный разделитель numpad зависит от раскладки клавиатуры (а не от региональных настроек - по крайней мере, в Windows 7) (я использую венгерский для написания текста, электронной почты ... и en-US для написания кода, поэтому он может быть либо точечным или запятую. Я также использую настроенные региональные настройки, где я изменил десятичный разделитель с ',' на '.' и разделитель списка с ';' на ','. Вы знаете, вызывать CSV ... Удачи нам всем, пишущим несколько -культурные приложения;)
Стивен Спарк

25

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

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

1
Re: «... потому что в Канаде мы пишем 3,5 вместо 3,5» Вы уверены в этом? Согласно десятичной метке : «Страны, где точка». «Используется как десятичная метка, включают ... Канаду (при использовании английского)» . Разве это не больше об использовании французской версии Windows?
Питер Мортенсен

Baybe из-за французской версии. В Монреале мы пишем 3,5, а не 3,5
Malus Jan

Так что по вашей логике всегда только 1 из них возвращает true?
Batmaci

Посмотрите на это
Malus Jan

Все еще есть ошибка. Для входной строки, такой как GetDouble ("10 ,,,,,,,, 0", 0.0). Упомянутая функция возвращает 100.
Криверс

21
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

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


1
Гораздо более правильный ответ, чем тот, у которого +133 голоса ... Он позволяет жить в обеих системах с "," или "." десятичный разделитель ...
Badiboy

@Badiboy, ты можешь объяснить, что не так с этим ответом? Как я понимаю, InvariantCulture всегда имеет «.» в качестве десятичного разделителя. Так что это должно работать для обеих систем.
Алекс П.

@ Alex11223 Вы правы. Вот почему я сказал, что этот ответ лучше, чем более популярный. PS: дружелюбно говоря, этот код также потерпит неудачу, если у вас есть "," в качестве разделителя списков (т.е. 1 234 560,01), но я вообще не знаю, как это решить. :)
Badiboy

4
Это не очень хороший ответ, потому что в некоторых культурах это разделитель тысяч, который можно использовать. Если вы замените его точкой, у вас будет несколько точек, и синтаксический анализ завершится неудачно: Double.Parse ((12345.67) .ToString ("N", new CultureInfo ("en")). Replace (',', '. '), CultureInfo.InvariantCulture), поскольку (12345.67) .ToString ("N", new CultureInfo ("en")). Replace (', ','. ') Будет отформатирован как "12.345.67"
codingdave

1 234,56 -> 1,234,56 не анализатор. другая идея - проверить, содержит ли число «.» и ',' и заменить ',' пустой строкой и, если только ',' представленная запятая замените его на '.'
GDocal

16

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

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

11

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

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

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

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


Согласовано! Попытка вручную реализовать функции Культуры в конечном итоге приведет к тому, что вы не ожидали, и к большой головной боли. Пусть .NET справится с этим правильно.
Халос

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

3
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

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

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


1
«1000», задуманная как тысяча, потерпит неудачу.
Defkon1

1

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

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

2
1.234.567.890 вернется 1234567.890
Дэн Фогель

Я не пробовал, но если вы запустите приложение в разных культурах, этот код не сработает, я думаю: /
Dani bISHOP

1

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

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

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

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


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.


0

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

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

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

double _dbl = double.Parse(_pos);

-3

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

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

Объяснение вашего кода и предоставление более подробной информации было бы полезно.
Чарли Фиш

Что здесь объяснить? Все в комментариях
Endorphinex

-3

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

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

-5

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

Например,

perc = double.Parse("3.555)*1000;
result = perc/1000
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.