I want to obtain a total of three values:
when none of the values is null, the total calculates successfully but when one or more of the values is null,
it returns an error:
"input string was not in correct format"
Below is the code:
double TotalVar = double.Parse(ExScore.Tables[0].Rows[i]["CAT1"].ToString()) +
double.Parse(ExScore.Tables[0].Rows[i]["CAT2"].ToString()) +
double.Parse(ExScore.Tables[0].Rows[i]["CAT3"].ToString()) +
double.Parse(ExScore.Tables[0].Rows[i]["EXAM"].ToString());
Use local function like this to safely parse data :
double Parse(string data)
{
return double.TryParse(data, NumberStyles.AllowDecimalPoint,
CultureInfo.InvariantCulture, out double val) ? val : 0;
}
if(ExScore == null || ExScore.Tables[0] == null ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["CAT1"]) ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["CAT2"]) ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["CAT3"]) ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["EXAM"])
)
{
//throw exception
//set value by default
}
//calcul normally
public bool IsNullOrEmpty(object value)
{
return value == null || string.IsNullOrEmpty(value.ToString());
}
Create a console app and test the app below. Everything you need is in the code below. Note: It is bad to direct cast strings without a minimal error handling, also you can/will have problems with "." or "," decimal separators in these casts. - Different regions use different decimal separators, and this can impact the quality of your results.
class Program
{
static void Main(string[] args)
{
//SumValues(
//ExScore.Tables[0].Rows[i]["CAT2"].ToString(),
//ExScore.Tables[0].Rows[i]["CAT3"].ToString(),
//ExScore.Tables[0].Rows[i]["EXAM"].ToString()
//);
Console.WriteLine(SumValues("100,32", "100,08", "100,10",null));
Console.WriteLine(SumValues("abcsdf", "ba123", "100,10", null));
Console.WriteLine(SumValues("100,32", "100,08", "100,10", null));
Console.ReadLine();
}
//simple function that will try to sum any number of strings you pass
public static double SumValues(params string[] values)
{
double sum=0;
foreach (var item in values)
{
//if the string is null, empty or white space, proceed to the next iteration in the loop
if (string.IsNullOrWhiteSpace(item))
continue;
if (IsNumeric(item))
sum += double.Parse(item);
}
return sum;
}
//Minimal 'error handling' function to avoid exceptions parsing strings
public static bool IsNumeric(string s)
{
double output;
return double.TryParse(s, out output);
}
}
Related
Please let me know good way to validate decimal value, if decimal(4,2) it should accept 2 numeric and 2 decimal places.
var value = "44.29";
var dec = value.Split('.');
Then finding the length will can be used, I need a better culture specific way. I need a generic solution which can be applied to all decimal field.
Like:
validate(int before,int afterdecimal);
var valid = validate(2,2);
Need a generic cleaner solution for this
private static bool IsDecimal(string value, int before, int after)
{
if (value.Contains("."))
{
var parts = value.Split('.');
if (parts[0].Length == before && parts[1].Length == after)
return true;
}
else if(value.Length == before)
return false;
return true;
}
You can try like this:
[RegularExpression(#"^\d{1,2}(\.\d{0,2})$",ErrorMessage = "Value contains more than 2 decimal places")]
public decimal Value { get; set; }
If you whant just validate, try to use the mod:
44.29 % 1 = 0.29
From the above answers I was able to do it like this
string value = "2009.99";
if (IsDecimal(value, 4, 4))
{
Console.WriteLine("Valid");
}
private static bool IsDecimal(string value, int before, int after)
{
var r = new Regex(#"^\d{1," + before + #"}(\.\d{0," + after + #"})$");
return r.IsMatch(value);
}
I'm having an issue retrieving the exact value from an cell from a worksheet. If I open the file the cell has a decimal number which it's shown only with 4 decimals, but if I click on the particular cell, the value is different, having 6 decimals. I know it's a setting applied to the cell in order to show only 4 decimals.
Now I'm trying to retrieve the cell's data in C#, using ClosedXML.Excel, not Microsoft.Office.Interop.Excel, but the only thing I'm able to get is the 4 decimals value, not the "whole" one, which is a great issue as I have calculation later on and I have big differences due to the missing 2 decimals.
I have tried using inputSheet.Worksheet.Cell(row,col).GetDouble(), or Convert.ToDouble(inputSheet.Worksheet.Cell(row, col).Value), or even to refer to the "RichText" property, or ...ToString("0.000000"), but no matter what I use, I can only retrieve the 4 decimal value instead of the full 6 decimals one.
I got a look at the source code of the library, here is what the Value getter does:
get
{
string str = this.FormulaA1;
if (!XLHelper.IsNullOrWhiteSpace(str))
{
string str2;
string sName;
if (str[0] == '{')
{
str = str.Substring(1, str.Length - 2);
}
if (str.Contains<char>('!'))
{
sName = str.Substring(0, str.IndexOf('!'));
if (sName[0] == '\'')
{
sName = sName.Substring(1, sName.Length - 2);
}
str2 = str.Substring(str.IndexOf('!') + 1);
}
else
{
sName = this.Worksheet.Name;
str2 = str;
}
if (this._worksheet.Workbook.WorksheetsInternal.Any<XLWorksheet>(w => (string.Compare(w.Name, sName, true) == 0)) && XLHelper.IsValidA1Address(str2))
{
return this._worksheet.Workbook.Worksheet(sName).Cell(str2).Value;
}
object obj2 = this.Worksheet.Evaluate(str);
IEnumerable enumerable = obj2 as IEnumerable;
if ((enumerable != null) && !(obj2 is string))
{
using (IEnumerator enumerator = enumerable.GetEnumerator())
{
while (enumerator.MoveNext())
{
return enumerator.Current;
}
}
}
return obj2;
}
string s = this.HasRichText ? this._richText.ToString() : this._cellValue;
if (this._dataType == XLCellValues.Boolean)
{
return (s != "0");
}
if (this._dataType == XLCellValues.DateTime)
{
return DateTime.FromOADate(double.Parse(s));
}
if (this._dataType == XLCellValues.Number)
{
return double.Parse(s);
}
if (this._dataType == XLCellValues.TimeSpan)
{
return TimeSpan.Parse(s);
}
return s;
}
I noticed, that when it reach string s = this.HasRichText ? this._richText.ToString() : this._cellValue;, if you have never called cell.RichText (so neither viewing it with the debugger) you get the correct value, because this.HasRichText is false because the class has still not assigned the correct value to it.
When it(this.HasRichText) is true, you get this._richText.ToString(), that is the formatted number.
So, if you access the Value property before accessing RichText you should get the correct value, anyway you can get _cellValue using reflection, then convert it to double like this:
var theRealDoubleValue = Convert.ToDouble(yourCell.GetType().GetField("_cellValue", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(yourCell));
I have a class(found on the internet, i have made some few modifications) that can export data to a .csv file. This works fine.
But when i open the file, numbers containing decimal values are displayed wrongly.
For example: 173,8543526 will be displayed something like: 17.385.435.26 which is due to the decimal separator not being correct when exporting to csv.
The export class can be seen below:
public class CsvExport
{
List<string> fields = new List<string>();
List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();
Dictionary<string, object> currentRow { get { return rows[rows.Count - 1]; } }
public object this[string field]
{
set
{
// Keep track of the field names, because the dictionary loses the ordering
if (!fields.Contains(field)) fields.Add(field);
currentRow[field] = value;
}
}
public void AddRow()
{
rows.Add(new Dictionary<string, object>());
}
public void RemoveLastRow()
{
rows.RemoveAt(rows.Count - 1);
}
string MakeValueCsvFriendly(object value)
{
if (value == null) return "";
if (value is INullable && ((INullable)value).IsNull) return "";
if (value is DateTime)
{
if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
return ((DateTime)value).ToString("yyyy-MM-dd");
return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
}
string output = value.ToString();
if (output.Contains(",") || output.Contains("\""))
output = '"' + output.Replace("\"", "\"\"") + '"';
return output;
}
public string Export(bool omitHeaders)
{
StringBuilder sb = new StringBuilder();
// The header
if (!omitHeaders)
{
foreach (string field in fields)
sb.Append(field).Append(";");
sb.AppendLine();
}
// The rows
foreach (Dictionary<string, object> row in rows)
{
foreach (string field in fields)
{
if (row.ContainsKey(field))
{
sb.Append(MakeValueCsvFriendly(row[field])).Append(";");
}
else
{
sb.Append(";");
}
}
sb.AppendLine();
}
return sb.ToString();
}
public string ExportToFileDialog(string defaultFileName)
{
string filename = String.Empty;
var dlg = new Microsoft.Win32.SaveFileDialog
{
FileName = !String.IsNullOrEmpty(defaultFileName) ? defaultFileName : String.Empty,
DefaultExt = ".csv",
Filter = "CSV (Comma delimited)|*.csv"
};
// Show save file dialog box
var result = dlg.ShowDialog();
// Process save file dialog box results
if (result == true)
{
// Save document
filename = dlg.FileName;
}
return filename;
}
public void ExportToFile(string path, bool append = false, bool omitHeaders = false)
{
try
{
if (append)
File.AppendAllText(path, Export(omitHeaders), Encoding.UTF8);
else
File.WriteAllText(path, Export(omitHeaders), Encoding.UTF8);
}
catch (Exception exc)
{
}
}
So my question is, how can I in this class define that "," should be decimal separator and "." should be thousands separator?
For this you will probably need to force the formatting to use different thousands separators and decimal places.
You can use a custom number formatter with the NumberFormatInfo type. First, place a class variable in the class:
NumberFormatInfo customNumFormat;
Then define a constructor as follows:
public CsvExport()
{
customNumFormat = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
customNumFormat.NumberGroupSeparator = ".";
customNumFormat.NumberDecimalSeparator = ",";
}
This will define a number formatter with the separators you require. This allows you to control the separators and decimal place marker to use whatever character you wish.
To use this, you will need to format the numbers yourself, so add in another if statement in your MakeValueCsvFriendly method as follows:
if (value is decimal || value is double || value is int)
return string.Format(customNumFormat, "{0:N}", value);
If you have other number types in your dictionary you will need to add these types to the if statement accordingly.
Hope this helps.
I tried to add logic to MakeValueCsvFriendly:
string MakeValueCsvFriendly(object value)
{
if (value == null) return "";
if (value is INullable && ((INullable)value).IsNull) return "";
if (value is DateTime)
{
if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
return ((DateTime)value).ToString("yyyy-MM-dd");
return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
}
else if(value is int || value is double || value is float || value is decimal || value is long || value is uint || value is ulong || value is ushort)
{
// Output numbers
// Specific culture: da-DK
// Specific format: N (thousand separator + 2 decimals)
// 123456789098.7654321 => 123.456.789.098,77
return value.ToString("N", new CultureInfo("da-DK"));
}
else
{
string output = value.ToString();
if (output.Contains(",") || output.Contains("\""))
output = '"' + output.Replace("\"", "\"\"") + '"';
return output;
}
}
See more Numeric formats here:
Standard: http://msdn.microsoft.com/en-us/library/dwhawy9k(v=vs.110).aspx
Custom: http://msdn.microsoft.com/en-us/library/0c899ak8(v=vs.110).aspx
Instead of string output = value.ToString();, use:
string output;
if (value is Decimal)
output = value.ToString("N");
else
output = value.ToString();
The problem here is in the interpretation, not the output.
There is nothing wrong with writing a decimal to CSV as 173,8543526 as long as your thousands separator is set correctly (along with your field separator).
In Excel (2010), if you click on Data > From Text and follow the Text Import Wizard to Step 3, you can click on Advanced to choose the decimal and thousands separator for each column.
Note that CSV isn't a formal standard (while there are some de facto rules), so you can choose whatever separators you like, as long as you can interpret them correctly.
I am trying to convert some particular types of strings into double in c#. Normally, Convert.ToDouble() works all great but this one does not always return healthy strings. Which means that input does not always come in format of "4.2". SOmetimes it also comes in form of 4.4.2. Now, i can also not rely on positioning and truncating since it can be 10.11.445 tomorrow?
Any easy and short string manipulation function that I can apply on this scenario?
struct CalifornicatedNumber
{
private string value;
public CalifornicatedNumber(string value)
{
this.value = value;
}
static public implicit operator CalifornicatedNumber(string value)
{
return new CalifornicatedNumber(value);
}
static public implicit operator CalifornicatedNumber(double value)
{
return new CalifornicatedNumber(value.ToString());
}
static public implicit operator double(CalifornicatedNumber calif)
{
return double.Parse(MakeItMakeSense(calif.value));
}
static private string MakeItMakeSense(string calif)
{
if (calif.Count(x => x == '.') > 1)
calif = calif.Substring(0, calif.IndexOf('.', calif.IndexOf('.') + 1));
return calif;
}
}
then...
CalifornicatedNumber califnum;
califnum = "10.11.145";
Console.WriteLine(califnum);
if (califnum > 10) { Console.WriteLine("huzzah");}
califnum = 13.42;
Console.WriteLine(califnum);
if (califnum > 10) { Console.WriteLine("huzzahZah"); }
...this is the most foolish piece of code I have ever written.
After the posted comments I am assuming you would like to take the string 4.4.2 and convert it to double dropping everything after the second . (if found).
A method such as.
public static double ConvertStringToDouble(string inputString)
{
if (inputString.Count(x => x == '.') > 1)
inputString = inputString.Substring(0, inputString.IndexOf('.', inputString.IndexOf('.') + 1));
return double.Parse(inputString);
}
I would do an approach of creating a bunch of strategies for parsing the input text and then iterating through the strategies until a result is found.
First I'd define a tryParseDouble function:
Func<string, double?> tryParseDouble = t =>
{
double value;
if (double.TryParse(t, out value))
{
return value;
}
return null;
};
Then I'd create my list of strategies:
var strategies = new Func<string, double?>[]
{
t =>
{
var parts = t.Split('.');
return parts.Length > 1
? tryParseDouble(String.Join(".", parts.Take(2)))
: null;
},
tryParseDouble,
t => null,
};
And finally I'd get the result:
var text = "4.4.2";
var result =
strategies
.Select(p => p(text))
.Where(v => v != null)
.FirstOrDefault();
The result variable is a double? with the value parsed or a null if none of the strategies work. The final strategy, t => null, is there to be explicit but is not necessary in getting the final null result.
As new strategies are needed to parse different types of input text they can just be added to the list as needed.
I think this will do what you want according to your comments by parsing either the whole string (if no decimal points) or just the first two parts of the sting if there are multiple decimals.
String[] parts = stringVal.Split('.');
double doubleVal;
if (parts.length > 1)
{
doubleVal = Convert.ToDouble(parts[0] + "." + parts[1]);
}
else
{
doubleVale = Convert.ToDouble(stingVal);
}
So, I'm importing data from one database into another. There are about 5,000 records (so nothing ridiculous, but not small enough to eyeball). Is there an easy way to automatically truncate data that is too long - specifically, varchar fields? I don't want truncation to be silent since the too-long fields will likely require attention, but it would be really nice if a name that is 2 characters too long wouldn't fail on insert, and throw a totally non-specific exception.
The solution I'd like to implement is one that will truncate the data, insert it, and log this. Has anyone else done something similar?
Linq2Sql will generate a property like this:
[Column(Storage="_Name", DbType="NVarChar(50) NOT NULL")]
public string Name
{
get
{
return this._Name;
}
set
{
if ((this._Name != value))
{
this.OnNameChanging(value);
this.SendPropertyChanging();
this._Name = value;
this.SendPropertyChanged("Name");
this.OnNameChanged();
}
}
}
See how it calls a function called OnNameChanged? Just create a function with that name to do the truncation and logging:
void OnNameChanged()
{
if (Name.Length > 50)
{
Name = Name.Substring(0, 50);
LogSomehow("Name truncated");
}
}
I would make an extension iterator method that you could throw inline with your query, doing something like this:
public static IEnumerable<string> Truncater(this IEnumerable<string> s, int len)
{
foreach( var str in s )
{
if( str.Length > len )
{
string outStr = str.Substring(0, len);
Console.WriteLine(String.Format("Truncated {0} to {1}", str, outStr));
yield return outStr;
}
else
yield return str;
}
}
Here's a more automatic method. It looks at the type/length of each column being inserted, on the fly.
Use it like this:
foreach (object insert in context.GetChangeSet().Inserts)
{
FindLongStrings(update);
}
context.SubmitChanges();
And here's the method: (It's horribly inefficient so I wouldn't leave it in production code, but if you've got a one-time conversion/import (and it sound like you do), it might do the trick.)
public static void FindLongStrings(object testObject)
{
foreach (PropertyInfo propInfo in testObject.GetType().GetProperties())
{
foreach (ColumnAttribute attribute in propInfo.GetCustomAttributes(typeof(ColumnAttribute), true))
{
if (attribute.DbType.ToLower().Contains("varchar"))
{
// kinda crude, but it works...
string dbType = attribute.DbType.ToLower();
int numberStartIndex = dbType.IndexOf("varchar(") + 8;
int numberEndIndex = dbType.IndexOf(")", numberStartIndex);
string lengthString = dbType.Substring(numberStartIndex, (numberEndIndex - numberStartIndex));
int maxLength = 0;
int.TryParse(lengthString, out maxLength);
string currentValue = (string)propInfo.GetValue(testObject, null);
// Do your logging and truncation here
if (!string.IsNullOrEmpty(currentValue) && currentValue.Length > maxLength)
Console.WriteLine(testObject.GetType().Name + "." + propInfo.Name + " " + currentValue + " Max: " + maxLength);
}
}
}
}