Efficient way to convert only specific values - c#

So lets say I have an array of strings:
string[] values = new[] { "1", "2", "3", "1.5", "56.5", "8" };
Lets say I have to loop through these values and do the following operations:
Round it to the nearest even number (if it's a double only)
After rounding remove the fractional part of the number.
If the number is negative remove the sign.
string[] values = new[] { "1", "2", "3", "1.5", "56.5", "8" };
for (int i = 0; i < values.Length; i++)
{
file[i].Value = ((Int32)Math.Abs(Math.Round(Double.Parse(values[i]), MidpointRounding.ToEven))).ToString();
}
which is basically the same as doing this:
string[] values = new[] { "1", "2", "3", "1.5", "56.5", "8" };
for (int i = 0; i < values.Length; i++)
{
String strValue = values[j];
Double dblValue = Double.Parse(strValue);
Double dblRoundedValue = Double.Parse(dblValue);
Int32 intValue = (Int32)dblRoundedValue;
Int32 intAbsValue = Math.Abs(intValue);
String finalValue = intAbsValue.ToString();
file[i].Value = finalValue;
}
There could be over a million values in that array so is there a way to make this process more efficient?

This operation is inherently parallelizable (if that's a word). A Parallel.ForEach loop, parallel Linq pipeline, or something similar, would improve execution time.
string[] values = new[] { "1", "2", "3", "1.5", "56.5", "8" };
var file = values.AsParallel()
.Select(s => Double.Parse(s))
.Select(d => (int)Math.Round(d))
.Select(i => Math.Abs(i).ToString())
.ToArray();

Well, I tried some things, and using a combination of older-than-the-supernova's suggestion of checking for a decimal point in the string before considering if it was necessary to parse it to a Double, and Andrew Cooper's suggestion of using Parallel.For I got results of
Init...Done
Simple
20024
LookingAtString
8082
ParallelConvertLookingAtString
3559
Simple
19552
LookingAtString
7985
ParallelConvertLookingAtString
3595
with the following code...
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
const int nNumbers = 20000000;
static string[] values = new string[nNumbers];
static string[] file = new string[nNumbers];
static Random rand = new Random();
// Create some sample data.
static void Init()
{
string sgn = "";
for (int i = 0; i <= nNumbers - 1; i++)
{
sgn = rand.Next(51) == 1 ? "-" : "";
if (rand.Next(4) == 1)
{
values[i] = sgn + (rand.NextDouble() * 100).ToString();
}
else
{
values[i] = sgn + rand.Next(100);
}
}
}
static void ConvertSimple()
{
for (int i = 0; i <= nNumbers - 1; i++)
{
file[i] = Math.Abs(Math.Round(double.Parse(values[i]), MidpointRounding.ToEven)).ToString();
}
}
static void ConvertLookingAtString()
{
for (int i = 0; i <= nNumbers - 1; i++)
{
if (values[i].IndexOf('.') >= 0)
{
file[i] = Math.Abs(Math.Round(double.Parse(values[i]), MidpointRounding.ToEven)).ToString();
}
else
{
file[i] = values[i].TrimStart('-');
}
}
}
static void ParallelConvertLookingAtString()
{
Parallel.For(0, nNumbers, i =>
{
if (values[i].IndexOf('.') >= 0)
{
file[i] = Math.Abs(Math.Round(double.Parse(values[i]), MidpointRounding.ToEven)).ToString();
}
else
{
file[i] = values[i].TrimStart('-');
}
});
}
static void Main()
{
Console.Write("Init...");
Init();
Console.WriteLine("Done");
Stopwatch sw = new Stopwatch();
// run each test twice
for (int testNum = 0; testNum < 2; testNum++)
{
sw.Reset();
sw.Start();
ConvertSimple();
sw.Stop();
Console.WriteLine("Simple\n" + sw.ElapsedMilliseconds.ToString());
sw.Reset();
sw.Start();
ConvertLookingAtString();
sw.Stop();
Console.WriteLine("LookingAtString\n" + sw.ElapsedMilliseconds.ToString());
sw.Reset();
sw.Start();
ParallelConvertLookingAtString();
sw.Stop();
Console.WriteLine("ParallelConvertLookingAtString\n" + sw.ElapsedMilliseconds.ToString());
}
Console.ReadLine();
}
}
}
Please note that that is using twenty million samples rather than the one million-ish you suggest, and about one third of the values are fractional, and about two percent are negative. You didn't give the expected fraction of those, so I made something up.
Some bits may be non-optimal C# because I converted it from VB. Run on an Intel Core i7 920 with 6GB of RAM, compiled to run on x64.
Edit: Oh yeah, so my answer is the above ParallelConvertLookingAtString method.

A couple of ideas:
If you have a million values and you expect a lot of repeats then you can build up a Hashtable of previous results that you can reuse.
I tested this and it is much faster if you have say all the elements the same. It is probably faster with a small number of distinct elements. However if the elements are all different it uses a lot of memory and you would want to cap it once the Hash gets too big.
If you are sure of the formatting locale, then you can do this:
A string is a character array. Instead of converting to double, iterate though the characters until you reach the first "." then take the next digit. Convert the text (ignoring any -) before the . to an integer. If the value after the . is 5,6,7,8,9 then add 1 to that integer.
Example:
Input: "-1002.55"
Text between - and . is: "1002"
Converted to int this is: 1002
Character immediately after . is: "5"
Result: 1002 + 1 = 1003
That is for round to nearest. If you need to round to nearest even number look at digit immediately before . and if there is one after, and the digit before . is 1,3,5,7,9 then add to the final number.

Double dblValue;
Double dblRoundedValue;
Int32 intValue;
Int32 intAbsValue;
String finalValue;
for (int i = 0; i < values.Length; i++)
{
strValue = values[j];
if (!Int32.TryParse(strValue, out intValue))
{
//dblValue = Double.Parse(strValue);
//dblRoundedValue = Double.Parse(dblValue);
//intValue = (Int32)dblRoundedValue;
intValue = (Int32)(Double.Parse(strValue));
}
//intValue = Math.Abs(intValue);
//finalValue = intValue .ToString();
file[i].Value = (Math.Abs(intValue)).ToString();
}
But I don't understand the two Double.Parse.
Put em back in if you need them
And convert this to a parallel
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 16;
Parallel.For(0, values.Length, parallelOptions, i =>
{
strValue = values[j];
if (!Int32.TryParse(strValue, out intValue))
{
intValue = (Int32)(Double.Parse(strValue));
}
if (intValue < 0) intValue = -intValue;
file[i].Value = intValue.ToString();
});
I think grunge will be the fastest
string[] values = new string[] { "1", "2", "3", "1.5", "56.5", "8" };
string[] files = new string[values.Length];
HashSet<char> lt5 = new HashSet<char> {'0','1','2','3','4'};
bool haveDecimal;
bool haveDecimalConfirmed;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < values.Length; i++)
{
sb.Clear();
haveDecimal = false;
haveDecimalConfirmed = false;
foreach(char c in values[i])
{
if (haveDecimal)
{
if (lt5.Contains(c))
{
files[i] = sb.ToString();
}
else
{
files[i] = (Int32.Parse(sb.ToString()) + 1).ToString();
}
haveDecimalConfirmed = true;
break;
}
else if (c == '.')
{
haveDecimal = true;
continue;
}
if (c == '-') continue;
sb.Append(c);
}
if (!haveDecimalConfirmed) files[i] = sb.ToString();
}

Assuming you mean "even number" to mean "nearest integer" and not "integer multiple of 2".
Assuming you mean to remove negative sign from all numbers as opposed to only numbers you have rounded.
You assume that your input strings are always valid Double format, is that reasonable? Assuming there are no numbers formatted with scientific notation, examine each input string for a decimal point, its faster than parsing and a test for a fractional part. No decimal point, no rounding needed.

Related

Thousands separator after the decimal point [duplicate]

I wonder what would be the best way to format numbers so that the NumberGroupSeparator would work not only on the integer part to the left of the comma, but also on the fractional part, on the right of the comma.
Math.PI.ToString("###,###,##0.0##,###,###,###") // As documented ..
// ..this doesn't work
3.14159265358979 // result
3.141,592,653,589,79 // desired result
As documented on MSDN the NumberGroupSeparator works only to the left of the comma. I wonder why??
A little clunky, and it won't work for scientific numbers but here is a try:
class Program
{
static void Main(string[] args)
{
var π=Math.PI*10000;
Debug.WriteLine(Display(π));
// 31,415.926,535,897,931,899
}
static string Display(double x)
{
int s=Math.Sign(x);
x=Math.Abs(x);
StringBuilder text=new StringBuilder();
var y=Math.Truncate(x);
text.Append((s*y).ToString("#,#"));
x-=y;
if (x>0)
{
// 15 decimal places is max reasonable precision
y=Math.Truncate(x*Math.Pow(10, 15));
text.Append(".");
text.Append(y.ToString("#,#").TrimEnd('0'));
}
return text.ToString();
}
}
It might be best to work with the string generated by your .ToString():
class Program
{
static string InsertSeparators(string s)
{
string decSeparator = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;
int separatorPos = s.IndexOf(decSeparator);
if (separatorPos >= 0)
{
string decPart = s.Substring(separatorPos + decSeparator.Length);
// split the string into parts of 3 or less characters
List<String> parts = new List<String>();
for (int i = 0; i < decPart.Length; i += 3)
{
string part = "";
for (int j = 0; (j < 3) && (i + j < decPart.Length); j++)
{
part += decPart[i + j];
}
parts.Add(part);
}
string groupSeparator = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberGroupSeparator;
s = s.Substring(0, separatorPos) + decSeparator + String.Join(groupSeparator, parts);
}
return s;
}
static void Main(string[] args)
{
for (int n = 0; n < 15; n++)
{
string s = Math.PI.ToString("0." + new string('#', n));
Console.WriteLine(InsertSeparators(s));
}
Console.ReadLine();
}
}
Outputs:
3
3.1
3.14
3.142
3.141,6
3.141,59
3.141,593
3.141,592,7
3.141,592,65
3.141,592,654
3.141,592,653,6
3.141,592,653,59
3.141,592,653,59
3.141,592,653,589,8
3.141,592,653,589,79
OK, not my strong side, but I guess this may be my best bet:
string input = Math.PI.ToString();
string decSeparator = System.Threading.Thread.CurrentThread
.CurrentCulture.NumberFormat.NumberGroupSeparator;
Regex RX = new Regex(#"([0-9]{3})");
string result = RX.Replace(input , #"$1" + decSeparator);
Thanks for listening..

how to Tryparse Numbers from Text file like ascii art with c#?

how can i get the numbers that are coded in a ascii art with sticks?
the numberss are in a txt file und it contains this:
I must get the nubmers that are drown with stick.
the first step is to get 4 line and than to controll it with an Alphabet.
i get the text in a string[]
string[] lines = File.ReadAllLines("SourceFile.txt");
the first 4 line are up to lines[3].
how can i controll the different line in the same position?
it is like a 2d Array or i must do something else?
First of all you need an object that stores the pattern and metrics of a symbol(number in your case). Also this object has a method for recognizing its pattern in the given array:
public class AsciiNumber
{
private readonly char[][] _data;
public AsciiNumber(char character, char[][] data)
{
this._data = data;
this.Character = character;
}
public char Character
{
get;
private set;
}
public int Width
{
get
{
return this._data[0].Length;
}
}
public int Height
{
get
{
return this._data.Length;
}
}
public bool Match(string[] source, int startRow, int startColumn)
{
if (startRow + this.Height > source.Length)
{
return false;
}
for (var i = startRow; i < startRow + this.Height; i++)
{
var row = source[i];
if (startColumn + this.Width > row.Length)
{
return false;
}
for (var j = startColumn; j < startColumn + this.Width; j++)
{
if (this._data[i - startRow][j - startColumn] != row[j])
{
return false;
}
}
}
return true;
}
}
Then you may create something like alphabet you deal with (I used only numbers 1 and 3):
public static class Alphabet
{
private static readonly AsciiNumber Number1 = new AsciiNumber('1', new[]{
new []{'|'},
new []{'|'},
new []{'|'},
new []{'|'},
});
private static readonly AsciiNumber Number3 = new AsciiNumber('3', new[]{
new []{'-','-','-'},
new []{' ',' ','/'},
new []{' ',' ','\\'},
new []{'-','-','-'},
});
public static readonly IEnumerable<AsciiNumber> All = new[] { Number1, Number3 };
}
Assuming that numbers in your source file have permament and equal height you may try code like this:
string[] lines = File.ReadAllLines("SourceFile.txt");
var lineHeight = 4;
var text = new StringBuilder();
for (var i = 0; i < lines.Length; i += lineHeight)
{
var j = 0;
while (j < lines[i].Length)
{
var match = Alphabet.All.FirstOrDefault(character => character.Match(lines, i, j));
if (match != null)
{
text.Append(match.Character);
j += match.Width;
}
else
{
j++;
}
}
}
Console.WriteLine("Recognized numbers: {0}", text.ToString());
N.B. If line height changes over the file you have to improve the code above.
Let's suppose we have this text and we want to parse the numbers in it:
--- --- | | | -----
/ _| | |__| |___
\ | | | |
-- --- | | ____|
First of all, we should remove any unnecessary white-space (or tab, if present) and put a separator char (e.g. $) between the numbers, obtaining something similar to this:
$---$---$|$| |$-----$
$ / $ _|$|$|__|$|___ $
$ \ $| $|$ |$ |$
$-- $---$|$ |$____|$
Now we should call the Split function on each line of the text, using $ as separator, and then compare the results with an alphabet.
Let's see how to do this with code. Given the text to parse string[] lines, we'll create an extension method to remove unnecessary white-spaces and put a separator char/string instead:
public static class StringHelperClass
{
// Extension method to remove any unnecessary white-space and put a separator char instead.
public static string[] ReplaceSpacesWithSeparator(this string[] text, string separator)
{
// Create an array of StringBuilder, one for every line in the text.
StringBuilder[] stringBuilders = new StringBuilder[text.Length];
// Initialize stringBuilders.
for (int n = 0; n < text.Length; n++)
stringBuilders[n] = new StringBuilder().Append(separator);
// Get shortest line in the text, in order to avoid Out Of Range Exception.
int shorterstLine = text.Min(line => line.Length);
// Temporary variables.
int lastSeparatorIndex = 0;
bool previousCharWasSpace = false;
// Start processing the text, char after char.
for (int n = 0; n < shorterstLine; ++n)
{
// Look for white-spaces on the same position on
// all the lines of the text.
if (text.All(line => line[n] == ' '))
{
// Go to next char if previous char was also a white-space,
// or if this is the first white-space char of the text.
if (previousCharWasSpace || n == 0)
{
previousCharWasSpace = true;
lastSeparatorIndex = n + 1;
continue;
}
previousCharWasSpace = true;
// Append non white-space chars to the StringBuilder
// of each line, for later use.
for (int i = lastSeparatorIndex; i < n; ++i)
{
for (int j = 0; j < text.Length; j++)
stringBuilders[j].Append(text[j][i]);
}
// Append separator char.
for (int j = 0; j < text.Length; j++)
stringBuilders[j].Append(separator);
lastSeparatorIndex = n + 1;
}
else
previousCharWasSpace = false;
}
for (int j = 0; j < text.Length; j++)
text[j] = stringBuilders[j].ToString();
// Return formatted text.
return text;
}
}
Then, in the Main method, we'll use:
lines = lines.ReplaceSpacesWithSeparator("$");
and
ASCIINumbersParser parser = new ASCIINumbersParser(lines, "$");
where ASCIINumbersParser is this class:
public class ASCIINumbersParser
{
// Will store a list of all the possible numbers
// found in the text.
public List<string[]> CandidatesList { get; }
public ASCIINumbersParser(string[] text, string separator)
{
CandidatesList = new List<string[]>();
string[][] candidates = new string[text.Length][];
for (int n = 0; n < text.Length; ++n)
{
// Split each line in the text, using the separator char/string.
candidates[n] =
text[n].Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
}
// Put the strings in such a way that each CandidateList item
// contains only one possible number found in the text.
for (int i = 0; i < candidates[0].Length; ++i)
CandidatesList.Add(candidates.Select(c => c[i]).ToArray());
}
}
At this point, we will use the following class to generate an ASCII art representation of some numbers and to compare the result with our original string (in Main):
public static class ASCIINumberHelper
{
// Get an ASCII art representation of a number.
public static string[] GetASCIIRepresentationForNumber(int number)
{
switch (number)
{
case 1:
return new[]
{
"|",
"|",
"|",
"|"
};
case 2:
return new[]
{
"---",
" _|",
"| ",
"---"
};
case 3:
return new[]
{
"---",
" / ",
#" \ ",
"-- "
};
case 4:
return new[]
{
"| |",
"|__|",
" |",
" |"
};
case 5:
return new[]
{
"-----",
"|___ ",
" |",
"____|"
};
default:
return null;
}
}
// See if two numbers represented as ASCII art are equal.
public static bool ASCIIRepresentationMatch(string[] number1, string[] number2)
{
// Return false if the any of the two numbers is null
// or their lenght is different.
// if (number1 == null || number2 == null)
// return false;
// if (number1.Length != number2.Length)
// return false;
if (number1?.Length != number2?.Length)
return false;
try
{
for (int n = 0; n < number1.Length; ++n)
{
if (number1[n].CompareTo(number2[n]) != 0)
return false;
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex);
return false;
}
return true;
}
}
In the end, the Main function will look like this:
static void Main()
{
string ASCIIString = #"
--- --- | | | -----
/ _| | |__| |___
\ | | | |
-- --- | | ____| ";
string[] lines =
ASCIIString.Split(new[] {"\n","\r\n"}, StringSplitOptions.RemoveEmptyEntries);
lines = lines.ReplaceSpacesWithSeparator("$");
ASCIINumbersParser parser = new ASCIINumbersParser(lines, "$");
// Try to find all numbers contained in the ASCII string
foreach (string[] candidate in parser.CandidatesList)
{
for (int i = 1; i < 10; ++i)
{
string[] num = ASCIINumberHelper.GetASCIIRepresentationForNumber(i);
if (ASCIINumberHelper.ASCIIRepresentationMatch(num, candidate))
Console.WriteLine("Number {0} was found in the string.", i);
}
}
}
// Expected output:
// Number 3 was found in the string.
// Number 2 was found in the string.
// Number 1 was found in the string.
// Number 4 was found in the string.
// Number 5 was found in the string.
Here you can find the full code.
I created all the models and saved them in the Dictionary.
I will create the directories for development
C:\temp\Validate C:\temp\Referenz C:\temp\Extract
In Dir Reference, a file will be created for each Model in the Dictionary.
I read all the lines, and save them in a string [].
Encoding I will do it personally from string [] to char [] []. After this operation we will have in Dir Validate the MyEncoding.txt file, containing the New Number List.
From the previous list all "Char.Empty" (\ t) were converted to char null '\ 0'.
As long as I go through the list from left to right column by column,
I extract the characters from the List, depending on the width of the Model in the Dictionary (ex NumberOne has width 2, NumberFour has width 5).
Each extract will be saved in a new file in Dir Extract
Compare each character in the lists if they are identical. If they are valid then the extracted model will be saved in a file in Dir Validated with the name of a recognized Model name,
and this file will be opened with Notepad for a few seconds, after which notepad will close and the search process will be repeated until the end of the List.
The solutions to this approaches can be found here!

Decimal group seperator for the fractional part

I wonder what would be the best way to format numbers so that the NumberGroupSeparator would work not only on the integer part to the left of the comma, but also on the fractional part, on the right of the comma.
Math.PI.ToString("###,###,##0.0##,###,###,###") // As documented ..
// ..this doesn't work
3.14159265358979 // result
3.141,592,653,589,79 // desired result
As documented on MSDN the NumberGroupSeparator works only to the left of the comma. I wonder why??
A little clunky, and it won't work for scientific numbers but here is a try:
class Program
{
static void Main(string[] args)
{
var π=Math.PI*10000;
Debug.WriteLine(Display(π));
// 31,415.926,535,897,931,899
}
static string Display(double x)
{
int s=Math.Sign(x);
x=Math.Abs(x);
StringBuilder text=new StringBuilder();
var y=Math.Truncate(x);
text.Append((s*y).ToString("#,#"));
x-=y;
if (x>0)
{
// 15 decimal places is max reasonable precision
y=Math.Truncate(x*Math.Pow(10, 15));
text.Append(".");
text.Append(y.ToString("#,#").TrimEnd('0'));
}
return text.ToString();
}
}
It might be best to work with the string generated by your .ToString():
class Program
{
static string InsertSeparators(string s)
{
string decSeparator = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;
int separatorPos = s.IndexOf(decSeparator);
if (separatorPos >= 0)
{
string decPart = s.Substring(separatorPos + decSeparator.Length);
// split the string into parts of 3 or less characters
List<String> parts = new List<String>();
for (int i = 0; i < decPart.Length; i += 3)
{
string part = "";
for (int j = 0; (j < 3) && (i + j < decPart.Length); j++)
{
part += decPart[i + j];
}
parts.Add(part);
}
string groupSeparator = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberGroupSeparator;
s = s.Substring(0, separatorPos) + decSeparator + String.Join(groupSeparator, parts);
}
return s;
}
static void Main(string[] args)
{
for (int n = 0; n < 15; n++)
{
string s = Math.PI.ToString("0." + new string('#', n));
Console.WriteLine(InsertSeparators(s));
}
Console.ReadLine();
}
}
Outputs:
3
3.1
3.14
3.142
3.141,6
3.141,59
3.141,593
3.141,592,7
3.141,592,65
3.141,592,654
3.141,592,653,6
3.141,592,653,59
3.141,592,653,59
3.141,592,653,589,8
3.141,592,653,589,79
OK, not my strong side, but I guess this may be my best bet:
string input = Math.PI.ToString();
string decSeparator = System.Threading.Thread.CurrentThread
.CurrentCulture.NumberFormat.NumberGroupSeparator;
Regex RX = new Regex(#"([0-9]{3})");
string result = RX.Replace(input , #"$1" + decSeparator);
Thanks for listening..

Make big and small numbers human-readable [duplicate]

This question already has answers here:
Formatting Large Numbers with .NET
(5 answers)
Closed 9 years ago.
I would like to print my very small numbers in C# in a human friendly way, such as:
30µ for 3E-5 or 456.789n for 0.000000456789.
I know of the Humanize_number() function from BSD in C, but only compatible with bit ints, not floats and doubles. Is there the equivalent in C# that supports those?
Also, it should keep a certain amount of precision when displaying numbers, like:
0.003596 should be displayed as 3.596µ, not 3.6µ (or worse, 4µ).
The possible answer here: Formatting Large Numbers with .NET but adapted for negative log10 is truncating the numbers to 1 digit after the comma. That's far from complete in my opinion.
Examples of how I'd like to present things:
3000 3K
3300 3.3K
3333 3.333K
30000 30k
300000 300k
3000000 3M
3000003 3.000003M // or 3M if I specify "4 digits precision"
0.253 253m
0.0253 25.3m
0.00253 2.53m
-0.253003 -253.003m
I couldn't formulate my question to find relevant answers in SO, so if the question has been already answered, fire away!
Try this:
static class Extensions
{
static string[] prefixes= { "f", "a", "p", "n", "μ", "m", string.Empty, "k", "M", "G", "T", "P", "E" };
public static string Nice(this double x, int significant_digits)
{
//Check for special numbers and non-numbers
if(double.IsInfinity(x)||double.IsNaN(x)||x==0||significant_digits<=0)
{
return x.ToString();
}
// extract sign so we deal with positive numbers only
int sign=Math.Sign(x);
x=Math.Abs(x);
// get scientific exponent, 10^3, 10^6, ...
int sci= x==0? 0 : (int)Math.Floor(Math.Log(x, 10)/3)*3;
// scale number to exponent found
x=x*Math.Pow(10, -sci);
// find number of digits to the left of the decimal
int dg= x==0? 0 : (int)Math.Floor(Math.Log(x, 10))+1;
// adjust decimals to display
int decimals=Math.Min(significant_digits-dg, 15);
// format for the decimals
string fmt=new string('0', decimals);
if(sci==0)
{
//no exponent
return string.Format("{0}{1:0."+fmt+"}",
sign<0?"-":string.Empty,
Math.Round(x, decimals));
}
// find index for prefix. every 3 of sci is a new index
int index=sci/3+6;
if(index>=0&&index<prefixes.Length)
{
// with prefix
return string.Format("{0}{1:0."+fmt+"}{2}",
sign<0?"-":string.Empty,
Math.Round(x, decimals),
prefixes[index]);
}
// with 10^exp format
return string.Format("{0}{1:0."+fmt+"}·10^{2}",
sign<0?"-":string.Empty,
Math.Round(x, decimals),
sci);
}
// test code
static void Main(string[] args)
{
double x=Math.PI/10e20;
do
{
Console.WriteLine(string.Format( "\t{0,20} = {1}", x, x.Nice(4)));
x*=10;
} while(x<=Math.PI*10e20);
}
}
Test output with four significant digits:
3.14159265358979E-19 = 314.2·10^-2
1.5707963267949E-18 = 1.571f
7.85398163397448E-18 = 7.854f
3.92699081698724E-17 = 39.27f
1.96349540849362E-16 = 196.3f
9.8174770424681E-16 = 981.7f
4.90873852123405E-15 = 4.909a
2.45436926061703E-14 = 24.54a
1.22718463030851E-13 = 122.7a
6.13592315154256E-13 = 613.6a
3.06796157577128E-12 = 3.068p
1.53398078788564E-11 = 15.34p
7.6699039394282E-11 = 76.70p
3.8349519697141E-10 = 383.5p
1.91747598485705E-09 = 1.917n
9.58737992428526E-09 = 9.587n
4.79368996214263E-08 = 47.94n
2.39684498107131E-07 = 239.7n
1.19842249053566E-06 = 1.198µ
5.99211245267829E-06 = 5.992µ
2.99605622633914E-05 = 29.96µ
0.000149802811316957 = 149.8µ
0.000749014056584786 = 749.0µ
0.00374507028292393 = 3.745m
0.0187253514146196 = 18.73m
0.0936267570730982 = 93.63m
0.468133785365491 = 468.1m
2.34066892682745 = 2.341
11.7033446341373 = 11.70
58.5167231706864 = 58.52
292.583615853432 = 292.6
1462.91807926716 = 1.463k
7314.5903963358 = 7.315k
36572.951981679 = 36.57k
182864.759908395 = 182.9k
914323.799541975 = 914.3k
4571618.99770987 = 4.572M
22858094.9885494 = 22.86M
114290474.942747 = 114.3M
571452374.713734 = 571.5M
2857261873.56867 = 2.857G
14286309367.8434 = 14.29G
71431546839.2168 = 71.43G
357157734196.084 = 357.2G
1785788670980.42 = 1.786T
8928943354902.1 = 8.929T
44644716774510.5 = 44.64T
223223583872552 = 223.2T
1.11611791936276E+15 = 1.116P
5.58058959681381E+15 = 5.581P
2.79029479840691E+16 = 27.90P
1.39514739920345E+17 = 139.5P
6.97573699601726E+17 = 697.6P
3.48786849800863E+18 = 3.488E
1.74393424900432E+19 = 17.44E
8.71967124502158E+19 = 87.20E
4.35983562251079E+20 = 436.0E
2.1799178112554E+21 = 2.180·10^21
as you want the decimal to be displayed as sign and not as a lot of 0's you could as well do something like:
class Program
{
static void Main(string[] args)
{
//these are your "unit precedessors"
char[] exponentsbig = new char[] {' ', 'k', 'M', 'G', 'T', 'P', 'E' };
char[] exponentssmall = new char[] { ' ', 'm', 'µ', 'n', 'p', 'a', 'f' };
//some example numbers
long[] numbersBig = new long[] { 3000, 3003, 30000, 300000, 300003, 1594900000000000 };
double[] numbersSmall = new double[] { 0.0002, 0.245, 0.245003, 0.000004578 };
//some helper vars
int counter = 0;
bool edited = false;
//let's have a look at what we produce;)
string output = "";
//Big numbers incoming!!
for (int i = 0; i < numbersBig.Length; i++)
{
counter=0;
double myNumber = Convert.ToDouble(numbersBig[i]);
do
{
edited = false;
//something to prevent unnecessary unit-adding and making sure you still divide by 1000
if (myNumber/1000>1 )
{
counter++;
myNumber /= 1000;
edited = true;
}
} while (edited);
output += numbersBig[i] + " " + myNumber + exponentsbig[counter] + "\n";
}
//small numbers incoming!!
for (int i = 0; i < numbersSmall.Length; i++)
{
counter = 0;
double myNumber = numbersSmall[i];
do
{
edited = false;
//this will go to 3 digits after comma. you can make the compared smaller
//to be more exact after the comma, but keep in mind you lose steps then
if (myNumber < 1)
{
counter++;
myNumber *= 1000;
edited = true;
}
} while (edited);
output += numbersSmall[i] + " " + myNumber + exponentssmall[counter] + "\n";
}
//see what we did
Console.Write(output);
Console.ReadKey();
}
}
Could you use DllImport to use the Humanize_Number function?? See here for details :
Dynamically loading a dll in C#
Why not multiply by 10^(count numbers after decimal)? You can use the same count of numbers after the decimal to figure out which unit to display. It's much better than importing an entire library.

Iterating through string?

Not entirely sure this is possible, but say I have two strings like so:
"IAmAString-00001"
"IAmAString-00023"
What would be a quick'n'easy way to iterate from IAmAString-0001 to IAmAString-00023 by moving up the index of just the numbers on the end?
The problem is a bit more general than that, for example the string I could be dealing could be of any format but the last bunch of chars will always be numbers, so something like Super_Confusing-String#w00t0003 and in that case the last 0003 would be what I'd use to iterate through.
Any ideas?
You can use char.IsDigit:
static void Main(string[] args)
{
var s = "IAmAString-00001";
int index = -1;
for (int i = 0; i < s.Length; i++)
{
if (char.IsDigit(s[i]))
{
index = i;
break;
}
}
if (index == -1)
Console.WriteLine("digits not found");
else
Console.WriteLine("digits: {0}", s.Substring(index));
}
which produces this output:
digits: 00001
string.Format and a for loop should do what you want.
for(int i = 0; i <=23; i++)
{
string.Format("IAmAString-{0:D4}",i);
}
or something close to that (not sitting in front of a compiler).
string start = "IAmAString-00001";
string end = "IAmAString-00023";
// match constant part and ending digits
var matchstart = Regex.Match(start,#"^(.*?)(\d+)$");
int numberstart = int.Parse(matchstart.Groups[2].Value);
var matchend = Regex.Match(end,#"^(.*?)(\d+)$");
int numberend = int.Parse(matchend.Groups[2].Value);
// constant parts must be the same
if (matchstart.Groups[1].Value != matchend.Groups[1].Value)
throw new ArgumentException("");
// create a format string with same number of digits as original
string format = new string('0', matchstart.Groups[2].Length);
for (int ii = numberstart; ii <= numberend; ++ii)
Console.WriteLine(matchstart.Groups[1].Value + ii.ToString(format));
You could use a Regex:
var match=Regex.Match("Super_Confusing-String#w00t0003",#"(?<=(^.*\D)|^)\d+$");
if(match.Success)
{
var val=int.Parse(match.Value);
Console.WriteLine(val);
}
To answer more specifically, you could use named groups to extract what you need:
var match=Regex.Match(
"Super_Confusing-String#w00t0003",
#"(?<prefix>(^.*\D)|^)(?<digits>\d+)$");
if(match.Success)
{
var prefix=match.Groups["prefix"].Value;
Console.WriteLine(prefix);
var val=int.Parse(match.Groups["digits"].Value);
Console.WriteLine(val);
}
If you can assume that the last 5 characters are the number then:
string prefix = "myprefix-";
for (int i=1; i <=23; i++)
{
Console.WriteLine(myPrefix+i.ToString("D5"));
}
This function will find the trailing number.
private int FindTrailingNumber(string str)
{
string numString = "";
int numTest;
for (int i = str.Length - 1; i > 0; i--)
{
char c = str[i];
if (int.TryParse(c.ToString(), out numTest))
{
numString = c + numString;
}
}
return int.Parse(numString);
}
Assuming all your base strings are the same, this would iterate between strings.
string s1 = "asdf123";
string s2 = "asdf127";
int num1 = FindTrailingNumber(s1);
int num2 = FindTrailingNumber(s2);
string strBase = s1.Replace(num1.ToString(), "");
for (int i = num1; i <= num2; i++)
{
Console.WriteLine(strBase + i.ToString());
}
I think it would be better if you do the search from the last (Rick already upvoted you since it was ur logic :-))
static void Main(string[] args)
{
var s = "IAmAString-00001";
int index = -1;
for (int i = s.Length - 1; i >=0; i--)
{
if (!char.IsDigit(s[i]))
{
index = i;
break;
}
}
if (index == -1)
Console.WriteLine("digits not found");
else
Console.WriteLine("digits: {0}", s.Substring(index));
Console.ReadKey();
}
HTH
If the last X numbers are always digits, then:
int x = 5;
string s = "IAmAString-00001";
int num = int.Parse(s.Substring(s.Length - x, x));
Console.WriteLine("Your Number is: {0}", num);
If the last digits can be 3, 4, or 5 in length, then you will need a little more logic:
int x = 0;
string s = "IAmAString-00001";
foreach (char c in s.Reverse())//Use Reverse() so you start with digits only.
{
if(char.IsDigit(c) == false)
break;//If we start hitting non-digit characters, then exit the loop.
++x;
}
int num = int.Parse(s.Substring(s.Length - x, x));
Console.WriteLine("Your Number is: {0}", num);
I'm not good with complicated RegEx. Because of this, I always shy away from it when maximum optimization is unnecessary. The reason for this is RegEx doesn't always parse strings the way you expect it to. If there is and alternate solution that will still run fast then I'd rather go that route as it's easier for me to understand and know that it will work with any combination of strings.
For Example: if you use some of the other solutions presented here with a string like "I2AmAString-000001", then you will get "2000001" as your number instead of "1".

Categories