Jak parsować ciąg z przecinkiem dziesiętnym do podwójnego?


231

Chcę przeanalizować ciąg podobny "3.5"do podwójnego. Jednak,

double.Parse("3.5") 

daje 35 i

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

rzuca a FormatException.

Teraz ustawienia regionalne mojego komputera są ustawione na niemiecki, gdzie przecinek jest używany jako separator dziesiętny. Być może trzeba coś z tym zrobić i double.Parse()oczekiwać, że "3,5"będzie to wkład, ale nie jestem pewien.

Jak parsować ciąg zawierający liczbę dziesiętną, która może być sformatowana zgodnie z moimi bieżącymi ustawieniami regionalnymi?


Przecinek dziesiętny z pewnością wpłynie na wynik.
ChrisF

12
Nie zapomnij o metodzie double.TryParse (), jeśli jest odpowiednia w twojej sytuacji.
Kyle Gagnet

Odpowiedzi:


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

Lubię korzystać z XmlConvertklasy ... czy masz jakieś pomysły, czy jest to lepsze, gorsze i / lub inne niż używanie CultureInfo.InvariantCulture?
ChrisW,

1
Cóż, XmlConverttak naprawdę nie jest przeznaczony do analizowania pojedynczej podwójnej wartości w kodzie. Wolę używać double.Parselub Convert.ToDoubleto czyni moją intencję oczywistą.
Mehrdad Afshari,

4
Oznacza to, że doulble.Parse używa domyślnej kultury, która nie może zawierać kropki jako miejsca dziesiętnego?
Ahmed powiedział

3
jeśli konwertujesz 12345678.12345678, to również konwertuje 12345678.123457
PUG

4
nie działało dla mnie: pomija przecinek i zwraca i int jako podwójny
fnc12

75

Zwykle używam funkcji wielokulturowej do analizowania danych wprowadzanych przez użytkownika, głównie dlatego, że jeśli ktoś jest przyzwyczajony do klawiatury numerycznej i używa kultury, która używa przecinka jako separatora dziesiętnego, osoba ta użyje punktu klawiatury numerycznej zamiast przecinka.

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

Uważaj jednak, komentarze @nikie są prawdziwe. W mojej obronie używam tej funkcji w kontrolowanym środowisku, w którym wiem, że kultura może być en-US, en-CA lub fr-CA. Używam tej funkcji, ponieważ w języku francuskim używamy przecinka jako separatora dziesiętnego, ale każdy, kto kiedykolwiek pracował w finansach, zawsze użyje separatora dziesiętnego na klawiaturze numerycznej, ale jest to kropka, a nie przecinek. Tak więc nawet w kulturze fr-CA muszę przeanalizować liczbę, która będzie miała kropkę jako separator dziesiętny.


18
Nie jestem pewien, czy to dobry pomysł. Nie możesz niezawodnie parsować podwójnego, jeśli nie znasz kultury: w Niemczech podwójne wartości mogą zawierać „.”, Ale są tam uważane za separatory tysięcy. Tak więc w przypadku Legate GetDouble („3.5”) zwróci 35 w niemieckich ustawieniach regionalnych i 3.5 w środowisku en-us.
Niki

Nie, Pierre Alain ma rację, ponieważ jest dokładnie tak, jak napisano. Jeśli Twoja kultura mówi, że separator „kropka to tysiąc”, wówczas „3,5” jest postrzegane jako „35” i jest dobre. Jeśli jednak kultywujesz jako brak reguł dla „kropki”, to znak jest analizowany jako przecinek dziesiętny, i również działa. Unikałbym próbowania kultury enUS, ale jest to osobisty wybór.
xryl669

Jeśli używasz numpada w kulturze z przecinkiem, klawisz kropki zostanie ustawiony jako przecinek.
CrazyBaran

Separator dziesiętny numpad zależy od układu klawiatury (a nie ustawień regionalnych - przynajmniej w Windows 7) (używam węgierskiego do pisania tekstu, e-maili ... i en-US do pisania kodu, więc może to być kropka lub przecinek. Korzystam również z niestandardowych ustawień regionalnych, w których zmieniłem separator dziesiętny z „,” na „.” i listę separatora z „;” na „,”. Wiesz, bo csv ... Powodzenia wszystkim, którzy piszą wiele -kultury;)
Steven Spark

25

Nie mogłem napisać komentarza, więc piszę tutaj:

double.Parse („3.5”, CultureInfo.InvariantCulture) nie jest dobrym pomysłem, ponieważ w Kanadzie piszemy 3,5 zamiast 3,5, a ta funkcja daje nam w rezultacie 35.

Przetestowałem oba na moim komputerze:

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

Jest to poprawny sposób, o którym wspomniał 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;
}

1
Re: „... ponieważ w Kanadzie piszemy 3,5 zamiast 3,5” Czy jesteś tego pewien? Zgodnie ze znakiem dziesiętnym : „Kraje, w których kropka” . Czy nie chodzi o korzystanie z francuskiej wersji systemu Windows?
Peter Mortensen

Baybe z powodu francuskiej wersji. W Montrealu piszemy 3,5, a nie 3,5
Malus,

Więc zgodnie z twoją logiką, zawsze tylko 1 z nich zwraca prawdę?
batmaci,

Spójrz na to
Malus Jan

Nadal występuje błąd. Dla ciągu wejściowego takiego jak GetDouble („10 ,,,,,,,, 0”, 0,0). Wspomniana funkcja zwraca 100.
Krivers

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

Przed analizą zamień przecinek na punkt. Przydatny w krajach z przecinkiem jako separatorem dziesiętnym. Pomyśl o ograniczeniu wprowadzania danych przez użytkownika (jeśli to konieczne) do jednego przecinka lub punktu.


1
Znacznie bardziej poprawna odpowiedź niż ta, która ma +133 głosów ... Pozwala żyć w obu systemach z „,” lub „.” separator dziesiętny ...
Badiboy

@Badiboy, czy możesz wyjaśnić, co jest nie tak z tą odpowiedzią? Jak rozumiem, InvariantCulture zawsze ma „.” jako separator dziesiętny. Powinien więc działać dla obu systemów.
Alex P.

@ Alex11223 Masz rację. Dlatego powiedziałem, że ta odpowiedź jest lepsza niż bardziej popularna. PS: Przyjazne mówienie tego kodu również się nie powiedzie, jeśli masz „,” jako LIST SEPARATOR (tj. 1 234 560,01), ale nie wiem, jak to rozwiązać. :)
Badiboy

4
To nie jest dobra odpowiedź, ponieważ w niektórych informacjach o kulturze separator tysięcy jest możliwy do użycia. Jeśli zamienisz go na kropkę, wtedy będziesz mieć kilka kropek i parsowanie się nie powiedzie: Double.Parse ((12345.67) .ToString („N”, nowa CultureInfo („en”)). Zamień („,”, „). '), CultureInfo.InvariantCulture), ponieważ (12345.67) .ToString („N”, nowe CultureInfo („en”))). Zamień („,”, „.”) Zostanie sformatowany jako „12.345.67”
kodowaniedave

1,234,56 -> 1,234,56 nie analizator składni. innym pomysłem jest sprawdzenie, czy liczba zawiera „.” i „,” i zamień ”,„ z pustym ciągiem, a jeśli tylko ”,„ przecinek przedstawiony zastąp go ”.
GDocal

16

Sztuką jest użycie niezmiennej kultury, aby przeanalizować kropkę we wszystkich kulturach.

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

11

Posłuchaj, każda powyższa odpowiedź, która proponuje napisanie zamiany ciągu na ciąg stały, może być tylko błędna. Czemu? Ponieważ nie przestrzegasz ustawień regionalnych systemu Windows! Windows zapewnia użytkownikowi swobodę ustawiania dowolnych znaków separatora. S / On może otworzyć panel sterowania, przejść do panelu regionu, kliknąć zaawansowane i zmienić postać w dowolnym momencie. Nawet podczas uruchamiania programu. Pomyśl o tym. Dobre rozwiązanie musi być tego świadome.

Najpierw musisz zadać sobie pytanie, skąd pochodzi ta liczba, którą chcesz przeanalizować. Jeśli pochodzi z danych wejściowych w .NET Framework, nie ma problemu, ponieważ będzie miał ten sam format. Ale może pochodziło to z zewnątrz, może z zewnętrznego serwera, może ze starej bazy danych, która obsługuje tylko właściwości ciągu. Tam administrator db powinien był podać regułę, w jakiej formacie mają być przechowywane liczby. Jeśli wiesz na przykład, że będzie to US DB w formacie US, możesz użyć tego fragmentu kodu:

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

Będzie to dobrze działać w dowolnym miejscu na świecie. I proszę nie używać „Convert.ToXxxx”. Klasa „Konwertuj” jest uważana jedynie za bazę do konwersji w dowolnym kierunku. Poza tym: Możesz użyć podobnego mechanizmu także dla DateTimes.


Zgoda! Próba ręcznego wdrożenia funkcji Kultura ostatecznie doprowadzi do nieoczekiwanego przypadku i dużego bólu głowy. Pozwól .NET poprawnie go obsłużyć.
Khalos

2
dużym problemem jest to, że użytkownicy używają separatora dziesiętnego, który nie jest uważany za separator dziesiętny dla jego ustawień kulturowych
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

Moje dwa centy na ten temat, próbując zapewnić ogólną metodę podwójnej konwersji:

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

Działa zgodnie z oczekiwaniami z:

  • 1.1
  • 1,1
  • 1 000 000 000
  • 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

Nie domyślna konwersja jest realizowany, więc to nie starają się analizować 1.3,14, 1,3.14czy podobne przypadki.


1
„1000” oznaczonych jako tysiąc upadnie.
Defkon1,

1

Poniższy kod wykonuje zadanie w dowolnym scenariuszu. To trochę parsowanie.

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 zwróci 1234567.890
Dan Vogel

Nie próbowałem, ale jeśli uruchomisz aplikację w różnych SO kulturowych, ten kod nie
dałby rady

1

Myślę, że 100% poprawna konwersja nie jest możliwa, jeśli wartość pochodzi z danych wejściowych użytkownika. np. jeśli wartość wynosi 123.456, może to być grupa lub kropka dziesiętna. Jeśli naprawdę potrzebujesz 100%, musisz opisać swój format i zgłosić wyjątek, jeśli nie jest poprawny.

Ale poprawiłem kod JanW, więc uzyskujemy trochę więcej do 100%. Chodzi o to, że jeśli ostatnim separatorem jest groupSeperator, byłby to raczej typ liczb całkowitych niż podwójny.

Dodawany kod znajduje się na pierwszym czy z GetDouble odwołują .

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

Zamiast określać ustawienia regionalne we wszystkich parsach, wolę ustawić ustawienia regionalne dla całej aplikacji, chociaż jeśli formaty ciągów nie są spójne w aplikacji, może to nie działać.

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

Zdefiniowanie tego na początku aplikacji sprawi, że wszystkie podwójne analizy będą oczekiwać przecinka jako separatora dziesiętnego. Możesz ustawić odpowiednie ustawienia narodowe, aby separator dziesiętny i tysiące pasowały do ​​analizowanych ciągów.


0

Jest to trudne bez określenia, jakiego separatora dziesiętnego należy szukać, ale jeśli tak, używam tego:

    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", ',');
    }

To powinno działać z każdą kulturą. Prawidłowo nie analizuje ciągów znaków, które mają więcej niż jeden separator dziesiętny, w przeciwieństwie do implementacji, które zastępują zamiast zamiany.


0

Poprawiłem również kod @JanW ...

Potrzebuję go do formatowania wyników z instrumentów medycznych, a także wysyłają „> 1000”, „23.3e02”, „350E-02” i „NEGATYWNE”.

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

Myślę, że to najlepsza odpowiedź:

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

Pomocne byłoby wyjaśnienie kodu i podanie bardziej szczegółowych informacji.
Charlie Fish,

Co tu wyjaśnić? Wszystko jest w komentarzach
Endorphinex

-3

Poniżej jest mniej wydajna, ale używam tej logiki. Jest to ważne tylko wtedy, gdy masz dwie cyfry po przecinku.

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

Pomnóż liczbę, a następnie podziel ją przez liczbę pomnożoną wcześniej.

Na przykład,

perc = double.Parse("3.555)*1000;
result = perc/1000
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.