Je veux analyser une chaîne comme "3.5"
en double. cependant,
double.Parse("3.5")
donne 35 et
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint)
jette une FormatException
.
Les paramètres régionaux de mon ordinateur sont désormais définis en allemand, une virgule servant de séparateur décimal. Il faudra peut-être faire quelque chose avec cela et double.Parse()
s'attendre à "3,5"
en entrée, mais je ne suis pas sûr.
Comment analyser une chaîne contenant un nombre décimal pouvant ou non être formaté comme spécifié dans mes paramètres régionaux actuels?
double.Parse("3.5", CultureInfo.InvariantCulture)
J'utilise habituellement une fonction multiculturelle pour analyser les entrées utilisateur, principalement parce que si quelqu'un est habitué au pavé numérique et utilise une culture utilisant une virgule comme séparateur décimal, cette personne utilisera le point du pavé numérique au lieu d'une virgule.
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;
}
Attention cependant, les commentaires de @nikie sont vrais. Pour ma défense, j'utilise cette fonction dans un environnement contrôlé où je sais que la culture peut être en-US, en-CA ou fr-CA. J'utilise cette fonction car en français, nous utilisons la virgule comme séparateur décimal, mais quiconque a déjà travaillé en finance utilisera toujours le séparateur décimal sur le pavé numérique, mais il s'agit d'un point et non d'une virgule. Donc, même dans la culture fr-CA, je dois analyser le nombre qui aura un point comme séparateur décimal.
Je ne pouvais pas écrire de commentaire, alors j'écris ici:
double.Parse ("3.5", CultureInfo.InvariantCulture)n'est pas une bonne idée, car au Canada, nous écrivons 3,5 au lieu de 3,5 et cette fonction nous en donne 35.
J'ai testé les deux sur mon ordinateur:
double.Parse("3.5", CultureInfo.InvariantCulture) --> 3.5 OK
double.Parse("3,5", CultureInfo.InvariantCulture) --> 35 not OK
C'est une façon correcte quePierre-Alain Vigeantmentionné
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;
}
L'astuce consiste à utiliser la culture invariante, à analyser les points dans toutes les cultures.
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)
Remplacez la virgule par un point avant l'analyse. Utile dans les pays avec une virgule comme séparateur décimal. Pensez à limiter la saisie de l'utilisateur (si nécessaire) à une virgule ou à un point.
Regardez, chaque réponse ci-dessus qui propose d'écrire un remplacement de chaîne par une chaîne constante ne peut être que fausse. Pourquoi? Parce que vous ne respectez pas les paramètres de région de Windows! Windows garantit à l'utilisateur la liberté de définir le caractère de séparation qu'il souhaite. Il peut ouvrir le panneau de configuration, accéder au panneau de région, cliquer sur Avancé et modifier le personnage à tout moment. Même pendant votre programme. Pensez à cela. Une bonne solution doit en être consciente.
Donc, vous devrez d’abord vous demander d’où vous voulez analyser ce numéro. Si cela provient d'une entrée dans le .NET Framework, cela ne pose aucun problème, car ce sera dans le même format. Mais cela venait peut-être de l'extérieur, peut-être d'un serveur externe, peut-être d'une ancienne base de données ne supportant que les propriétés de chaîne. Là, l'administrateur de base de données aurait dû donner une règle dans laquelle les nombres doivent être stockés. Si vous savez par exemple qu'il s'agira d'une base de données américaine au format américain, vous pouvez utiliser ce morceau de code:
CultureInfo usCulture = new CultureInfo("en-US");
NumberFormatInfo dbNumberFormat = usCulture.NumberFormat;
decimal number = decimal.Parse(db.numberString, dbNumberFormat);
Cela fonctionnera bien partout dans le monde. Et s'il vous plaît, n'utilisez pas 'Convert.ToXxxx'. La classe 'Convert' est uniquement considérée comme une base pour les conversions dans n'importe quelle direction. En outre: Vous pouvez également utiliser le même mécanisme pour DateTimes.
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)));
Je pense qu'une conversion correcte à 100% n'est pas possible si la valeur provient d'une entrée utilisateur. par exemple. si la valeur est 123,456, il peut s'agir d'un groupement ou d'un point décimal. Si vous avez vraiment besoin de 100%, vous devez décrire votre format et émettre une exception s'il n'est pas correct.
Mais j'ai amélioré le code de JanW, nous avons donc un peu plus d'avance sur les 100%. L'idée sous-jacente est que si le dernier séparateur est un groupSeperator, il s'agirait davantage d'un type entier que d'un double.
Le code ajouté est dans le premier si de 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;
}
Le code suivant fait le travail dans n'importe quel scénario. C'est un peu d'analyse.
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);
}
C'est difficile sans spécifier le séparateur décimal à rechercher, mais si vous le faites, c'est ce que j'utilise:
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", ',');
}
Cela devrait fonctionner avec n'importe quelle culture. Il échoue correctement lors de l'analyse de chaînes comportant plusieurs séparateurs décimaux, contrairement aux implémentations qui remplacent le remplacement par swap.
J'ai aussi amélioré le code de @JanW ...
J'en ai besoin pour formater les résultats des instruments médicaux, et ils envoient également "> 1000", "23.3e02", "350E-02" et "NÉGATIF".
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;
}
}
Mes deux cents sur ce sujet, en essayant de fournir une méthode générique à double conversion:
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;
}
Fonctionne comme prévu avec:
Aucune conversion par défaut n'étant implémentée, la tentative d'analyse de 1.3,14
, 1,3.14
ou de cas similaires échouera.
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);
Au lieu d'avoir à spécifier des paramètres régionaux dans toutes les analyses, je préfère définir des paramètres régionaux à l'échelle de l'application. Toutefois, si les formats de chaîne ne sont pas cohérents dans l'application, cela risque de ne pas fonctionner.
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-PT");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("pt-PT");
Si vous définissez cela au début de votre application, toutes les doubles analyses s'attendent à une virgule comme séparateur décimal. Vous pouvez définir des paramètres régionaux appropriés pour que le séparateur décimal et le séparateur de milliers correspondent aux chaînes que vous analysez.