Як розібрати рядок з десятковою крапкою до подвійного?


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 як подвійне
fnc12

75

Я зазвичай використовую мультикультурну функцію для розбору введення користувача, здебільшого тому, що якщо хтось звик до нумерації та використовує культуру, яка використовує кому в якості десяткового роздільника, ця людина використовуватиме точку numpad замість коми.

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. Я використовую цю функцію, тому що французькою мовою ми використовуємо коску як десятковий роздільник, але кожен, хто коли-небудь працював у фінансах, завжди буде використовувати десятковий роздільник на numpad, але це точка, а не кома. Тож навіть у культурі fr-CA мені потрібно проаналізувати число, яке матиме крапку як десятковий роздільник.


18
Я не впевнений, що це гарна ідея. Ви не можете надійно розібрати дубль, якщо не знаєте культури: У Німеччині подвійні значення можуть містити ".", Але там вони вважаються тисячами-роздільниками. Так у випадку Legate GetDouble ("3,5") повертає 35 у німецькій мові та 3,5 у середовищі en-us.
Нікі

Ні, П’єр Ален правий, адже це точно так, як написано. Якщо у вашій культурі є роздільник "крапка - тисяча", то "3,5" сприймається як "35", і це добре. Однак якщо ви культуєте як відсутні правила для "крапки", то символ розбирається як десяткова крапка, і він також працює. Я б уникнув спробувати культуру enUS, але це особистий вибір.
xryl669

Якщо ви використовуєте numpad в культурі з комою, точковою клавішею буде встановлена ​​кома.
CrazyBaran

Десятковий розділювач числових цифр залежить від розкладки клавіатури (а не від регіональних налаштувань - принаймні для 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 з них повертає правду?
batmaci

Подивіться на це
Малус

Помилка є. Для вхідного рядка типу GetDouble ("10 ,,,,,,,, 0", 0.0). Згадана функція повертається 100.
Криверс

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

Замініть кому крапкою перед розбором. Корисний у країнах, що мають десятковий роздільник. Подумайте про обмеження введення користувачем (якщо необхідно) однією комою чи точкою.


1
Набагато правильніша відповідь, ніж та, яка має +133 голоси ... Це дозволяє жити в обох системах з "," або "." десятковий роздільник ...
Бадібой

@Badiboy Ви можете пояснити, що не так у цій відповіді? Як я розумію, InvariantCulture завжди має ". як десятковий роздільник. Отже, це має працювати для обох систем.
Алекс П.

@ Alex11223 Ви праві. Тому я сказав, що ця відповідь краще, ніж популярніша. PS: Дружньо кажучи цей код також не вдасться, якщо у вас є "", як СПИСОК СЕПАРАТОР (тобто 1,234,560.01), але я не знаю, як це взагалі вирішити. :)
Бадібой

4
Це не є гарною відповіддю, оскільки в деяких культурах відомо, що є роздільником тисяч, і його можна використовувати. Якщо ви заміните його на крапку, тоді у вас з’явиться кілька крапок і розбір не вдасться: Double.Parse ((12345.67) .ToString ("N", новий CultureInfo ("en")). Замініть (',', '. '), CultureInfo.InvariantCulture), тому що (12345.67) .ToString ("N", новий CultureInfo ("en")). Замінити (', ','. ') Буде відформатовано як "12.345.67"
кодування до

1,234,56 -> 1,234,56 не розбирає. інша ідея - перевірити, чи число містить "." і ',' і замінити ',' з порожнім рядком і якщо тільки ',' представлена ​​кома замінить його на '.'
GDocal

16

Хитрість полягає у використанні інваріантної культури, для розбору крапок у всіх культурах.

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

11

Подивіться, кожна відповідь, що пропонує писати заміну рядка на постійний рядок, може бути лише помилковою. Чому? Тому що ви не дотримуєтесь регіональних налаштувань Windows! Windows запевняє, що користувач може встановити будь-який символ, який він розділяє. S / Він може відкрити панель керування, зайти в панель регіону, натиснути на розширений та змінити символ у будь-який час. Навіть під час запуску програми. Подумайте про це. Хорошого рішення треба знати про це.

Отже, спочатку вам доведеться запитати себе, звідки походить це число, що ви хочете проаналізувати. Якщо він надходить із введення в .NET Framework, немає проблем, оскільки він буде в тому ж форматі. Але, можливо, воно надходило ззовні, можливо, із зовнішнього сервера, можливо зі старого БД, який підтримує лише рядкові властивості. Там адміністратор db повинен був дати правило, у якому форматі повинні зберігатися номери. Якщо ви знаєте, наприклад, що це буде БД США у форматі США, ви можете використовувати цей фрагмент коду:

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
  • 1 000 000 000 000
  • 1.000.000.000
  • 1 000 000 000,99
  • 1.000.000.000,99
  • 5000,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
Dan Vogel

Я не пробував, але якщо ви запускаєте додаток в інших культурах SO, цей код не спричинить хитрість, я думаю: /
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" та "NEGATIVE".

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.