Ondalık virgül içeren bir dizeyi ikiye nasıl ayrıştırabilirim?


231

Ben bir dize gibi bir dize ayrıştırmak istiyorum "3.5". Ancak,

double.Parse("3.5") 

verim 35 ve

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

atar FormatException.

Bilgisayarımın yerel ayarı, ondalık ayırıcı olarak virgül kullanılan Almanca olarak ayarlanmıştır. Bununla ilgili bir şey yapmak ve girdi olarak double.Parse()beklemek gerekebilir "3,5", ama emin değilim.

Geçerli yerel ayarımda belirtildiği gibi biçimlendirilebilen veya biçimlendirilemeyen ondalık sayı içeren bir dizeyi nasıl ayrıştırabilirim?


Ondalık virgül kesinlikle çıktıyı etkileyecektir.
ChrisF

12
Durumunuza uygunsa double.TryParse () yöntemini unutmayın.
Kyle Gagnet

Yanıtlar:


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

XmlConvertSınıfı kullanmayı seviyorum ... bunun kullanımdan daha iyi, daha kötü ve / veya farklı olup olmadığı hakkında bir fikrin var mı CultureInfo.InvariantCulture?
ChrisW

1
Aslında, XmlConvertkoddaki tek bir çift değeri ayrıştırmak için kullanılması amaçlanmamıştır. Kullanmayı tercih ediyorum double.Parseya da Convert.ToDoublebu niyetimi belli ediyor.
Mehrdad Afshari

4
Bu doulble.Parse ondalık noktası olarak nokta içermeyen varsayılan kültür kullanır ??
Ahmed Said

3
12345678.12345678 dönüştürülürse, çok da dönüştürülür 12345678.123457
PUG

4
benim için çalışmadı: virgül atlar ve çift olarak döner ve int
fnc12 08

75

Çoğunlukla kullanıcı girişini ayrıştırmak için çok kültürlü bir işlev kullanırım, çünkü çoğunlukla sayısal tuş takımına alışır ve ondalık ayırıcı olarak virgül kullanan bir kültür kullanıyorsa, o kişi virgül yerine sayısal tuş takımının noktasını kullanır.

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

Yine de, @nikie yorumları doğrudur. Savunmam için bu işlevi, kültürün en-US, en-CA veya fr-CA olabileceğini bildiğim kontrollü bir ortamda kullanıyorum. Bu işlevi kullanıyorum çünkü Fransızca'da virgül ondalık ayırıcı olarak kullanıyoruz, ancak finansta çalışan herkes sayısal tuş takımında ondalık ayırıcıyı her zaman kullanacak, ancak bu bir nokta, virgül değil. Fr-CA kültüründe bile, ondalık ayırıcı olarak bir noktaya sahip sayıları ayrıştırmam gerekiyor.


18
Bunun iyi bir fikir olduğundan emin değilim. Kültürü bilmiyorsanız güvenilir bir şekilde çiftleri ayrıştıramazsınız: Almanya'da çift değerler '.' İçerebilir, ancak orada binlerce ayırıcı olarak kabul edilir. Bu nedenle Legate'in durumunda, GetDouble ("3.5") 35 yerel ayarda 35 ve en-us ortamında 3,5 döndürür.
Niki

Hayır, Pierre Alain haklı, aynen yazıldığı gibi. Kültürünüz "nokta bin" ayırıcısı diyorsa, "3.5" "35" olarak görülür ve iyidir. Ancak "nokta" için herhangi bir kural olarak kültür yapıyorsanız, karakter ondalık nokta olarak ayrıştırılır ve aynı zamanda çalışır. EnUS kültürünü denemekten kaçınırdım, ama bu kişisel bir seçim.
xryl669

Virgül ile kültürde numpad kullanırsanız, nokta tuşu virgül olarak ayarlanır.
CrazyBaran

Numpad ondalık ayırıcısı klavye düzenine (bölgesel ayarlara değil - en azından Windows 7'ye) bağlıdır (kod yazmak için Macarca, metin yazmak için Macarca kullanıyorum ... Ayrıca ondalık ayırıcıyı ',' yerine 'olarak değiştirdiğim özelleştirilmiş bölge ayarlarını da kullanıyorum ve ayırıcıyı'; '' den ',' ye biliyorsunuz. -kültür uygulamaları;)
Steven Spark

25

Yorum yazamadım, bu yüzden buraya yazıyorum:

double.Parse ("3.5", CultureInfo.InvariantCulture) iyi bir fikir değildir, çünkü Kanada'da 3.5 yerine 3,5 yazıyoruz ve bu işlev bize 35 sonuç veriyor.

Her ikisini de bilgisayarımda test ettim:

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

Pierre-Alain Vigeant'in bahsettiği doğru yol budur

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: "... çünkü Kanada'da 3,5 yerine 3,5 yazıyoruz" Bundan emin misiniz? Ondalık işaretine göre : "Nokta" olan ülkeler. "Ondalık işareti olarak kullanılır ... Kanada (İngilizce kullanırken)" . Daha çok Windows'un Fransızca sürümünü kullanmakla ilgili değil mi?
Peter Mortensen

Fransız versiyonu nedeniyle Baybe. Montreal'de 3,5 değil 3,5 yazıyoruz
Malus Jan

Yani mantığınıza göre, her zaman sadece 1 tanesi doğru mu döner?
batmaci

Bak o
Malus Oca

Hala bir hata var. GetDouble gibi giriş dizesi için ("10 ,,,,,,,, 0", 0,0). Bahsedilen işlev 100
değerini

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

Ayrıştırmadan önce virgül ile bir nokta değiştirin. Ondalık ayırıcı olarak virgül bulunan ülkelerde kullanışlıdır. Kullanıcı girişini (gerekirse) bir virgül veya nokta ile sınırlamayı düşünün.


1
+133 oyu olandan çok daha doğru cevap ... Her iki sistemde de "," veya "." ondalık ayırıcı ...
Badiboy

@Badiboy bu cevapta neyin yanlış olduğunu açıklayabilir misiniz? Anladığım kadarıyla InvariantCulture her zaman '.' ondalık ayırıcı olarak. Bu yüzden her iki sistem için de çalışmalıdır.
Alex P.

@ Alex11223 Haklısın. Bu yüzden bu cevabın daha popüler olandan daha iyi olduğunu söyledim. PS: Dostça konuşma Bu kod LIST SEPARATOR (yani 1,234,560.01) olarak "," varsa da başarısız olacaktır, ama bunu nasıl çözeceğini bilmiyorum. :)
Badiboy

4
Bu iyi bir cevap değildir, çünkü bazı kültürlerde, binlerce ayırıcıdır ve kullanılabilir. Bir noktaya değiştirirseniz, birkaç noktaya sahip olursunuz ve ayrıştırma başarısız olur: Double.Parse ((12345.67) .ToString ("N", yeni CultureInfo ("en")). Değiştirin (',', '. '), CultureInfo.InvariantCulture) çünkü (12345.67) .ToString ("N", yeni CultureInfo ("en")). Değiştirin (', ','. ') "12.345.67" olarak biçimlendirilecek
kodlama tarihi

1,234,56 -> 1,234,56 ayrıştırıcı değil. başka bir fikir, sayının '.' içerip içermediğini kontrol etmektir. ve ',' ve ',' yerine boş bir dize ekleyin ve yalnızca ',' sunulan virgül ile değiştirin '.'
GDocal

16

Hile, tüm kültürlerde noktayı ayrıştırmak için değişmez kültür kullanmaktır.

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

11

Bakın, yukarıdaki sabit bir dize ile bir dize değiştirme yazmayı öneren her yanıt sadece yanlış olabilir. Neden? Çünkü Windows'un bölge ayarlarına saygı duymuyorsunuz! Windows, kullanıcının istediği ayırıcı karakteri ayarlama özgürlüğüne sahip olmasını sağlar. Kontrol panelini açabilir, bölge paneline girebilir, gelişmiş seçeneğini tıklayabilir ve karakteri istediğiniz zaman değiştirebilir. Program çalışırken bile. Bunu bir düşün. İyi bir çözüm bunun farkında olmalıdır.

Bu yüzden, önce kendinize, bu numaranın nereden geldiğini, ayrıştırmak istediğinizi sormak zorunda kalacaksınız. .NET Framework girdisinden geliyorsa, aynı biçimde olacağı için sorun yok. Ama belki dışarıdan, belki harici bir sunucudan, belki de sadece string özelliklerini destekleyen eski bir DB'den geliyordu. Orada, db admin sayıların saklanacağı formatta bir kural vermiş olmalıdır. Örneğin, bunun ABD biçiminde bir US DB olacağını biliyorsanız, bu kod parçasını kullanabilirsiniz:

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

Bu dünyanın herhangi bir yerinde iyi çalışacaktır. Ve lütfen 'Convert.ToXxxx' kullanmayın. 'Convert' sınıfı yalnızca herhangi bir yöndeki dönüşümler için bir temel olarak düşünülür. Ayrıca: Benzer mekanizmayı DateTimes için de kullanabilirsiniz.


Kabul! Kültür özelliklerini manuel olarak uygulamaya çalışmak, sonunda beklemediğiniz bir duruma ve büyük bir baş ağrısına neden olacaktır. NET düzgün işlemesine izin verin.
Khalos

2
büyük bir sorun, kullanıcılar kültürel ayarları için ondalık ayırıcı olarak kabul edilmeyen bir ondalık ayırıcı kullandıklarında
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

Bu konudaki iki sentim, genel bir çift dönüşüm yöntemi sağlamaya çalışıyor:

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

Beklendiği gibi çalışır:

  • 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

Hiçbir varsayılan dönüşüm ayrıştırması çalışırken başarısız olur, böylece uygulanan 1.3,14, 1,3.14ya da benzer durumlar.


1
Bin olarak tasarlanan "1.000" başarısız olacaktır.
Defkon1

1

Aşağıdaki kod, herhangi bir senaryoda işi yapar. Biraz ayrıştırma.

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

Ben denemedim, ama uygulamayı farklı kültür
SOs yürütürseniz

1

Değer bir kullanıcı girişinden geliyorsa,% 100 doğru dönüşüm mümkün olmadığını düşünüyorum. örneğin, değer 123.456 ise, bir gruplama olabilir veya ondalık bir nokta olabilir. Gerçekten% 100'e ihtiyacınız varsa, biçiminizi tanımlamanız ve doğru değilse bir istisna atmanız gerekir.

Ama JanW kodunu geliştirdim, bu yüzden% 100'e biraz daha yaklaştık. Arkadaki fikir, eğer son ayırıcı bir groupSeperator ise, bu bir çiftten daha tamsayı tipi olacaktır.

İlave kod ilk olduğu takdirde arasında 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

Tüm ayrıştırmalarda bir yerel ayar belirtmek zorunda kalmadan, uygulama genelinde yerel ayar yapmayı tercih ederim, ancak dize biçimleri uygulamada tutarlı değilse, bu çalışmayabilir.

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

Bunu uygulamanızın başlangıcında tanımlamak, tüm çift ayrıştırmaların ondalık ayırıcı olarak virgül beklemesini sağlayacaktır. Ondalık ve binlik ayırıcının ayrıştırdığınız dizelere uyması için uygun bir yerel ayar ayarlayabilirsiniz.


0

Hangi ondalık ayırıcıyı arayacağınızı belirtmeden zor, ancak yaparsanız, kullandığım şey budur:

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

Bu herhangi bir kültürle çalışmalıdır. Takas yerine değiştirilen uygulamaların aksine, birden fazla ondalık ayırıcıya sahip dizeleri ayrıştırma işlemi başarısız olur.


0

@JanW kodunu da geliştirdim ...

Tıbbi cihazlardan sonuçları biçimlendirmek için buna ihtiyacım var ve ayrıca "> 1000", "23.3e02", "350E-02" ve "NEGATIVE" gönderiyorlar.

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

Bence en iyi cevap bu:

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

Kodunuzu açıklamak ve daha fazla ayrıntı sağlamak yardımcı olacaktır.
Charlie Fish

Burada ne açıklamalı? Her şey yorumlarda
Endorphinex

-3

Aşağıdakiler daha az verimlidir, ancak bu mantığı kullanıyorum. Bu yalnızca ondalık noktadan sonra iki basamağınız varsa geçerlidir.

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

Sayıyı çarpın ve sonra daha önce çarptığınız sayıya bölün.

Örneğin,

perc = double.Parse("3.555)*1000;
result = perc/1000
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.