Лучший способ преобразования строки в десятичный разделитель "." и "," нечувствительным способом?
Приложение имеет дело со строками, которые представляют десятичные числа, которые поступают из разных культур.
Например, "1.1" и "1,1" - это одно и то же значение.
Я играл с комбинациями флагов Decimal.TryParse
, но не смог добиться желаемого результата. "1,1" стало "11" или "0" в конце концов.
Можно ли преобразовать такие строки в десятичные числа в одну строку кода без предварительной замены "," char на "." или проигрывание с помощью NumberFormat.NumberDecimalSeparator
?
Как вы справляетесь с такими ситуациями?
Заранее благодарю!
Ответы
Ответ 1
У вас есть следующие возможности:
- Вы знаете культуру
- Используйте текущую настройку Culture, для которой установлен компьютер.
- Вы разрешаете пользователю устанавливать свою культуру → пользовательские настройки в вашей программе.
- Вы не знаете культуру
- Вы должны решить это: вы должны определить и документировать свое решение.
- Угадайте: вы пытаетесь разобрать и попытаться разобрать и попытаться... пока не получите действительные числа.
Ответ 2
Вы можете создать временный объект CultureInfo
для использования при анализе.
// get a temporary culture (clone) to modify
var ci = CultureInfo.InvariantCulture.Clone() as CultureInfo;
ci.NumberFormat.NumberDecimalSeparator = ",";
decimal number = decimal.Parse("1,1", ci); // 1.1
Ответ 3
Я нашел другой способ сделать это. Это выглядит странно, но это отлично работает для меня.
Итак, если вы не знаете культуру целевой системы и не знаете, какое значение вы получите, например 12.33 или 12,33, вы можете сделать следующее
string amount = "12.33";
// or i.e. string amount = "12,33";
var c = System.Threading.Thread.CurrentThread.CurrentCulture;
var s = c.NumberFormat.CurrencyDecimalSeparator;
amount = amount.Replace(",", s);
amount = amount.Replace(".", s);
decimal transactionAmount = Convert.ToDecimal(amount);
Ответ 4
Вам просто нужно иметь правильный набор культуры при вызове Parse
, например:
string s = "11,20";
decimal c1 = decimal.Parse(s, new CultureInfo("fr-FR"));
decimal c2 = decimal.Parse(s, new CultureInfo("en-AU"));
Console.WriteLine(c1);
Console.WriteLine(c2);
Ответ 5
Ниже моя реализация, любой хороший пророк?
/// <summary>
///
/// </summary>
public static class NumberExtensions
{
/// <summary>
/// Convert string value to decimal ignore the culture.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>Decimal value.</returns>
public static decimal ToDecimal ( this string value )
{
decimal number;
string tempValue = value;
var punctuation = value.Where ( x => char.IsPunctuation ( x ) ).Distinct ( );
int count = punctuation.Count ( );
NumberFormatInfo format = CultureInfo.InvariantCulture.NumberFormat;
switch ( count )
{
case 0:
break;
case 1:
tempValue = value.Replace ( ",", "." );
break;
case 2:
if ( punctuation.ElementAt ( 0 ) == '.' )
tempValue = value.SwapChar ( '.', ',' );
break;
default:
throw new InvalidCastException ( );
}
number = decimal.Parse ( tempValue, format );
return number;
}
/// <summary>
/// Swaps the char.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="from">From.</param>
/// <param name="to">To.</param>
/// <returns></returns>
public static string SwapChar ( this 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 ( );
}
}
[TestClass]
public class NumberTest
{
/// <summary>
///
/// </summary>
[TestMethod]
public void Convert_To_Decimal_Test ( )
{
string v1 = "123.4";
string v2 = "123,4";
string v3 = "1,234.5";
string v4 = "1.234,5";
string v5 = "123";
string v6 = "1,234,567.89";
string v7 = "1.234.567,89";
decimal a1 = v1.ToDecimal ( );
decimal a2 = v2.ToDecimal ( );
decimal a3 = v3.ToDecimal ( );
decimal a4 = v4.ToDecimal ( );
decimal a5 = v5.ToDecimal ( );
decimal a6 = v6.ToDecimal ( );
decimal a7 = v7.ToDecimal ( );
Assert.AreEqual ( ( decimal ) 123.4, a1 );
Assert.AreEqual ( ( decimal ) 123.4, a2 );
Assert.AreEqual ( ( decimal ) 1234.5, a3 );
Assert.AreEqual ( ( decimal ) 1234.5, a4 );
Assert.AreEqual ( ( decimal ) 123, a5 );
Assert.AreEqual ( ( decimal ) 1234567.89, a6 );
Assert.AreEqual ( ( decimal ) 1234567.89, a7 );
}
/// <summary>
///
/// </summary>
[TestMethod]
public void Swap_Char_Test ( )
{
string v6 = "1,234,567.89";
string v7 = "1.234.567,89";
string a1 = v6.SwapChar ( ',', '.' );
string a2 = v7.SwapChar ( ',', '.' );
Assert.AreEqual ( "1.234.567,89", a1 );
Assert.AreEqual ( "1,234,567.89", a2 );
}
}
Ответ 6
хороший, все же он не на 100% правильный.
когда вы используете случай 1: вы автоматически предполагаете, что ',' означает десятичную цифру. вы должны хотя бы проверить, происходит ли это более одного раза, в этом случае его группа, разделяющая символ
case 1:
var firstPunctuation = linq.ElementAt(0);
var firstPunctuationOccurence = value.Where(x => x == firstPunctuation).Count();
if (firstPunctuationOccurence == 1)
{
// we assume it a decimal separator (and not a group separator)
value = value.Replace(firstPunctuation.ToString(), format.NumberDecimalSeparator);
}
else
{
// multiple occurence means that symbol is a group separator
value = value.Replace(firstPunctuation.ToString(), format.NumberGroupSeparator);
}
break;
Ответ 7
Используйте TryParse дважды с 2 стилями, которые представляют две возможности
Если только одно возвращает значение, используйте это значение. Если оба возвращают значение, используйте меньшее значение в абсолютном выражении.
TryParse вернет 0 для чисел, которые используют как группировку, так и десятичный разделитель, если вы используете неправильный стиль, но если у вас нет разделителя группировки в вашей строке (например, число меньше 1000), он вернет значение в обоих случаях, но "неправильное" число будет больше (опять же в абсолютном выражении)