Come posso analizzare una stringa con un punto decimale in un doppio?


231

Voglio analizzare una stringa come "3.5"una doppia. Però,

double.Parse("3.5") 

produce 35 e

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

genera a FormatException.

Ora le impostazioni internazionali del mio computer sono impostate su tedesco, in cui una virgola viene utilizzata come separatore decimale. Potrebbe essere necessario fare qualcosa con quello e double.Parse()aspettarsi "3,5"come input, ma non sono sicuro.

Come posso analizzare una stringa contenente un numero decimale che può o non può essere formattato come specificato nella mia locale corrente?


La virgola decimale influenzerà sicuramente l'output.
ChrisF

12
Non dimenticare il metodo double.TryParse (), se è appropriato per la tua situazione.
Kyle Gagnet,

Risposte:


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

Mi piace usare la XmlConvertclasse ... hai qualche idea se sia meglio, peggio e / o diverso dall'uso CultureInfo.InvariantCulture?
ChrisW,

1
Bene, XmlConvertnon è davvero destinato ad essere utilizzato per analizzare un singolo doppio valore nel codice. Preferisco usare double.Parseo Convert.ToDoubleche rendono evidente la mia intenzione.
Mehrdad Afshari,

4
Questo significa doulble.Parse utilizza la cultura predefinita che potrebbe non contenere punto come punto decimale ??
Ahmed ha detto il

3
se converte 12345678.12345678, converte anche 12345678.123457
PUG

4
non ha funzionato per me: salta virgola e restituisce e int come double
fnc12

75

Di solito uso una funzione multiculturale per analizzare l'input dell'utente, soprattutto perché se qualcuno è abituato al tastierino numerico e usa una cultura che usa una virgola come separatore decimale, quella persona utilizzerà il punto del tastierino numerico anziché una virgola.

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

Attenzione però, i commenti di @nikie sono veri. A mia difesa, utilizzo questa funzione in un ambiente controllato in cui so che la cultura può essere en-US, en-CA o fr-CA. Uso questa funzione perché in francese usiamo la virgola come separatore decimale, ma chiunque abbia mai lavorato in finanza userà sempre il separatore decimale sul tastierino numerico, ma questo è un punto, non una virgola. Quindi, anche nella cultura fr-CA, ho bisogno di analizzare il numero che avrà un punto come separatore decimale.


18
Non sono sicuro che sia una buona idea. Non è possibile analizzare in modo affidabile un doppio se non si conosce la cultura: in Germania, i valori doppi possono contenere ".", Ma sono considerati migliaia di separatori lì. Quindi nel caso di Legate, GetDouble ("3.5") restituirebbe 35 in una locale tedesca e 3.5 in un ambiente en-us.
Niki,

No, Pierre Alain ha ragione, poiché è esattamente come scritto. Se la tua cultura dice che "punto è mille" separatore, allora "3.5" è visto come "35" ed è buono. Tuttavia, se si coltiva come nessuna regola per il "punto", il carattere viene analizzato come punto decimale e funziona anche. Avrei evitato di provare la cultura enUS, ma è una scelta personale.
xryl669,

Se usi il tastierino numerico in cultura con virgola, il tasto punto verrà impostato come virgola.
CrazyBaran,

Il separatore decimale del tastierino numerico dipende dal layout della tastiera (e non dalle impostazioni internazionali - almeno da Windows 7) (uso l'ungherese per scrivere testo, e-mail ... ed en-US per scrivere codice, quindi può essere sia punto o virgola. Uso anche le impostazioni internazionali personalizzate in cui ho cambiato il separatore decimale da ',' a '.' e il separatore di elenco da ';' a ','. Sai, causa csv ... Buona fortuna a tutti noi che scriviamo multi -culture apps;)
Steven Spark,

25

Non ho potuto scrivere un commento, quindi scrivo qui:

double.Parse ("3.5", CultureInfo.InvariantCulture) non è una buona idea, perché in Canada scriviamo 3,5 anziché 3,5 e questa funzione ci dà 35 di conseguenza.

Ho provato entrambi sul mio computer:

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

Questo è un modo corretto di cui parlava 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
Ri: "... perché in Canada scriviamo 3,5 invece di 3,5" Ne sei sicuro? Secondo il segno decimale : "I paesi in cui un punto". "Viene utilizzato come segno decimale includono ... Canada (quando si utilizza l'inglese)" . Non si tratta più di usare una versione francese di Windows?
Peter Mortensen,

Baybe a causa della versione francese. In montreal scriviamo 3,5 non 3,5
Malus

Quindi secondo la tua logica, solo 1 di loro restituisce vero?
Batmaci,

Guardate che
Malus Jan

C'è ancora un errore. Per la stringa di input come GetDouble ("10 ,,,,,,,, 0", 0.0). La funzione menzionata restituisce 100.
Krivers

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

Sostituisci la virgola con un punto prima dell'analisi. Utile nei paesi con una virgola come separatore decimale. Pensa a limitare l'input dell'utente (se necessario) a una virgola o punto.


1
Risposta molto più corretta di quella che ha +133 voti ... Permette di vivere su entrambi i sistemi con "," o "." separatore decimale ...
Badiboy,

@Badiboy puoi spiegare cosa c'è di sbagliato in questa risposta? A quanto ho capito, InvariantCulture ha sempre "." come separatore decimale. Quindi dovrebbe funzionare per entrambi i sistemi.
Alex P.

@ Alex11223 Hai ragione. Ecco perché ho detto che questa risposta è migliore di quella più popolare. PS: Parlare amichevole questo codice fallirà anche se hai il "," come LIST SEPARATOR (cioè 1.234.560.01), ma non so come risolverlo affatto. :)
Badiboy,

4
Questa non è una buona risposta perché in alcune informazioni sulla cultura, è il separatore delle migliaia e può essere usato. Se lo sostituisci in un punto, alla fine avrai diversi punti e l'analisi fallirà: Double.Parse ((12345.67). ToString ("N", nuovo CultureInfo ("en")). Sostituisci (',', '. '), CultureInfo.InvariantCulture) because (12345.67) .ToString ("N", new CultureInfo ("en")). Sostituisci (', ','. ') Sarà formattato come "12.345.67"
codingdave

1.234,56 -> 1.234,56 non parser. un'altra idea è quella di verificare se il numero contiene '.' e ',' e sostituisci ',' con stringa vuota e, se solo ',' la virgola presentata la sostituisce in '.'
GDocal

16

Il trucco è usare la cultura invariante, per analizzare il punto in tutte le culture.

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

11

Guarda, ogni risposta sopra che propone di scrivere una sostituzione di stringa con una stringa costante non può che essere sbagliata. Perché? Perché non rispetti le impostazioni della regione di Windows! Windows assicura all'utente la libertà di impostare il carattere di separazione desiderato. Può aprire il pannello di controllo, accedere al pannello delle regioni, fare clic su Avanzate e modificare il personaggio in qualsiasi momento. Anche durante l'esecuzione del programma. Pensa a questo Una buona soluzione deve esserne consapevole.

Quindi, prima dovrai chiederti, da dove proviene questo numero, che vuoi analizzare. Se proviene dall'input in .NET Framework nessun problema, perché sarà nello stesso formato. Ma forse proveniva dall'esterno, forse da un server esterno, forse da un vecchio DB che supporta solo le proprietà di stringa. Lì, l'amministratore di database dovrebbe aver dato una regola in quale formato devono essere memorizzati i numeri. Se ad esempio sai che sarà un DB americano con formato USA, puoi usare questo codice:

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

Funzionerà bene in qualsiasi parte del mondo. E per favore non usare 'Convert.ToXxxx'. La classe "Converti" è pensata solo come base per conversioni in qualsiasi direzione. Inoltre: è possibile utilizzare il meccanismo simile anche per DateTimes.


Concordato! Cercare di implementare manualmente le funzionalità Cultura alla fine si tradurrà in un caso che non ti aspettavi e un grosso mal di testa. Lascia che .NET lo gestisca correttamente.
Khalos,

2
un grosso problema è quando gli utenti utilizzano un separatore decimale che non è considerato il separatore decimale per le sue impostazioni culturali
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

I miei due centesimi su questo argomento, cercando di fornire un metodo di doppia conversione generico:

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

Funziona come previsto con:

  • 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

Nessuna conversione di default è implementato, in modo che non riuscirebbe cercando di analizzare 1.3,14, 1,3.14o casi simili.


1
"1.000" intesi come mille falliranno.
Defkon1

1

Il codice seguente esegue il lavoro in qualsiasi scenario. Sta un po 'analizzando.

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 restituire 1234567.890
Dan Vogel il

Non ho provato, ma se esegui l'app in diversi SO di cultura, questo codice non farebbe il trucco, penso: /
Dani BISHOP,

1

Penso che la conversione corretta al 100% non sia possibile, se il valore deriva da un input dell'utente. ad es. se il valore è 123.456, può essere un raggruppamento o può essere un punto decimale. Se hai davvero bisogno del 100% devi descrivere il tuo formato e generare un'eccezione se non è corretto.

Ma ho migliorato il codice di JanW, quindi arriviamo un po 'più avanti al 100%. L'idea alla base è che se l'ultimo separatore è un groupSeperator, questo sarebbe più un tipo intero, che un doppio.

Il codice aggiunto è nel primo se di 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

Invece di dover specificare una locale in tutti gli analisi, preferisco impostare una locale a livello di applicazione, anche se se i formati di stringa non sono coerenti in tutta l'app, questo potrebbe non funzionare.

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

Definendolo all'inizio dell'applicazione, tutti i doppi analisi prevedono una virgola come delimitatore decimale. È possibile impostare una locale appropriata in modo che il decimale e il separatore delle migliaia corrispondano alle stringhe che si stanno analizzando.


0

È difficile senza specificare quale separatore decimale cercare, ma se lo fai, questo è quello che sto usando:

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

Questo dovrebbe funzionare con qualsiasi cultura. Non riesce correttamente ad analizzare le stringhe che hanno più di un separatore decimale, a differenza delle implementazioni che sostituiscono invece dello swap.


0

Ho migliorato anche il codice di @JanW ...

Ne ho bisogno per formattare i risultati di strumenti medici e inviano anche "> 1000", "23.3e02", "350E-02" e "NEGATIVO".

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

Penso che sia la risposta migliore:

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

Spiegare il tuo codice e fornire maggiori dettagli sarebbe utile.
Charlie Fish,

Cosa spiegare qui? Tutto è nei commenti
Endorphinex,

-3

Il seguito è meno efficiente, ma io uso questa logica. Questo è valido solo se hai due cifre dopo il punto decimale.

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

Moltiplica il numero e poi dividerlo per ciò che hai moltiplicato prima.

Per esempio,

perc = double.Parse("3.555)*1000;
result = perc/1000
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.