Bagaimana cara mengurai string dengan titik desimal menjadi ganda?


231

Saya ingin mengurai string seperti "3.5"menjadi ganda. Namun,

double.Parse("3.5") 

hasil 35 dan

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

melempar a FormatException.

Sekarang komputer saya disetel ke bahasa Jerman, di mana koma digunakan sebagai pemisah desimal. Mungkin harus melakukan sesuatu dengan itu dan double.Parse()berharap "3,5"sebagai masukan, tapi saya tidak yakin.

Bagaimana cara mengurai string yang berisi angka desimal yang mungkin atau mungkin tidak diformat seperti yang ditentukan dalam lokal saya saat ini?


Koma desimal pasti akan mempengaruhi output.
ChrisF

12
Jangan lupa tentang metode double.TryParse (), jika itu sesuai untuk situasi Anda.
Kyle Gagnet

Jawaban:


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

Saya suka menggunakan XmlConvertkelas ... apakah Anda punya ide apakah ini lebih baik, lebih buruk, dan / atau berbeda daripada menggunakan CultureInfo.InvariantCulture?
ChrisW

1
Yah, XmlConvertsebenarnya tidak dimaksudkan untuk digunakan untuk mengurai nilai ganda tunggal dalam kode. Saya lebih suka menggunakan double.Parseatau Convert.ToDoubleyang membuat niat saya jelas.
Mehrdad Afshari

4
Ini berarti doulble.Parse menggunakan budaya default yang mungkin tidak mengandung titik sebagai titik desimal ??
Ahmed Said

3
jika mengkonversi 12345678.12345678, itu juga mengubah 12345678.123457
PUG

4
tidak bekerja untuk saya: melompati koma dan kembali dan int sebagai dobel
fnc12

75

Saya biasanya menggunakan fungsi multi-budaya untuk mem-parsing input pengguna, terutama karena jika seseorang terbiasa dengan numpad dan menggunakan budaya yang menggunakan koma sebagai pemisah desimal, orang itu akan menggunakan titik numpad bukan koma.

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

Namun waspadalah, komentar @nikie benar. Untuk pertahanan saya, saya menggunakan fungsi ini di lingkungan yang terkendali di mana saya tahu bahwa budaya dapat berupa en-US, en-CA atau fr-CA. Saya menggunakan fungsi ini karena dalam bahasa Prancis, kami menggunakan koma sebagai pemisah desimal, tetapi siapa pun yang pernah bekerja di bidang keuangan akan selalu menggunakan pemisah desimal pada numpad, tetapi ini adalah poin, bukan koma. Jadi, bahkan dalam budaya fr-CA, saya perlu menguraikan angka yang akan memiliki titik sebagai pemisah desimal.


18
Saya tidak yakin itu ide yang bagus. Anda tidak dapat menguraikan ganda secara andal jika Anda tidak tahu budaya: Di Jerman, nilai ganda mungkin mengandung '., Tetapi mereka dianggap sebagai ribuan pemisah di sana. Jadi dalam kasus Legate, GetDouble ("3.5") akan mengembalikan 35 di lokal Jerman dan 3.5 di lingkungan en-us.
Niki

Tidak, Pierre Alain benar, karena persis seperti yang tertulis. Jika budaya Anda mengatakan "titik adalah seribu" pemisah, maka "3,5" dipandang sebagai "35" dan itu bagus. Namun, jika Anda membuat budaya sebagai tidak ada aturan untuk "titik", maka karakter diuraikan sebagai titik desimal, dan itu juga berfungsi. Saya akan menghindari mencoba budaya ENUS, tetapi itu adalah pilihan pribadi.
xryl669

Jika Anda menggunakan numpad dalam budaya dengan koma, tombol titik akan ditetapkan koma.
CrazyBaran

Pemisah desimal numpad tergantung pada tata letak keyboard (dan bukan pengaturan regional - setidaknya pada Windows 7) (saya menggunakan bahasa Hongaria untuk menulis teks, email ... dan en-US untuk menulis kode, sehingga bisa berupa titik atau koma. Saya juga menggunakan pengaturan regional khusus di mana saya mengubah pemisah desimal dari ',' ke '.' dan daftar pemisah dari ';' ke ','. Anda tahu, menyebabkan csv ... Semoga beruntung bagi kita semua menulis multi -culture apps;)
Steven Spark

25

Saya tidak bisa menulis komentar, jadi saya menulis di sini:

double.Parse ("3.5", CultureInfo.InvariantCulture) bukan ide yang baik, karena di Kanada kita menulis 3,5 bukan 3,5 dan hasilnya fungsi ini memberi kita 35 hasilnya.

Saya menguji keduanya di komputer saya:

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

Ini adalah cara yang benar yang disebutkan 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: "... karena di Kanada kami menulis 3,5 bukannya 3,5" Apakah Anda yakin tentang itu? Menurut tanda Desimal : "Negara tempat tanda". "Digunakan sebagai tanda desimal termasuk ... Kanada (saat menggunakan bahasa Inggris)" . Bukankah ini lebih banyak tentang menggunakan Windows versi Perancis?
Peter Mortensen

Baybe karena versi Perancis. Dalam montreal kami menulis 3,5 bukan 3,5
Malus

Jadi menurut logika Anda, selalu hanya 1 yang benar?
batmaci

Lihat itu
Malus

Masih ada kesalahan. Untuk string input seperti GetDouble ("10 ,,,,,,,, 0", 0,0). Fungsi yang disebutkan mengembalikan 100.
Krivers

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

Ganti koma dengan titik sebelum parsing. Berguna di negara-negara dengan koma sebagai pemisah desimal. Pikirkan tentang membatasi input pengguna (jika perlu) ke satu koma atau titik.


1
Jawaban yang jauh lebih tepat daripada yang memiliki +133 suara ... Memungkinkan untuk hidup di kedua sistem dengan "," atau "." pemisah desimal ...
Badiboy

@Badiboy dapat Anda jelaskan apa yang salah dengan jawaban ini? Seperti yang saya pahami InvariantCulture selalu memiliki '.' sebagai pemisah desimal. Jadi itu harus bekerja untuk kedua sistem.
Alex P.

@ Alex11223 Anda benar. Itu sebabnya saya mengatakan bahwa jawaban ini lebih baik daripada yang populer. NB: Berbicara dengan ramah kode ini juga akan gagal jika Anda memiliki "," seperti LIST SEPARATOR (yaitu 1.234.560,01), tetapi saya tidak tahu bagaimana menyelesaikannya sama sekali. :)
Badiboy

4
Ini bukan jawaban yang baik karena dalam beberapa kultur info, adalah pemisah ribuan dan dapat digunakan. Jika Anda menggantinya ke titik maka Anda akhirnya memiliki beberapa titik dan parse akan gagal: Double.Parse ((12345.67) .ToString ("N", CultureInfo ("en") baru). Ganti (',', '. '), CultureInfo.InvariantCulture) karena (12345.67) .ToString ("N", CultureInfo baru ("en")). Ganti (', ','. ') Akan diformat sebagai "12.345.67"
codingdave

1,234.56 -> 1.234.56 tidak parser. ide lain adalah untuk memeriksa apakah nomor mengandung '.' dan ',' dan ganti ',' dengan string kosong dan jika hanya ',' koma disajikan ganti menjadi '.'
GDocal

16

Caranya adalah dengan menggunakan budaya invarian, untuk mengurai titik di semua budaya.

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

11

Lihat, setiap jawaban di atas yang mengusulkan penulisan penggantian string dengan string konstan hanya bisa salah. Mengapa? Karena Anda tidak menghormati pengaturan wilayah Windows! Windows meyakinkan pengguna untuk memiliki kebebasan untuk mengatur karakter pemisah apa pun yang diinginkannya. Ia dapat membuka panel kontrol, masuk ke panel wilayah, klik maju dan ubah karakter kapan saja. Bahkan selama program Anda berjalan. Pikirkan ini. Solusi yang baik harus menyadari hal ini.

Jadi, pertama Anda harus bertanya pada diri sendiri, dari mana nomor ini berasal, bahwa Anda ingin menguraikan. Jika itu berasal dari input dalam .NET Framework tidak ada masalah, karena itu akan berada dalam format yang sama. Tapi mungkin itu datang dari luar, mungkin dari server eksternal, mungkin dari DB lama yang hanya mendukung properti string. Di sana, admin db seharusnya memberikan aturan di mana format angka yang akan disimpan. Jika Anda tahu misalnya bahwa itu akan menjadi DB AS dengan format AS, Anda dapat menggunakan kode ini:

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

Ini akan bekerja dengan baik di mana saja di dunia. Dan tolong jangan gunakan 'Convert.ToXxxx'. Kelas 'Konversi' dianggap hanya sebagai basis untuk konversi ke segala arah. Selain itu: Anda juga dapat menggunakan mekanisme serupa untuk DateTimes.


Sepakat! Mencoba menerapkan fitur Budaya secara manual pada akhirnya akan menghasilkan kasus yang tidak Anda harapkan dan sakit kepala besar. Biarkan .NET menanganinya dengan benar.
Khalos

2
masalah besar adalah ketika pengguna menggunakan pemisah desimal yang tidak dianggap sebagai pemisah desimal untuk pengaturan budayanya
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

Dua sen saya pada topik ini, mencoba memberikan metode konversi ganda umum:

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

Bekerja seperti yang diharapkan dengan:

  • 1.1
  • 1,1
  • 1.000.000.000
  • 1.000.000.000
  • 1.000.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

Tidak ada konversi default yang diterapkan, sehingga gagal mencoba menguraikan 1.3,14, 1,3.14atau kasus serupa.


1
"1.000" yang dimaksudkan sebagai seribu akan gagal.
Defkon1

1

Kode berikut melakukan pekerjaan dalam skenario apa pun. Ini sedikit parsing.

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 akan mengembalikan 1234567.890
Dan Vogel

Saya belum mencoba, tetapi jika Anda menjalankan aplikasi dalam SO budaya yang berbeda, kode ini tidak akan berhasil, saya pikir: /
Dani bISHOP

1

Saya pikir konversi 100% benar tidak mungkin, jika nilainya berasal dari input pengguna. misalnya jika nilainya 123.456, bisa berupa pengelompokan atau bisa menjadi titik desimal. Jika Anda benar-benar membutuhkan 100%, Anda harus menggambarkan format dan melemparkan pengecualian jika itu tidak benar.

Tapi saya memperbaiki kode JanW, jadi kami mendapat sedikit lebih banyak ke depan ke 100%. Gagasan di baliknya adalah, bahwa jika pemisah terakhir adalah groupSeperator, ini akan lebih merupakan tipe integer, daripada double.

Kode yang ditambahkan adalah yang pertama jika dari 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

Alih-alih harus menentukan lokal di semua parse, saya lebih memilih untuk menetapkan lokal aplikasi yang luas, meskipun jika format string tidak konsisten di seluruh aplikasi, ini mungkin tidak berfungsi.

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

Menentukan ini pada awal aplikasi Anda akan membuat semua parses ganda mengharapkan koma sebagai pembatas desimal. Anda dapat mengatur lokal yang sesuai sehingga pemisah desimal dan ribuan sesuai dengan string yang Anda parsing.


0

Sulit tanpa menentukan pemisah desimal apa yang harus dicari, tetapi jika Anda melakukannya, inilah yang saya gunakan:

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

Ini harus bekerja dengan budaya apa pun. Itu benar gagal untuk mem-parsing string yang memiliki lebih dari satu pemisah desimal, tidak seperti implementasi yang menggantikan bukan swap.


0

Saya memperbaiki kode @JanW juga ...

Saya membutuhkannya untuk memformat hasil dari instrumen medis, dan mereka juga mengirim "> 1000", "23.3e02", "350E-02", dan "NEGATIF".

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

Saya pikir itu adalah jawaban terbaik:

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

Menjelaskan kode Anda dan memberikan lebih banyak detail akan sangat membantu.
Charlie Fish

Apa yang harus dijelaskan di sini? Semuanya ada di komentar
Endorphinex

-3

Di bawah ini kurang efisien, tetapi saya menggunakan logika ini. Ini hanya valid jika Anda memiliki dua digit setelah titik desimal.

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

Lipat gandakan jumlahnya dan kemudian bagikan dengan apa yang Anda gandakan sebelumnya.

Sebagai contoh,

perc = double.Parse("3.555)*1000;
result = perc/1000
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.