String to Double Conversion (Comma to dot issue) - c#

Struggling with the basics - I'm trying to code a simple currency converter. The XML provided by external source uses comma as a decimal separator for exchange rate (kurs_sredni):
<pozycja>
<nazwa_waluty>bat (Tajlandia)</nazwa_waluty>
<przelicznik>1</przelicznik>
<kod_waluty>THB</kod_waluty>
<kurs_sredni>0,1099</kurs_sredni>
</pozycja>
I already managed to load the data from XML into a nifty list of objects (kursyAktualne), and now i'm trying to do the math. I'm stuck with conversion.
First of all i'm assigning "kurs_sredni" to a string, trying to replace "," with "." and converting the hell out of it:
string kursS = kursyAktualne[iNa].kurs_sredni;
kursS.Replace(",",".");
kurs = Convert.ToDouble(kursS);
MessageBox.Show(kurs.ToString());
The messagebox show 1099 instead of expected 0.1099 and kursS still has comma, not dot.
Tried toying with some cultureInfo stuff i googled, but that was too random. I need to understand how to control this.

Just use decimal.Parse but specify a CultureInfo. There's nothing "random" about it - pick an appropriate CultureInfo, and then use that. For example:
using System;
using System.Globalization;
class Test
{
static void Main()
{
var french = CultureInfo.GetCultureInfo("fr-FR");
decimal value = decimal.Parse("0,1099", french);
Console.WriteLine(value.ToString(CultureInfo.InvariantCulture)); // 0.1099
}
}
This is just using French as one example of a culture which uses , as a decimal separator. It would probably make sense to use the culture of the origin of the data.
Note that decimal is a better pick for currency values than double - you're trying to represent an "artificial" construct which is naturally specified in base10, rather than a "natural" continuous value such as a weight.
(I would also be wary of a data provider who provides data in a non-standard format. If they're getting that wrong, who knows what else they'll get wrong. It's not like XML doesn't have a well-specified format for numbers...)

It is because Replace method returns new string with replaced characters. It does not modify your original string.
So you need to reassign it:
kursS = kursS.Replace(",",".");

Replace returns a string. So you need an assignment.
kursS = kursS.Replace(",", ".");
There is "neater" way of doing this by using CulturInfo. Look this up on the MSDN website.

You replace result isn't used, but the original value that doesn't contain the replace.
You should do:
kursS = kursS.Replace(",", ".")
In addition this method isn't really safe if there are thousands-separators.
So if you are not using culture settings you should do:
kursS = kursS.Replace(".", "").Replace(",", ".")

Related

How to format a string into a string with currency symbol and seperators

Numerical formatting of int, double, decimal can be simply achieved via using Standard Numerical Formatter for example assuming Culture is "en-GB":
int value = 1000;
Console.WriteLine(value.ToString("C0")); // Would output £1,000
However I am wondering if there is a simple way to format a string to something of the same effect as above. For example:
string amount = "£2000"; // Would want to format to "£2,000"
Is there a way to format this string so that the thousands separator is added in the correct position?
Given it's a string I don't think numerical formatting would work without converting the string to a numerical data type beforehand:
var result = Int32.Parse("£2000", NumberStyles.AllowCurrencySymbol, new CultureInfo("en-GB"));
Console.WriteLine(result.ToString("C0", new CultureInfo("en-GB"))); // Outputs £2,000
However its a bit verbose to convert string to int then back to string. Is there is a simpler way to do this given that the starting string has the currency symbol?
Given it's a string I don't think numerical formatting would work without converting the string to a numerical data type beforehand
Indeed.
Is there is a simpler way to do this given that the starting string has the currency symbol?
No. And I seriously doubt that such a feature would ever be added and/or welcomed by the developer community. A formal specification for such a feature would be a complexity nightmare.
Of course, in your particular case, if you are sure that your string always consists of "currency symbol + sequence of digits without comma or period", you could develop a string-based solution optimized for your use case (for example, a fancy regular expression). However, I think that your current solution is both readable and maintainable and you would do your future self a favor by keeping it that way. If you need it multiple times, extract it into a method.

C# String format: escape a dot

I have a line of code, something like:
mbar.HealthLabel.text = String.Format("{0:0.0}", _hp);
Output is: 2.25 for example. Is it possible to escape a dot from the output string view with String.Format function ?
For. ex. 225,
To make my question more clear, I need the same effect like:
Math.Floor(_hp * 100).ToString();
But need to do it by String.Format template.. Thanks.
Simply you can do it this way
double value = 1.25;
var stringValue = string.Format("{0:0}", value * 100, CultureInfo.InvariantCulture); //125
EDIT:
More general solution would be to Replace the dot with empty string as stated in the comments.
double value = 1.25;
var stringValue = value.ToString(CultureInfo.InvariantCulture).Replace(".",string.Empty);
EDIT2: Also there is another general idea that do not use Replace function (but also it does not use the String.Format)
var stringValue = string.Join("", value.ToString().Where(char.IsDigit));
Also another similar idea:
var stringValue = new string(value.ToString().Where(char.IsDigit).ToArray());
First of all, please read my comment to the question. As i mentioned there, string format for numeric values depends on regional settings. So, below line
mbar.HealthLabel.text = String.Format("{0:0.0}", _hp);
will return: 2,25 (as to the polish numeric standard)
In my opinion you need something like this:
mbar.HealthLabel.text = String.Format("{0:D}", Math.Floor(_hp*100));
For furhter details, please see:
Standard Numeric Format Strings
Custom Numeric Format Strings
Up to Microsoft.NET Framework 4.7 there is no way to solve this when we are only allowed to modify the format string ("template"). All solutions require to either:
Post-process the resulting string. Here, the closest for the special case with two decimals may be the % format
Compute the numeric argument first to make it integer
Write and apply a custom IFormatProvider implementation
If you want to eliminate the decimal separtor ("escape the dot", as you've put it), try replacing the decimal separator with empty string:
string result = String
.Format("{0:0.0}", _hp)
.Replace(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, "");

custom string format puzzler

We have a requirement to display bank routing/account data that is masked with asterisks, except for the last 4 numbers. It seemed simple enough until I found this in unit testing:
string.Format("{0:****1234}",61101234)
is properly displayed as: "****1234"
but
string.Format("{0:****0052}",16000052)
is incorrectly displayed (due to the zeros??): "****1600005252""
If you use the following in C# it works correctly, but I am unable to use this because DevExpress automatically wraps it with "{0: ... }" when you set the displayformat without the curly brackets:
string.Format("****0052",16000052)
Can anyone think of a way to get this format to work properly inside curly brackets (with the full 8 digit number passed in)?
UPDATE: The string.format above is only a way of testing the problem I am trying to solve. It is not the finished code. I have to pass to DevExpress a string format inside braces in order for the routing number to be formatted correctly.
It's a shame that you haven't included the code which is building the format string. It's very odd to have the format string depend on the data in the way that it looks like you have.
I would not try to do this in a format string; instead, I'd write a method to convert the credit card number into an "obscured" string form, quite possibly just using Substring and string concatenation. For example:
public static string ObscureFirstFourCharacters(string input)
{
// TODO: Argument validation
return "****" + input.Substring(4);
}
(It's not clear what the data type of your credit card number is. If it's a numeric type and you need to convert it to a string first, you need to be careful to end up with a fixed-size string, left-padded with zeroes.)
I think you are looking for something like this:
string.Format("{0:****0000}", 16000052);
But I have not seen that with the * inline like that. Without knowing better I probably would have done:
string.Format("{0}{1}", "****", str.Substring(str.Length-4, 4);
Or even dropping the format call if I knew the length.
These approaches are worthwhile to look through: Mask out part first 12 characters of string with *?
As you are alluding to in the comments, this should also work:
string.Format("{0:****####}", 16000052);
The difference is using the 0's will display a zero if no digit is present, # will not. Should be moot in your situation.
If for some reason you want to print the literal zeros, use this:
string.Format("{0:****\0\052}", 16000052);
But note that this is not doing anything with your input at all.

xml Convert.ToDouble works wrong

I have a project that reads some data from xml file
Convert.ToDouble metod works wrong.
it converts 0.05 as 5.
is there any idea to solve this problem?
this is my ReadDataFromXml method:
public static List<double> XmldenTabanDegerleriniOku(string ID)
{
string ayarDir = System.Environment.CurrentDirectory + "\\Ayarlar";
string Dosya = ayarDir + "\\YuzeyBoyutlari.xml";
XmlDocument doc = new XmlDocument();
doc.Load(Dosya);
XmlNodeList holList = doc.GetElementsByTagName("HOL");
List<double> tabanDegerleri = new List<double>();
foreach (XmlNode node in holList)
{
XmlElement holElement = (XmlElement)node;
if (node.Attributes["ID"].Value == ID) {
double uzunluk =Convert.ToDouble(holElement.GetElementsByTagName("uzunluk")[0].InnerText.Replace('.',','));
double genislik =Convert.ToDouble(holElement.GetElementsByTagName("genislik")[0].InnerText.Replace('.',','));
double cizgilerArasiMesafe = Convert.ToDouble(holElement.GetElementsByTagName("cizgilerArasiMesafe")[0].InnerText.Replace('.', ','));
tabanDegerleri.Add(uzunluk);
tabanDegerleri.Add(genislik);
tabanDegerleri.Add(cizgilerArasiMesafe);
break;
}
}
return tabanDegerleri;
}
Don't use Convert.ToDouble then: use XmlConvert.ToDouble. I believe that effectively uses the invariant culture (as XML documents conveying data shouldn't be culture-specific in that sense).
EDIT: I hadn't noticed that you were manually replacing '.' with ',' - so when you say that "it is converting 0.05 as 5" you really mean "it is converting 0,05 as 5". You should use XmlConvert and stop messing with the data yourself.
The problem
Your are replacing . by , before converting. That means when you get 0.05 you convert it to 0,05. That conversion will behave according to your locale. In US, for instance, that is 5.
The Solution
Take a look at #JonSkeet's answer and use XmlConvert.ToDouble which is culture invariant (it uses XML standarts for data formatting). And, of course, drop the string replacements.
You should use the correct CultureInfo instead of replacing the dots with comas.
You can do this using this signature of the Convert.ToDouble method. Something like:
double uzunluk =Convert.ToDouble(holElement.GetElementsByTagName("uzunluk")[0].InnerText, CultureInfo.CurrentCulture);
If you still have a problem with dots and comas, it means that the culture of your Xml file is not coherent with the current culture (which is the culture of the machine executing the line of code), for example your Windows installation is set to have comas as decimal separators (in this case it seems CurrentCulture is the Turkish culture, and your xml has a different one, like US Culture). In this case, you have to call Convert using the actual culture of your xml, understanding where it has been generated.
If it has dots as decimal separators, then you can try getting the common invariant culture (CultureInfo.InvariantCulture), which indeed uses dots, or maybe be more specific. (see GetCultureInfo).
I notice you're doing some text replacement of . to ,.
Does this mean you are in a culture where there's a decimal comma rather than decimal point? In this case "0.05" is 5. If you wanted the fraction the string would have to be "0,05".
Does the double conversion work if you convert the value before changing the decimal point to a comma? I think you have a locale issue.

how to change values in string from 0,00 to 0.00

How can I change values in string from 0,00 to 0.00? - only numeric values, not all chars "," to "."
FROM
string myInputString = "<?xml version=\"1.0\"?>\n<List xmlns:Table=\"urn:www.navision.com/Formats/Table\"><Row><HostelMST>12,0000</HostelMST><PublicMST>0,0000</PublicMST><TaxiMST>0,0000</TaxiMST><ParkMST>0,0000</ParkMST><RoadMST>0,0000</RoadMST><FoodMST>0,0000</FoodMST><ErrorCode>0</ErrorCode><ErrorDescription></ErrorDescription></Row></List>\n";
TO
string myInputString = "<?xml version=\"1.0\"?>\n<List xmlns:Table=\"urn:www.navision.com/Formats/Table\"><Row><HostelMST>12.0000</HostelMST><PublicMST>0.0000</PublicMST><TaxiMST>0.0000</TaxiMST><ParkMST>0.0000</ParkMST><RoadMST>0.0000</RoadMST><FoodMST>0.0000</FoodMST><ErrorCode>0</ErrorCode><ErrorDescription></ErrorDescription></Row></List>\n";
Thanks for answers, but I mean to change only numeric values, not all chars "," to "."
I don't want change string from
string = "<Attrib>txt txt, txt</Attrib><Attrib1>12,1223</Attrib1>";
to
string = "<Attrib>txt txt. txt</Attrib><Attrib1>12.1223</Attrib1>";
but this one is ok
string = "<Attrib>txt txt, txt</Attrib><Attrib1>12.1223</Attrib1>";
Try this :
Regex.Replace("attrib1='12,34' attrib2='43,22'", "(\\d),(\\d)", "$1.$2")
output : attrib1='12.34' attrib2='43.22'
The best method depends on the context. Are you parsing the XML? Are you writing the XML. Either way it's all to do with culture.
If you are writing it then I am assuming your culture is set to something which uses commas as decimal seperators and you're not aware of that fact. Firstly go change your culture in Windows settings to something which better fits your culture and the way you do things. Secondly, if you were writing the numbers out for human display then I would leave it as culturally sensative so it will fit whoever is reading it. If it is to be parsed by another machine then you can use the Invariant Culture like so:
12.1223.ToString(CultureInfo.InvariantCulture);
If you are reading (which I assume is what you are doing) then you can use the culture info again. If it was from a human source (e.g. they typed it in a box) then again use their default culture info (default in float.Parse). If it is from a computer then use InvariantCulture again:
float f = float.Parse("12.1223", CultureInfo.InvariantCulture);
Of course, this assumes that the text was written with an invariant culutre. But as you're asking the question it's not (unless you have control over it being written, in which case use InvariantCulture to write it was suggested above). You can then use a specific culture which does understand commas to parse it:
NumberFormatInfo commaNumberFormatInfo = new NumberFormatInfo();
commaNumberFormatInfo.NumberDecimalSeperator = ",";
float f = float.Parse("12,1223", commaNumberFormatInfo);
I strongly recommend joel.neely's regex approach or the one below:
Use XmlReader to read all nodes
Use double.TryParse with the formatter = a NumberFormatInfo that uses a comma as decimal separator, to identify numbers
Use XmlWriter to write a new XML
Use CultureInfo.InvariantCulture to write the numbers on that XML
The answer from ScarletGarden is a start, but you'll need to know the complete context and grammar of "numeric values" in your data.
The problem with the short answer is that cases such as this get modified:
<elem1>quantity<elem2>12,6 of which were broken</elem2></elem1>
Yes, there's probably a typo (missing space after the comma) but human-entered data often has such errors.
If you include more context, you're likely to reduce the false positives. A pattern like
([\s>]-?$?\d+),(\d+[\s<])
(which you can escape to taste for your programming language of choice) would only match when the "digits-comma-digits" portion (with optional sign and currency symbol) was bounded by space or an end of an element. If all of your numeric values are isolated within XML elements, then you'll have an easier time.
string newStr = myInputString.Replace("0,00", "0.00");
While you could theoretically do this using a Regex, the pattern would be complex and hard to to test. ICR is on the right track, you need to do this based on culture.
Do you know that your numbers are always going to be using a comma as a decimal separator instead of a period? It looks like you can, given that Navision is a Danish company.
If so, you'll need to traverse the XML document in the string, and rewrite the numeric values. It appears you can determine this on node name, so this won't be an issue.
When you convert the number, use something similar to this:
here's what you want to do:
internal double ConvertNavisionNumber(string rawValue)
{
double result = 0;
if (double.TryParse(rawValue, NumberStyles.Number, new CultureInfo("da-DK").NumberFormat, out result))
return result;
else
return 0;
}
This tells the TryParse() method that you're converting a number from Danish (da-DK). Once you call the function, you can use ToString() to write the number out in your local format (which I'm assuming is US or Canadian) to get a period for your decimal separator. This will also take into account numbers with different thousands digit separator (1,234.56 in Canada is written as 1 234,56 in Denmark).
ConvertNavisionNumber("4,43").ToString()
will result in "4.43".
ConvertNavisionNumber("1 234").ToString()
will result in "1,234".
if the , is not used anywhere else but number with in the string you can use the following:
string newStr = myInputString.Replace(",", ".");

Categories