I have SSRS reports that display currency amounts. They need to be both culture aware and currency aware. Some reports show different currencies in the same table. I have no trouble with culture awareness. It's currency formatting that's the trouble. Importantly, when I export to Excel, the values in these currency fields must be sortable as numbers. That means the cell values must be numbers, so I cannot use the normal .ToString("C", culture) functions that so many other posts end up with. I need to keep the numeric value in the field and to apply .NET's format string to the number (e.g. "'$'#,0.00;('$'#,0.00)"). This way, Excel will treat the value as a number for sorting purposes but display the correctly formatted currency.
Is it possible to use code to modify a NumberFormatInfo instance and then somehow return the string value of the formatter, such as "'€'#,0.00;('€'#,0.00)"?
var numberFormat = new CultureInfo("en-US").NumberFormat;
numberFormat.CurrencySymbol = "€";
return numberFormat.GetCurrencyFormatString(); //this is an imaginary function that I need to return "'€'#,0.00;('€'#,0.00)"
I have tried programmatically setting the currency symbol based on the currency information of each row. As far as I know, SSRS does not allow me to use an Expression to set the currency symbol. It only offers a dropdown list.
My users don't like it when I show the currency code (e.g. USD, CAD), so I'm stuck with showing the symbol (e.g. $, CA$).
As far as I can tell, you'll need to manually construct this format string using the CultureInfo class.
Using the docs on CurrencyPositivePattern and CurrencyNegativePattern (see here and here), I've put together something that works but might need some tweaking:
string GetCurrencyFormat(CultureInfo culture)
{
//we'll use string.Format later to replace {0} with the currency symbol
//and {1} with the number format
string[] negativePatternStrings = { "({0}{1})", "-{0}{1}", "{0}-{1}", "{0}{1}-", "({1}{0})",
"-{1}{0}", "{1}-{0}", "{1}{0}-", "-{1} {0}", "-{0} {1}",
"{1} {0}-", "{0} {1}-", "{0} -{1}", "{1}- {0}", "({0} {1})",
"({1} {0})" };
string[] positivePatternStrings = { "{0}{1}", "{1}{0}", "{0} {1}", "{1}{0}" };
var numberFormat = culture.NumberFormat;
//Generate 0's to fill in the format after the decimal place
var decimalPlaces = new string('0', numberFormat.CurrencyDecimalDigits);
//concatenate the full number format, e.g. #,0.00
var fullDigitFormat = $"#{numberFormat.CurrencyGroupSeparator}0{numberFormat.CurrencyDecimalSeparator}{decimalPlaces}";
//use string.Format on the patterns to get the positive and
//negative formats
var positiveFormat = string.Format(positivePatternStrings[numberFormat.CurrencyPositivePattern],
numberFormat.CurrencySymbol, fullDigitFormat);
var negativeFormat = string.Format(negativePatternStrings[numberFormat.CurrencyNegativePattern],
numberFormat.CurrencySymbol, fullDigitFormat);
//finally, return the full format
return $"{positiveFormat};{negativeFormat}";
}
This returns $#,0.00;($#,0.00) for en-US, £#,0.00;-£#,0.00 for en-GB, for example.
This question already has answers here:
String to decimal conversion: dot separation instead of comma
(8 answers)
Closed 3 years ago.
I have this small piece of code:
using System;
using System.Globalization;
namespace project
{
class conditionalStatements
{
static void Main(string[] args)
{
Console.WriteLine("Enter a number greater than 45.2");
string answer = Console.ReadLine();
decimal answer_decimal = Convert.ToDecimal(answer);
// decimal answer_decimal = Decimal.Parse(answer);
decimal compareValue = 45.2m;
Console.WriteLine(answer_decimal);
//prints 452
if(Decimal.Compare(answer_decimal, compareValue) > 0){
// stuff
}
else{
// should enter here
}
}
}
}
The problem is that since both the method Convert.ToDecimal() and Decimal.Parse() ignore the dot notation of decimal values (or at least that's what's happening to me) the number is interpreted as 452 instead of 45.2. No matter how many dots I input. In fact, if I were to enter:
45......2
the converted value still would be converted to 452. Only if I use the comma, then the converted number is correctly interpreted as 45.2 and I am able to enter the else condition.
I did not change the NumberFormatInfo.CurrencyDecimalSeparator. I left it as default '.'
Convert input string to decimal with a given culture that treats dot as decimal separator:
decimal answer_decimal = Convert.ToDecimal(answer, new CultureInfo("en-US"));
You could try something like the code below:
decimal answer_decimal;
while(!decimal.TryParse(answer, out answer_decimal)){
Console.WriteLine("Value entered could not be converted.");
}
decimal compareValue = 45.2m;
Console.WriteLine(answer_decimal);
//prints 452
if(Decimal.Compare(answer_decimal, compareValue) > 0){
// stuff
}
else{
// should enter here
}
This way you prevent the program from crashing if the conversion is not possible.
If you want to use a specific culture you can do so by using an overload of decimal.TryParse as follows:
decimal.TryParse(answer, System.Globalization.NumberStyles.Any, new System.Globalization.CultureInfo("EN-us"), out answer_decimal);
Is your culture set to Dari?
I think the applicable NumberFormatInfo property would be NumberDecimalSeparator, not CurrencyDecimalSeparator. decimal.Parse(), called directly or via Convert.ToDecimal(), would have no idea currency is what's being parsed unless a NumberStyles value with one of the *Currency* flags were passed.
When an overload of decimal.Parse() is called that does not accept a NumberStyles parameter it defaults to NumberStyles.Number. This composite style includes the NumberStyles.AllowDecimalPoint style, the documentation for which states (emphasis mine)...
If the NumberStyles value includes the AllowCurrencySymbol flag and the parsed string includes a currency symbol, the decimal separator character is determined by the CurrencyDecimalSeparator property. Otherwise, the decimal separator character is determined by the NumberDecimalSeparator property.
Now, are there actually any cultures that use different decimal separators for numbers and currency? Let's find out...
CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(culture => culture.NumberFormat.CurrencyDecimalSeparator != culture.NumberFormat.NumberDecimalSeparator);
On .NET Framework v4.7.2 that yields a small number of cultures...
fr-CH
kea
kea-CV
mr
mr-IN
prs
prs-AF
pt-CV
Tweaking that LINQ query to account for the specific behavior you're seeing (currency decimal separator is ".", number decimal separator is ",", number group separator is ".")...
CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(culture => culture.NumberFormat.CurrencyDecimalSeparator == ".")
.Where(culture => culture.NumberFormat.NumberDecimalSeparator == ",")
.Where(culture => culture.NumberFormat.NumberGroupSeparator == ".");
...narrows it down to two Dari cultures...
prs
prs-AF
Sure enough, if I change my culture to Dari beforehand...
CultureInfo.CurrentCulture = new CultureInfo("prs");
...on my system your code behaves exactly as you described. If you don't want to use your culture's separators the solution, of course, is to specify at the system, thread, or method level a specific or custom culture with the separators you do want.
As part of my work,I need to read price values from Excel sheet . I need to implement
Prices: Non-numeric characters in Price not allowed
price should be valid number for price like int,decimal,double etc like 10,10.00,10,233 valid, -10,-10.00,-10,2333.00 etc are invalid
Prices: Price format (dots, comma's, decimals)
Zero and negative price values are not allowed
Need to check price value type (number type like int,float,decimal etc but will save in database in money format)
What datatype I should we choose for Price ? decimal or double or anyother ? In database I took database field type as money.
I do not think you need any regex if you just need to validate price numbers in C#.
I'd suggest using Decimal type, here you can find why. The Decimal class contains a static TryParse method that can be used to validate numbers as valid decimal numbers. Here is a slightly modified example from MSDN (I decided to go with InvariantCulture, but it depends on whether your DB contains currencies in EN-US format or not):
var validated = false;
decimal number;
// Parse currency value using current culture.
var value = "1,097.63";
var style = System.Globalization.NumberStyles.Number;
var culture = System.Globalization.CultureInfo.InvariantCulture;
if (!Decimal.TryParse(value, style, culture, out number))
if (number > 0) // Check if the value is not negative or zero
validated = true;
Consider a Decimal value:
Decimal value = -1234567890.1234789012M;
i want to convert this Decimal value to a string, and include "thousands separators".
Note: i don't want to include thousand's separators, i want to include digit grouping. The difference is important for cultures that don't group numbers into thousands, or don't use commas to separate groups
Some example output with different standard formatting strings, on my computer, with my current locale:
value.ToString() = -1234567890..1234789012 (Implicit General)
value.ToString("g") = -1234567890..1234789012 (General)
value.ToString("d") = FormatException (Decimal whole number)
value.ToString("e") = -1..234568e++009 (Scientific)
value.ToString("f") = -1234567890..123 (Fixed Point)
value.ToString("n") = -12,,3456,,7890..123 (Number with commas for thousands)
value.ToString("r") = FormatException (Round trippable)
value.ToString("c") = -$$12,,3456,,7890..123 (Currency)
value.ToString("#,0.#") = -12,,3456,,7890..1
What i want (depending on culture) is:
en-US -1,234,567,890.1234789012
ca-ES -1.234.567.890,1234789012
gsw-FR -1 234 567 890,1234789012 (12/1/2012: fixed gws-FR to gsw-FR)
fr-CH -1'234'567'890.1234789012
ar-DZ 1,234,567,890.1234789012-
prs-AF 1.234.567.890,1234789012-
ps-AF 1،234،567،890,1234789012-
as-IN -1,23,45,67,890.1234789012
lo-LA (1234567,890.1234789012) (some debate if numbers should be "1,234,567,890")
qps-PLOC 12,,3456,,7890..1234789012
How can i convert a Decimal to a string, with digit groupings?
Update: Some more desired output, using my current culture of :
-1234567890M --> -12,,3456,,7890
-1234567890.1M --> -12,,3456,,7890..1
-1234567890.12M --> -12,,3456,,7890..12
-1234567890.123M --> -12,,3456,,7890..123
-1234567890.1234M --> -12,,3456,,7890..1234
-1234567890.12347M --> -12,,3456,,7890..12347
-1234567890.123478M --> -12,,3456,,7890..123478
-1234567890.1234789M --> -12,,3456,,7890..1234789
-1234567890.12347890M --> -12,,3456,,7890..1234789
-1234567890.123478901M --> -12,,3456,,7890..123478901
-1234567890.1234789012M --> -12,,3456,,7890..1234789012
Update: i tried peeking at how Decimal.ToString() manages to use the General format to show all the digits that it needs to show:
public override string ToString()
{
return Number.FormatDecimal(this, null, NumberFormatInfo.CurrentInfo);
}
except that Number.FormatDecimal is hidden somewhere:
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern string FormatDecimal(decimal value, string format, NumberFormatInfo info);
So that's a dead end.
The ToString method on decimals by default uses the CultureInfo.CurrentCulture for the user's session, and thus varies based on whom is running the code.
The ToString method also accepts an IFormatProvider in various overloads. This is where you need to supply your culture-specific Formatters.
For instance, if you pass the NumberFormat for fr-CH, you can format things as that culture expects:
var culture = CultureInfo.CreateSpecificCulture("fr-CH");
Decimal value = -1234567890.1234789012M;
Console.WriteLine(value.ToString("##,#.###############", culture.NumberFormat));
Will output
-1'234'567'890.1234789012
Edit #3 - rewrote using custom formatters. This should do what you want based on the new updated question.
Edit #4 - Took all of your input, and ran this:
public void TestOutput()
{
PrintValue(-1234567890M);
PrintValue(-1234567890.1M);
PrintValue(-1234567890.12M);
PrintValue(-1234567890.123M);
PrintValue(-1234567890.1234M);
PrintValue(-1234567890.12347M);
PrintValue(-1234567890.123478M);
PrintValue(-1234567890.1234789M);
PrintValue(-1234567890.12347890M);
PrintValue(-1234567890.123478901M);
PrintValue(-1234567890.1234789012M);
}
private static void PrintValue(decimal value)
{
var culture = CultureInfo.CreateSpecificCulture("qps-PLOC");
Console.WriteLine(value.ToString("##,#.###############", culture.NumberFormat));
}
Gives output matching what you supplied:
--12,,3456,,7890
--12,,3456,,7890..1
--12,,3456,,7890..12
--12,,3456,,7890..123
--12,,3456,,7890..1234
--12,,3456,,7890..12347
--12,,3456,,7890..123478
--12,,3456,,7890..1234789
--12,,3456,,7890..1234789
--12,,3456,,7890..123478901
--12,,3456,,7890..1234789012
As pointed out by Joshua, this only works for some locales.
From the looks of it then, you need to pick the lesser of two evils: Knowing the precision of your numbers, or specifying formats for each culture. I'd wager knowing the precision of your numbers may be easier.
In which case, a previous version of my answer may be of use:
To explicitly control the number of decimal places to output, you can clone the number format provided by the culture and modify the NumberDecimalDigits property.
var culture = CultureInfo.CreateSpecificCulture("fr-CH");
Decimal value = -1234567890.1234789012M;
NumberFormatInfo format = (NumberFormatInfo)culture.NumberFormat.Clone();
format.NumberDecimalDigits = 30;
Console.WriteLine(value.ToString("n", format));
This outputs:
-1'234'567'890.123478901200000000000000000000
You can specify a custom pattern (the pattern will appropriately resolve to the culture specific method of grouping and the appropriate grouping and decimal separator characters). A pattern can have positive, negative and zero sections. The positive pattern is always the same but the negative pattern depends on the culture and can be retrieved from the NumberFormatInfo's NumberNegativePattern property. Since you want as much precision as possible, you need to fill out 28 digit placeholders after the decimal; the comma forces grouping.
public static class DecimalFormatters
{
public static string ToStringNoTruncation(this Decimal n, IFormatProvider format)
{
NumberFormatInfo nfi = NumberFormatInfo.GetInstance(format);
string[] numberNegativePatterns = {
"(#,0.############################)", //0: (n)
"-#,0.############################", //1: -n
"- #,0.############################", //2: - n
"#,0.############################-", //3: n-
"#,0.############################ -"};//4: n -
var pattern = "#,0.############################;" + numberNegativePatterns[nfi.NumberNegativePattern];
return n.ToString(pattern, format);
}
public static string ToStringNoTruncation(this Decimal n)
{
return n.ToStringNoTruncation(CultureInfo.CurrentCulture);
}
}
Sample output
Locale Output
======== ============================
en-US -1,234,567,890.1234789012
ca-ES -1.234.567.890,1234789012
hr-HR - 1.234.567.890,1234789012
gsw-FR -1 234 567 890,1234789012
fr-CH -1'234'567'890.1234789012
ar-DZ 1,234,567,890.1234789012-
prs-AF 1.234.567.890,1234789012-
ps-AF 1،234،567،890,1234789012-
as-IN -1,23,45,67,890.1234789012
lo-LA (1234567,890.1234789012)
qps-PLOC -12,,3456,,7890..1234789012
There is currently no locale that uses NegativeNumberFormat 4 (n -), so that case cannot be tested. But there's no reason to think it would fail.
You need to include the culture when formatting for your strings. You can either use String.Format and include the culture as the first parameter or use the object's ToString method and use the overload that takes a culture.
The following code produces the expected output (except for gws-FR, it couldn't find a culture with that string).
namespace CultureFormatting {
using System;
using System.Globalization;
class Program {
public static void Main() {
Decimal value = -1234567890.1234789012M;
Print("en-US", value);
Print("ca-ES", value);
//print("gws-FR", value);
Print("fr-CH", value);
Print("ar-DZ", value);
Print("prs-AF", value);
Print("ps-AF", value);
Print("as-IN", value);
Print("lo-LA", value);
Print("qps-PLOC", value);
}
static void Print(string cultureName, Decimal value) {
CultureInfo cultureInfo = new CultureInfo(cultureName);
cultureInfo.NumberFormat.NumberDecimalDigits = 10;
// Or, you could replace the {1:N} with {1:N10} to do the same
// for just this string format call.
string result =
String.Format(cultureInfo, "{0,-8} {1:N}", cultureName, value);
Console.WriteLine(result);
}
}
}
The above code produces the following output:
en-US -1,234,567,890.1234789012
ca-ES -1.234.567.890,1234789012
fr-CH -1'234'567'890.1234789012
ar-DZ 1,234,567,890.1234789012-
prs-AF 1.234.567.890,1234789012-
ps-AF 1،234،567،890,1234789012-
as-IN -1,23,45,67,890.1234789012
lo-LA (1234567,890.1234789012)
qps-PLOC --12,,3456,,7890..1234789012
If you're working with a multithreaded system, such as ASP.Net, you can change the thread's CurrentCulture property. Changing the thread's culture will allow all of the associated ToString and String.Format calls to use that culture.
Update
Since you're wanting to display all of the precision you're going to have to do a bit of work. Using NumberFormat.NumberDecimalDigits will work, except that if the value has less precision, the number will output with trailing zeros. If you need to make sure you display every digit without any extras, you will need to calculate the precision beforehand and set that before you convert it to a string. The StackOverflow question Calculate System.Decimal Precision and Scale may be able to help you determine the precision of the decimal.