"3.5"
のような文字列をdoubleに解析したいのですが。しかしながら、
double.Parse("3.5")
35と
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint)
FormatException
をスローします。
現在、私のコンピューターのロケールはドイツ語に設定されています。ここでは、コンマが小数点記号として使用されています。それと、"3,5"
を入力として期待するdouble.Parse()
で何かをする必要があるかもしれませんが、私はよくわかりません。
現在のロケールの指定どおりにフォーマットされている場合とそうでない場合がある10進数を含む文字列を解析する方法を教えてください。
double.Parse("3.5", CultureInfo.InvariantCulture)
私は通常、マルチカルチャ関数を使用してユーザー入力を解析します。これは、誰かがテンキーに慣れていて、小数点としてコンマを使用するカルチャを使用している場合、その人はカンマの代わりにテンキーのポイントを使用するためです。
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;
}
ただし注意してください、@ nikieのコメントは正しいです。私の弁護するために、私は文化がen-US、en-CAまたはfr-CAのいずれかである可能性があることがわかっている管理された環境でこの機能を使用します。フランス語では小数点記号としてコンマを使用しているので、この関数を使用していますが、これまで金融で働いたことがある人は常にテンキーで小数点記号を使用しますが、これはコンマではなくポイントです。したがって、fr-CAカルチャでも、小数点を区切り文字として持つ数を解析する必要があります。
私はコメントを書くことができなかったので、私はここに書く:
double.Parse( "3.5"、CultureInfo.InvariantCulture)はお勧めできません。カナダでは3と書くので、 3.5ではなく5で、この関数は結果として35を返します。
私は自分のコンピューターで両方をテストしました。
double.Parse("3.5", CultureInfo.InvariantCulture) --> 3.5 OK
double.Parse("3,5", CultureInfo.InvariantCulture) --> 35 not OK
これは正しい方法です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;
}
トリックは、すべての文化でドットを解析するために、不変の文化を使用することです。
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)
解析する前にコンマをポイントに置き換えます。小数点としてコンマを使用している国で役立ちます。必要に応じて、ユーザー入力を1つのコンマまたはポイントに制限することを検討してください。
見てください、定数文字列で文字列置換を書くことを提案する上記のすべての答えは間違っていることができるだけです。どうして?あなたはWindowsの地域設定を尊重しないので! Windowsは、ユーザーが希望する区切り文字を自由に設定できることをユーザーに保証します。 S /彼はコントロールパネルを開いてリージョンパネルに入り、詳細設定をクリックしていつでもキャラクターを変更することができます。プログラム実行中でもこれを考えてください。良い解決策はこれを知っていなければなりません。
だから、最初にあなたは自分自身に、この数字がどこから来ているのか、あなたがパースしたいと頼む必要があるでしょう。それが同じフォーマットになるので、それが.NET Frameworkの入力から来ているのであれば問題ありません。しかし、おそらくそれは外部から来ているのかもしれませんし、外部のサーバーから来ているのかもしれませんし、おそらく文字列プロパティだけをサポートする古いDBから来ているかもしれません。そこでは、データベース管理者は、数字がどのフォーマットで格納されるべきかという規則を与えているべきです。たとえば、それがUS形式のUS DBになることがわかっている場合は、次のコードを使用できます。
CultureInfo usCulture = new CultureInfo("en-US");
NumberFormatInfo dbNumberFormat = usCulture.NumberFormat;
decimal number = decimal.Parse(db.numberString, dbNumberFormat);
これは世界中のどこでもうまく機能します。そして 'Convert.ToXxxx'を使わないでください。 'Convert'クラスは、あらゆる方向への変換の基礎としてのみ考えられています。ほかにも:あなたは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)));
値がユーザー入力から来る場合、100%正しい変換は不可能だと思います。例えば値が123.456の場合は、グループ化することも、小数点にすることもできます。あなたが本当に100%を必要とするなら、あなたはあなたのフォーマットを記述し、それが正しくないなら例外を投げなければなりません。
しかし、私はJanWのコードを改良したので、もう少し100%に進みます。背後にある考え方は、最後の区切り文字がgroupSeperatorの場合、これはdoubleではなく整数型になるということです。
追加されたコードは最初のif of 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;
}
次のコードはどのようなシナリオでも機能します。それは少し構文解析です。
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);
}
どの小数点区切り文字を探すのかを指定しないと難しいのですが、もしそうするなら、これが私が使っているものです:
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", ',');
}
これはどんな文化でもうまくいくはずです。スワップの代わりに置き換える実装とは異なり、複数の小数点を含む文字列を正しく解析できません。
@JanWのコードも改良しました...
医療機器からの結果をフォーマットするためにそれが必要です、そして、彼らは同じく「> 1000」、「23.3e02」、「350E-02」と「否定的」を送ります。
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つの見解は、一般的な二重の変換方法を提供しようとしています:
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;
}
期待通りに動作します:
デフォルトの変換は実装されていないので、1.3,14
、1,3.14
、または同様のケースを解析しようとして失敗するでしょう。
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);
すべての解析でロケールを指定する必要があるのではなく、アプリケーション全体のロケールを設定することをお勧めします。ただし、文字列フォーマットがアプリケーション全体で一貫していない場合は、これでうまくいかない場合があります。
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-PT");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("pt-PT");
アプリケーションの開始時にこれを定義すると、すべての二重解析で小数点区切り文字としてコンマが使用されます。適切なロケールを設定して、小数点と千単位の区切り記号が解析対象の文字列に合うようにすることができます。