Достаточно часто возникает задача преобразовать string в float/double. При этом разработчик сталкивается с проблемой "точки и запятой" (разделителя целой и дробной частей десятичной дроби). Задача, в принципе, тривиальная, но все-таки хотелось бы рассмотреть некоторые моменты поподробнее.
Какие есть варианты ее решения?
1) заменить запятую на точку и распарсить:
public static float AsFloat1(this string s)
{
return float.Parse(s.Replace(",", "."), CultureInfo.InvariantCulture);
}
2) попробовать распарсить с использованием ожидаемой (или как вариант - текущей) локали, и если что не так (FormatException), попробовать распарсить в инвариантной локали (InvariantCulture).
public static float AsFloat2(this string s)
{
try
{
return float.Parse(s, CultureInfo.GetCultureInfo("uk"));
}
catch (FormatException)
{
return float.Parse(s, CultureInfo.InvariantCulture);
}
}3) "более правильный 1-й вариант": заменять запятую не на точку, а на символ, определенный в качестве разделителя в инвариантной локали:
public static float AsFloat3(this string s)
{
return float.Parse(
s.Replace(",", CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator),
CultureInfo.InvariantCulture);
}Какой же из вариантов наиболее подходящий?
С одной стороны 2-й вариант кажется весьма логичным: от пользователя ожидаются данные в формате, определяемом его локалью (явно, например, через настройки приложения, или неявно), и если что не так, то пытаемся использовать инвариантную локаль.
Но с другой стороны: как у этих методов с производительностью?
private static void Main()
{
var strings = new[] {"12,3", "12.3"};
var functions = new Func<string, float>[]
{
x => x.AsFloat1(),
x => x.AsFloat2(),
x => x.AsFloat3()
};
var watch = new Stopwatch();
var tests = from function in functions
select new Action<string>(x =>
{
watch.Restart();
var n = function(x);
watch.Stop();
Console.WriteLine("String: {0}; Number: {1}; Time: {2} ms.",
x, n, watch.Elapsed.TotalMilliseconds);
});
var testIndex = 0;
foreach (var test in tests)
{
Console.WriteLine("Method: AsFloat{0}", ++testIndex);
foreach (var s in strings)
test(s);
}
}Оказывается, что:
Method: AsFloat1 String: 12,3; Number: 12,3; Time: 0,1989 ms. String: 12.3; Number: 12,3; Time: 0,0018 ms. Method: AsFloat2 String: 12,3; Number: 12,3; Time: 0,2546 ms. String: 12.3; Number: 12,3; Time: 44,7575 ms. Method: AsFloat3 String: 12,3; Number: 12,3; Time: 0,2659 ms. String: 12.3; Number: 12,3; Time: 0,0032 ms. Для продолжения нажмите любую клавишу . . .
Поэтому, с точки зрения производительности, наиболее верным есть 1-й вариант (что, в принципе, не удивительно). Хотя 3-й вариант, который не полагается на знание того, что в инвариантной локали разделитель - точка, не сильно отстал и показывает примерно то же время.
Конечно, это не единственные варианты, можно придумать и еще, но в качестве пищи к размышлению, скорее всего, этого достаточно.
Столько лет программирования и ни разу не столкнулся с такой проблемой. Но сегодня решил запихнуть float из кода в MSSQL и тогда дошла вся банальность ситуации.
ОтветитьУдалитьСпасибо. Как раз была задача с прохождением чисел в виде строк в мультикультурной среде.
ОтветитьУдалить