I have a input like
string input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
// up to 10MB string size
int result = Calc(input); // 11
the calculation is from left to right, number by number
the numbers are 0 to 99
multiplication before addition is ignored so 14 + 2 * 32 is 512
the possible calculations are +-*/
division by 0 is not possible so after a / can't be a 0
My Approach
public static int Calc(string sInput)
{
int iCurrent = sInput.IndexOf(' ');
int iResult = int.Parse(sInput.Substring(0, iCurrent));
int iNext = 0;
while ((iNext = sInput.IndexOf(' ', iCurrent + 4)) != -1)
{
iResult = Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3), iNext - (iCurrent + 2))));
iCurrent = iNext;
}
return Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3))));
}
public static int Operate(int iReturn, char cOperator, int iOperant)
{
switch (cOperator)
{
case '+':
return (iReturn + iOperant);
case '-':
return (iReturn - iOperant);
case '*':
return (iReturn * iOperant);
case '/':
return (iReturn / iOperant);
default:
throw new Exception("Error");
}
}
I need the fastest way to get a result.
Question: is there a way to make this calculation faster? I have multiple threads but I use only one.
Update:
Test-Case: (I've removed the division by 0 bug and removed the StringBuilder.ToString() from the StopWatch measurement)
Random rand = new Random();
System.Text.StringBuilder input = new System.Text.StringBuilder();
string operators = "+-*/";
input.Append(rand.Next(0, 100));
for (int i = 0; i < 1000000; i++)
{
int number = rand.Next(0, 100);
char coperator = operators[rand.Next(0, number > 0 ? 4 : 3)];
input.Append(" " + coperator + " " + number);
}
string calc = input.ToString();
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
int result = Calc(calc);
watch.Stop();
Edit edit: updated with latest versions by The General and Mirai Mann:
If you want to know which horse is fastest: race the horses. Here are BenchmarkDotNet results comparing various answers from this question (I have not merged their code into my full example, because that feels wrong - only the numbers are presented) with repeatable but large random input, via:
static MyTests()
{
Random rand = new Random(12345);
StringBuilder input = new StringBuilder();
string operators = "+-*/";
var lastOperator = '+';
for (int i = 0; i < 1000000; i++)
{
var #operator = operators[rand.Next(0, 4)];
input.Append(rand.Next(lastOperator == '/' ? 1 : 0, 100) + " " + #operator + " ");
lastOperator = #operator;
}
input.Append(rand.Next(0, 100));
expression = input.ToString();
}
private static readonly string expression;
with sanity checks (to check they all do the right thing):
Original: -1426
NoSubStrings: -1426
NoSubStringsUnsafe: -1426
TheGeneral4: -1426
MiraiMann1: -1426
we get timings (note: Original is OP's version in the question; NoSubStrings[Unsafe] is my versions from below, and two other versions from other answers by user-name):
(lower "Mean" is better)
(note; there is a newer version of Mirai Mann's implementation, but I no longer have things setup to run a new test; but: fair to assume it should be better!)
Runtime: .NET Framework 4.7 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2633.0
Method | Mean | Error | StdDev |
------------------- |----------:|----------:|----------:|
Original | 104.11 ms | 1.4920 ms | 1.3226 ms |
NoSubStrings | 21.99 ms | 0.4335 ms | 0.7122 ms |
NoSubStringsUnsafe | 20.53 ms | 0.4103 ms | 0.6967 ms |
TheGeneral4 | 15.50 ms | 0.3020 ms | 0.5369 ms |
MiraiMann1 | 15.54 ms | 0.3096 ms | 0.4133 ms |
Runtime: .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2633.0
Method | Mean | Error | StdDev | Median |
------------------- |----------:|----------:|----------:|----------:|
Original | 114.15 ms | 1.3142 ms | 1.0974 ms | 114.13 ms |
NoSubStrings | 21.33 ms | 0.4161 ms | 0.6354 ms | 20.93 ms |
NoSubStringsUnsafe | 19.24 ms | 0.3832 ms | 0.5245 ms | 19.43 ms |
TheGeneral4 | 13.97 ms | 0.2795 ms | 0.2745 ms | 13.86 ms |
MiraiMann1 | 15.60 ms | 0.3090 ms | 0.4125 ms | 15.53 ms |
Runtime: .NET Core 2.1.0-preview1-26116-04 (CoreCLR 4.6.26116.03, CoreFX 4.6.26116.01), 64bit RyuJIT
Method | Mean | Error | StdDev | Median |
------------------- |----------:|----------:|----------:|----------:|
Original | 101.51 ms | 1.7807 ms | 1.5786 ms | 101.44 ms |
NoSubStrings | 21.36 ms | 0.4281 ms | 0.5414 ms | 21.07 ms |
NoSubStringsUnsafe | 19.85 ms | 0.4172 ms | 0.6737 ms | 19.80 ms |
TheGeneral4 | 14.06 ms | 0.2788 ms | 0.3723 ms | 13.82 ms |
MiraiMann1 | 15.88 ms | 0.3153 ms | 0.5922 ms | 15.45 ms |
Original answer from before I added BenchmarkDotNet:
If I was trying this, I'd be tempted to have a look at the Span<T> work in 2.1 previews - the point being that a Span<T> can be sliced without allocating (and a string can be converted to a Span<char> without allocating); this would allow the string carving and parsing to be performed without any allocations. However, reducing allocations is not always quite the same thing as raw performance (although they are related), so to know if it was faster: you'd need to race your horses (i.e. compare them).
If Span<T> isn't an option: you can still do the same thing by tracking an int offset manually and just *never using SubString)
In either case (string or Span<char>): if your operation only needs to cope with a certain subset of representations of integers, I might be tempted to hand role a custom int.Parse equivalent that doesn't apply as many rules (cultures, alternative layouts, etc), and which works without needing a Substring - for example it could take a string and ref int offset, where the offset is updated to be where the parse stopped (either because it hit an operator or a space), and which worked.
Something like:
static class P
{
static void Main()
{
string input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
var val = Evaluate(input);
System.Console.WriteLine(val);
}
static int Evaluate(string expression)
{
int offset = 0;
SkipSpaces(expression, ref offset);
int value = ReadInt32(expression, ref offset);
while(ReadNext(expression, ref offset, out char #operator, out int operand))
{
switch(#operator)
{
case '+': value = value + operand; break;
case '-': value = value - operand; break;
case '*': value = value * operand; break;
case '/': value = value / operand; break;
}
}
return value;
}
static bool ReadNext(string value, ref int offset,
out char #operator, out int operand)
{
SkipSpaces(value, ref offset);
if(offset >= value.Length)
{
#operator = (char)0;
operand = 0;
return false;
}
#operator = value[offset++];
SkipSpaces(value, ref offset);
if (offset >= value.Length)
{
operand = 0;
return false;
}
operand = ReadInt32(value, ref offset);
return true;
}
static void SkipSpaces(string value, ref int offset)
{
while (offset < value.Length && value[offset] == ' ') offset++;
}
static int ReadInt32(string value, ref int offset)
{
bool isNeg = false;
char c = value[offset++];
int i = (c - '0');
if(c == '-')
{
isNeg = true;
i = 0;
// todo: what to do here if `-` is not followed by [0-9]?
}
while (offset < value.Length && (c = value[offset++]) >= '0' && c <= '9')
i = (i * 10) + (c - '0');
return isNeg ? -i : i;
}
}
Next, I might consider whether it is worthwhile switching to unsafe to remove the double length checks. So I'd implement it both ways, and test it with something like BenchmarkDotNet to see whether it is worth it.
Edit: here is is with unsafe added and BenchmarkDotNet usage:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
static class P
{
static void Main()
{
var summary = BenchmarkRunner.Run<MyTests>();
System.Console.WriteLine(summary);
}
}
public class MyTests
{
const string expression = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
[Benchmark]
public int Original() => EvalOriginal.Calc(expression);
[Benchmark]
public int NoSubStrings() => EvalNoSubStrings.Evaluate(expression);
[Benchmark]
public int NoSubStringsUnsafe() => EvalNoSubStringsUnsafe.Evaluate(expression);
}
static class EvalOriginal
{
public static int Calc(string sInput)
{
int iCurrent = sInput.IndexOf(' ');
int iResult = int.Parse(sInput.Substring(0, iCurrent));
int iNext = 0;
while ((iNext = sInput.IndexOf(' ', iCurrent + 4)) != -1)
{
iResult = Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3), iNext - (iCurrent + 2))));
iCurrent = iNext;
}
return Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3))));
}
public static int Operate(int iReturn, char cOperator, int iOperant)
{
switch (cOperator)
{
case '+':
return (iReturn + iOperant);
case '-':
return (iReturn - iOperant);
case '*':
return (iReturn * iOperant);
case '/':
return (iReturn / iOperant);
default:
throw new Exception("Error");
}
}
}
static class EvalNoSubStrings {
public static int Evaluate(string expression)
{
int offset = 0;
SkipSpaces(expression, ref offset);
int value = ReadInt32(expression, ref offset);
while (ReadNext(expression, ref offset, out char #operator, out int operand))
{
switch (#operator)
{
case '+': value = value + operand; break;
case '-': value = value - operand; break;
case '*': value = value * operand; break;
case '/': value = value / operand; break;
default: throw new Exception("Error");
}
}
return value;
}
static bool ReadNext(string value, ref int offset,
out char #operator, out int operand)
{
SkipSpaces(value, ref offset);
if (offset >= value.Length)
{
#operator = (char)0;
operand = 0;
return false;
}
#operator = value[offset++];
SkipSpaces(value, ref offset);
if (offset >= value.Length)
{
operand = 0;
return false;
}
operand = ReadInt32(value, ref offset);
return true;
}
static void SkipSpaces(string value, ref int offset)
{
while (offset < value.Length && value[offset] == ' ') offset++;
}
static int ReadInt32(string value, ref int offset)
{
bool isNeg = false;
char c = value[offset++];
int i = (c - '0');
if (c == '-')
{
isNeg = true;
i = 0;
}
while (offset < value.Length && (c = value[offset++]) >= '0' && c <= '9')
i = (i * 10) + (c - '0');
return isNeg ? -i : i;
}
}
static unsafe class EvalNoSubStringsUnsafe
{
public static int Evaluate(string expression)
{
fixed (char* ptr = expression)
{
int len = expression.Length;
var c = ptr;
SkipSpaces(ref c, ref len);
int value = ReadInt32(ref c, ref len);
while (len > 0 && ReadNext(ref c, ref len, out char #operator, out int operand))
{
switch (#operator)
{
case '+': value = value + operand; break;
case '-': value = value - operand; break;
case '*': value = value * operand; break;
case '/': value = value / operand; break;
default: throw new Exception("Error");
}
}
return value;
}
}
static bool ReadNext(ref char* c, ref int len,
out char #operator, out int operand)
{
SkipSpaces(ref c, ref len);
if (len-- == 0)
{
#operator = (char)0;
operand = 0;
return false;
}
#operator = *c++;
SkipSpaces(ref c, ref len);
if (len == 0)
{
operand = 0;
return false;
}
operand = ReadInt32(ref c, ref len);
return true;
}
static void SkipSpaces(ref char* c, ref int len)
{
while (len != 0 && *c == ' ') { c++;len--; }
}
static int ReadInt32(ref char* c, ref int len)
{
bool isNeg = false;
char ch = *c++;
len--;
int i = (ch - '0');
if (ch == '-')
{
isNeg = true;
i = 0;
}
while (len-- != 0 && (ch = *c++) >= '0' && ch <= '9')
i = (i * 10) + (ch - '0');
return isNeg ? -i : i;
}
}
The following solution is a finite automaton. Calc(input) = O(n). For better performance, this solution does not use IndexOf, Substring, Parse, string concatenation, or repeated reading of value (fetching input[i] more than once)... just a character processor.
static int Calculate1(string input)
{
int acc = 0;
char last = ' ', operation = '+';
for (int i = 0; i < input.Length; i++)
{
var current = input[i];
switch (current)
{
case ' ':
if (last != ' ')
{
switch (operation)
{
case '+': acc += last - '0'; break;
case '-': acc -= last - '0'; break;
case '*': acc *= last - '0'; break;
case '/': acc /= last - '0'; break;
}
last = ' ';
}
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (last == ' ') last = current;
else
{
var num = (last - '0') * 10 + (current - '0');
switch (operation)
{
case '+': acc += num; break;
case '-': acc -= num; break;
case '*': acc *= num; break;
case '/': acc /= num; break;
}
last = ' ';
}
break;
case '+': case '-': case '*': case '/':
operation = current;
break;
}
}
if (last != ' ')
switch (operation)
{
case '+': acc += last - '0'; break;
case '-': acc -= last - '0'; break;
case '*': acc *= last - '0'; break;
case '/': acc /= last - '0'; break;
}
return acc;
}
And another implementation. It reads 25% less from the input. I expect that it has 25% better performance.
static int Calculate2(string input)
{
int acc = 0, i = 0;
char last = ' ', operation = '+';
while (i < input.Length)
{
var current = input[i];
switch (current)
{
case ' ':
if (last != ' ')
{
switch (operation)
{
case '+': acc += last - '0'; break;
case '-': acc -= last - '0'; break;
case '*': acc *= last - '0'; break;
case '/': acc /= last - '0'; break;
}
last = ' ';
i++;
}
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (last == ' ')
{
last = current;
i++;
}
else
{
var num = (last - '0') * 10 + (current - '0');
switch (operation)
{
case '+': acc += num; break;
case '-': acc -= num; break;
case '*': acc *= num; break;
case '/': acc /= num; break;
}
last = ' ';
i += 2;
}
break;
case '+': case '-': case '*': case '/':
operation = current;
i += 2;
break;
}
}
if (last != ' ')
switch (operation)
{
case '+': acc += last - '0'; break;
case '-': acc -= last - '0'; break;
case '*': acc *= last - '0'; break;
case '/': acc /= last - '0'; break;
}
return acc;
}
I implemented one more variant:
static int Calculate3(string input)
{
int acc = 0, i = 0;
var operation = '+';
while (true)
{
var a = input[i++] - '0';
if (i == input.Length)
{
switch (operation)
{
case '+': acc += a; break;
case '-': acc -= a; break;
case '*': acc *= a; break;
case '/': acc /= a; break;
}
break;
}
var b = input[i];
if (b == ' ') i++;
else
{
a = a * 10 + (b - '0');
i += 2;
}
switch (operation)
{
case '+': acc += a; break;
case '-': acc -= a; break;
case '*': acc *= a; break;
case '/': acc /= a; break;
}
if (i >= input.Length) break;
operation = input[i];
i += 2;
}
return acc;
}
Results in abstract points:
Calculate1 230
Calculate2 192
Calculate3 111
NOTE
Per comments, this answer does not give a performant solution.
I'll leave it here as there are points to be considered / which may be of interest to others finding this thread in future; but as people have said below, this is far from the most performant solution.
Original Answer
The .net framework already supplies a way to handle formulas given as strings:
var formula = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
var result = new DataTable().Compute(formula, null);
Console.WriteLine(result); //returns 139.125490196078
Initial feedback based on comments
Per the comments thread I need to point out some things:
Does this work the way I've described?
No; this follows the normal rules of maths.
I assume that your amended rules are to simplify writing code to handle them, rather than because you want to support a new branch of mathematics? If that's the case, I'd argue against that. People will expect things to behave in a certain way; so you'd have to ensure that anyone sending equations to your code was primed with the knowledge to expect the rules of this new-maths rather than being able to use their existing expectations.
There isn't an option to change the rules here; so if your requirement is to change the rules of maths, this won't work for you.
Is this the Fastest Solution
No. However it should perform well given MS spend a lot of time optimising their code, and so will likely perform faster than any hand-rolled code to do the same (though admittedly this code does a lot more than just support the four main operators; so it's not doing exactly the same).
Per MatthewWatson's specific comment (i.e. calling the DataTable constructor incurs a significant overhead) you'd want to create and then re-use one instance of this object. Depending on what your solution looks like there are various ways to do that; here's one:
interface ICalculator //if we use an interface we can easily switch from datatable to some other calulator; useful for testing, or if we wanted to compare different calculators without much recoding
{
T Calculate<T>(string expression) where T: struct;
}
class DataTableCalculator: ICalculator
{
readonly DataTable dataTable = new DataTable();
public DataTableCalculator(){}
public T Calculate<T>(string expression) where T: struct =>
(T)dataTable.Compute(expression, null);
}
class Calculator: ICalculator
{
static ICalculator internalInstance;
public Calculator(){}
public void InitialiseCalculator (ICalculator calculator)
{
if (internalInstance != null)
{
throw new InvalidOperationException("Calculator has already been initialised");
}
internalInstance = calculator;
}
public T Calculate<T>(string expression) where T: struct =>
internalInstance.Calculate<T>(expression);
}
//then we use it on our code
void Main()
{
var calculator1 = new Calculator();
calculator1.InitialiseCalculator(new DataTableCalculator());
var equation = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
Console.WriteLine(calculator1.Calculate<double>(equation)); //139.125490196078
equation = "1 + 2 - 3 + 4";
Console.WriteLine(calculator1.Calculate<int>(equation)); //4
calculator1 = null;
System.GC.Collect(); //in reality we'd pretty much never do this, but just to illustrate that our static variable continues fro the life of the app domain rather than the life of the instance
var calculator2 = new Calculator();
//calculator2.InitialiseCalculator(new DataTableCalculator()); //uncomment this and you'll get an error; i.e. the calulator should only be initialised once.
equation = "1 + 2 - 3 + 4 / 5 * 6 - 7 / 8 + 9";
Console.WriteLine(calculator2.Calculate<double>(equation)); //12.925
}
NB: The above solution uses a static variable; some people are against use of statics. For this scenario (i.e. where during the lifetime of the application you're only going to require one way of doing calculations) this is a legitimate use case. If you wanted to support switching the calculator at runtime a different approach would be required.
Update after Testing & Comparing
Having run some performance tests:
The DataTable.Compute method's biggest problem is that for equations the size of which you're dealing with it throws a StackOverflowException; (i.e. based on your equation generator's loop for (int i = 0; i < 1000000; i++).
For a single operation with a smaller equation (i < 1000), the compute method (including constructor and Convert.ToInt32 on the double result) takes almost 100 times longer.
for the single operation I also encountered overflow exceptions more often; i.e. because the result of the operations had pushed the value outside the bounds of supported data types...
Even if we move the constructor/initialise call outside of the timed area and remove the conversion to int (and run for thousands of iterations to get an average), your solution comes in 3.5 times faster than mine.
Link to the docs: https://msdn.microsoft.com/en-us/library/system.data.datatable.compute%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
Update
My original answer was just a bit of fun late at night trying to put this in unsafe and I failed miserably (actually didn't work at all and was slower). However I decided to give this another shot.
The premise was to make everything inline, to remove as much IL as I could, keep everything in int or char*, and make my code pretty. I further optimized this by removing the switch, Ifs will be more efficient in this situation, also we can order them in the most logical way. And lastly, if we remove the amount of checks for things we do and assume the input is correct we can remove even more overhead by just assuming things like; if the char is > '0' it must be a number. If it's a space we can do some calculations, else it must be an operator.
This is my last attempt with 10,000,000 calculations run 100 times to get an average, each test does a GC.Collect() and GC.WaitForPendingFinalizers() so we aren't fragmenting the memory.
Results
Test : ms : Cycles (rough) : Increase
-------------------------------------------------------------------
OriginalCalc : 1,295 : 4,407,795,584 :
MarcEvalNoSubStrings : 241 : 820,660,220 : 437.34%, * 5.32
MarcEvalNoSubStringsUnsafe : 206 : 701,980,373 : 528.64%, * 6.28
MiraiMannCalc1 : 225 : 765,678,062 : 475.55%, * 5.75
MiraiMannCalc2 : 183 : 623,384,924 : 607.65%, * 7.07
MyCalc4 : 156 : 534,190,325 : 730.12%, * 8.30
MyCalc5 : 146 : 496,185,459 : 786.98%, * 8.86
MyCalc6 : 134 : 455,610,410 : 866.41%, * 9.66
Fastest Code so far
unsafe int Calc6(ref string expression)
{
int res = 0, val = 0, op = 0;
var isOp = false;
// pin the array
fixed (char* p = expression)
{
// Let's not evaluate this 100 million times
var max = p + expression.Length;
// Let's go straight to the source and just increment the pointer
for (var i = p; i < max; i++)
{
// numbers are the most common thing so let's do a loose
// basic check for them and push them in to our val
if (*i >= '0') { val = val * 10 + *i - 48; continue; }
// The second most common thing are spaces
if (*i == ' ')
{
// not every space we need to calculate
if (!(isOp = !isOp)) continue;
// In this case 4 ifs are more efficient then a switch
// do the calculation, reset out val and jump out
if (op == '+') { res += val; val = 0; continue; }
if (op == '-') { res -= val; val = 0; continue; }
if (op == '*') { res *= val; val = 0; continue; }
if (op == '/') { res /= val; val = 0; continue; }
// this is just for the first op
res = val; val = 0; continue;
}
// anything else is considered an operator
op = *i;
}
if (op == '+') return res + val;
if (op == '-') return res - val;
if (op == '*') return res * val;
if (op == '/') return res / val;
throw new IndexOutOfRangeException();
}
}
Previous
unsafe int Calc4(ref string expression)
{
int res = 0, val = 0, op = 0;
var isOp = false;
fixed (char* p = expression)
{
var max = p + expression.Length;
for (var i = p; i < max; i++)
switch (*i)
{
case ' ':
isOp = !isOp;
if (!isOp) continue;
switch (op)
{
case '+': res += val; val = 0; continue;
case '-': res -= val; val = 0; continue;
case '*': res *= val; val = 0; continue;
case '/': res /= val; val = 0; continue;
default: res = val; val = 0; continue;
}
case '+': case '-': case '*': case '/': op = *i; continue;
default: val = val * 10 + *i - 48; continue;
}
switch (op)
{
case '+': return res + val;
case '-': return res - val;
case '*': return res * val;
case '/': return res / val;
default : return -1;
}
}
}
How I measured the Thread cycles
static class NativeMethods {
public static ulong GetThreadCycles() {
ulong cycles;
if (!QueryThreadCycleTime(PseudoHandle, out cycles))
throw new System.ComponentModel.Win32Exception();
return cycles;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool QueryThreadCycleTime(IntPtr hThread, out ulong cycles);
private static readonly IntPtr PseudoHandle = (IntPtr)(-2);
}
Original Post
I thought I'd try to be smart and use fixed and max this out with millions of calculations
public static unsafe int Calc2(string sInput)
{
var buf = "";
var start = sInput.IndexOf(' ');
var value1 = int.Parse(sInput.Substring(0, start));
string op = null;
var iResult = 0;
var isOp = false;
fixed (char* p = sInput)
{
for (var i = start + 1; i < sInput.Length; i++)
{
var cur = *(p + i);
if (cur == ' ')
{
if (!isOp)
{
op = buf;
isOp = true;
}
else
{
var value2 = int.Parse(buf);
switch (op[0])
{
case '+': iResult += value1 + value2; break;
case '-': iResult += value1 - value2; break;
case '*': iResult += value1 * value2; break;
case '/': iResult += value1 / value2; break;
}
value1 = value2;
isOp = false;
}
buf = "";
}
else
{
buf += cur;
}
}
}
return iResult;
}
private static void Main(string[] args)
{
var input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
var sb = new StringBuilder();
sb.Append(input);
for (var i = 0; i < 10000000; i++)
sb.Append(" + " + input);
var sw = new Stopwatch();
sw.Start();
Calc2(sb.ToString());
sw.Stop();
Console.WriteLine($"sw : {sw.Elapsed:c}");
}
Results were 2 seconds slower than the original!
Here is a Java fun fact. I implemented the same thing in Java and it runs about 20 times faster than Mirai Mann implementation in C#. On my machine 100M chars input string took about 353 milliseconds.
Below is the code that creates and tests the result.
Also, note that while it's a good Java/C# performance tester this is not an optimal solution. A better performance can be achieved by multithreading it. It's possible to calculate portions of the string and then combine the result.
public class Test {
public static void main(String...args){
new Test().run();
}
private void run() {
long startTime = System.currentTimeMillis();
Random random = new Random(123);
int result = 0;
StringBuilder input = new StringBuilder();
input.append(random.nextInt(99) + 1);
while (input.length() < 100_000_000){
int value = random.nextInt(100);
switch (random.nextInt(4)){
case 0:
input.append("-");
result -= value;
break;
case 1: // +
input.append("+");
result += value;
break;
case 2:
input.append("*");
result *= value;
break;
case 3:
input.append("/");
while (value == 0){
value = random.nextInt(100);
}
result /= value;
break;
}
input.append(value);
}
String inputData = input.toString();
System.out.println("Test created in " + (System.currentTimeMillis() - startTime));
startTime = System.currentTimeMillis();
int testResult = test(inputData);
System.out.println("Completed in " + (System.currentTimeMillis() - startTime));
if(result != testResult){
throw new Error("Oops");
}
}
private int test(String inputData) {
char[] input;
try {
Field val = String.class.getDeclaredField("value");
val.setAccessible(true);
input = (char[]) val.get(inputData);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
int result = 0;
int startingI = 1;
{
char c = input[0];
if (c >= '0' && c <= '9') {
result += c - '0';
c = input[1];
if (c >= '0' && c <= '9') {
result += (c - '0') * 10;
startingI++;
}
}
}
for (int i = startingI, length = input.length, value=0; i < length; i++) {
char operation = input[i];
i++;
char c = input[i];
if(c >= '0' && c <= '9'){
value += c - '0';
c = input[i + 1];
if(c >= '0' && c <= '9'){
value = value * 10 + (c - '0');
i++;
}
}
switch (operation){
case '-':
result -= value;
break;
case '+':
result += value;
break;
case '*':
result *= value;
break;
case '/':
result /= value;
break;
}
value = 0;
}
return result;
}
}
When you read the code then you can see that I used a small hack when converting the string to a char array. I mutated the string in order to avoid additional memory allocations for the char array.
Related
I am trying to make a program that is a small console calculator where the input is inserted on a single line in the console. example the input "88+12*7/2" should be translated into a math operation looking like this => "((88+ 12) * 7)/2" and print answer in the console.
I will be grateful if you help me to complete this code...
I did a part of the project but it only works to do the operator on two numbers
static string Input_User()
{
string Input_User = Console.ReadLine();
return Input_User;
}
static void ShowMessage()
{
Console.WriteLine("Enter your numbers with operation like: 7*7");
}
ShowMessage();
string input_string = Input_User();
int result = PerformCalculation(InputToList(input_string));
Console.WriteLine($"{input_string}={result}");
static string[] InputToList(string input)
{
string number1 = "";
string number2 = "";
string Oprt = "";
string[] Arithmetic = new string[3];
int n = 0;
foreach (char charecter in input)
{
int num;
bool isNumerical = int.TryParse(charecter.ToString(), out num);
n += 1;
if (isNumerical)
{
number1 += num;
}
else
{
Oprt = charecter.ToString();
Arithmetic[0] = number1;
Arithmetic[1] = Oprt;
for (int i = n; i <= input.Length - 1; i++)
{
number2 += input[i];
}
Arithmetic[2] = number2;
}
}
return Arithmetic;
}
static int PerformCalculation(string[] Input)
{
int result = 0;
switch (Input[1])
{
case "+":
result = Int32.Parse(Input[0]) + Int32.Parse(Input[2]);
break;
case "-":
result = Int32.Parse(Input[0]) - Int32.Parse(Input[2]);
break;
case "*":
result = Int32.Parse(Input[0]) * Int32.Parse(Input[2]);
break;
case "/":
result = Int32.Parse(Input[0]) / Int32.Parse(Input[2]);
break;
}
return result;
}
If your input would be for example '5 + 5 + 5' it would not work because your function InputToList would do the following
InputToList("5+5+5")
-> Return Value ["5","+","5+5"]
The function PerformCalculation would now try to parse 5+5 to an Integer, and that's simply not possible.
One Solution would be to use regular expressions to filter and check the input.
Then you could use a binary tree or a linked list in which you insert the numbers and operators.
After this you would be able to iterate over the list/tree and to do multiple operations.
I am learning to use expression trees/expressions in C#. I have gradually built up a parser, with which I can take a string in "calculator" syntax (like "2 * 3 + 14 * 4 / 7 - 5 * 5") and build and evaluate an abstract syntax tree (AST). It even calculates the correct answer! :-) The AST consists of Expression nodes: arithmethical binary nodes (Add, Subtract, Multiply, Divide) and unary Constant nodes representing the integer values.
Next step: I want to add parameters to the expression to be parsed, like "2 * 3 + myVar1 * 4 / 7 - 5 * myVar2", and supply the actual values for the parameters at runtime (after the AST has been compiled). I can easily add the ParameterExpressions to the tree - but I cannot find out how to correctly compile my tree and supply the values.
The parser is built using Coco/R and an attributed Bachus-Naur grammar, and looks like this:
using System.Linq.Expressions;
using Ex = System.Linq.Expressions.Expression;
using System;
namespace AtgKalk
{
public class Parser
{
public const int _EOF = 0;
public const int _identifikator = 1;
public const int _tall = 2;
public const int _pluss = 3;
public const int _minus = 4;
public const int _ganger = 5;
public const int _deler = 6;
public const int maxT = 7;
const bool T = true;
const bool x = false;
const int minErrDist = 2;
public Scanner scanner;
public Errors errors;
public Token t; // last recognized token
public Token la; // lookahead token
int errDist = minErrDist;
public Parser(Scanner scanner)
{
this.scanner = scanner;
errors = new Errors();
}
void SynErr(int n)
{
if (errDist >= minErrDist) errors.SynErr(la.line, la.col, n);
errDist = 0;
}
void Get()
{
for (; ; )
{
t = la;
la = scanner.Scan();
if (la.kind <= maxT) { ++errDist; break; }
la = t;
}
}
void Expect(int n)
{
if (la.kind == n) Get(); else { SynErr(n); }
}
void Calculator()
{
Ex n;
CalcExpr(out n);
Console.Write($"AST: {n} = ");
Console.WriteLine(Ex.Lambda<Func<int>>(n).Compile()());
// The above works fine as long as there are no parameter names in the input string
}
void CalcExpr(out Ex n1)
{
Ex n2; Func<Ex, Ex, Ex> f;
Term(out n1);
while (la.kind == 3 || la.kind == 4)
{
AddOp(out f);
Term(out n2);
n1 = f(n1, n2);
}
}
void Term(out Ex n1)
{
n1 = null; Ex n2; Func<Ex, Ex, Ex> f = null;
Fact(out n1);
while (la.kind == 5 || la.kind == 6)
{
MulOp(out f);
Fact(out n2);
n1 = f(n1, n2);
}
}
void AddOp(out Func<Ex, Ex, Ex> f)
{
f = null;
if (la.kind == 3)
{
Get();
f = (l, r) => Ex.Add(l, r);
}
else if (la.kind == 4)
{
Get();
f = (l, r) => Ex.Subtract(l, r);
}
else SynErr(8);
}
void Fact(out Ex n)
{
n = null;
if (la.kind == 2)
{
Number(out n);
}
else if (la.kind == 1)
{
Parameter(out n);
}
else SynErr(9);
}
void MulOp(out Func<Ex, Ex, Ex> f)
{
f = null;
if (la.kind == 5)
{
Get();
f = (l, r) => Ex.Multiply(l, r);
}
else if (la.kind == 6)
{
Get();
f = (l, r) => Ex.Divide(l, r);
}
else SynErr(10);
}
void Number(out Ex n)
{
Expect(2);
n = Ex.Constant(int.Parse(t.val), typeof(int));
}
void Parameter(out Ex n)
{
Expect(1);
n = Ex.Parameter(typeof(int), t.val);
}
public void Parse()
{
la = new Token();
la.val = "";
Get();
Calculator();
Expect(0);
}
static readonly bool[,] set = {
{T,x,x,x, x,x,x,x, x}
};
} // end Parser
public class Errors
{
public int count = 0; // number of errors detected
public System.IO.TextWriter errorStream = Console.Out; // error messages go to this stream
public string errMsgFormat = "-- line {0} col {1}: {2}"; // 0=line, 1=column, 2=text
public virtual void SynErr(int line, int col, int n)
{
string s;
switch (n)
{
case 0: s = "EOF expected"; break;
case 1: s = "identifikator expected"; break;
case 2: s = "tall expected"; break;
case 3: s = "pluss expected"; break;
case 4: s = "minus expected"; break;
case 5: s = "ganger expected"; break;
case 6: s = "deler expected"; break;
case 7: s = "??? expected"; break;
case 8: s = "invalid AddOp"; break;
case 9: s = "invalid Fakt"; break;
case 10: s = "invalid MulOp"; break;
default: s = "error " + n; break;
}
errorStream.WriteLine(errMsgFormat, line, col, s);
count++;
}
} // Errors
public class FatalError : Exception
{
public FatalError(string m) : base(m) { }
}
}
My problem lies in line 63, I think:
Console.WriteLine(Ex.Lambda<Func<int>>(n).Compile()());
Invocation:
Scanner scanner = new Scanner(args[0]); // if args[0] contains the input string :-)
Parser parser = new Parser(scanner);
parser.Parse();
I have now solved my problem. Thanks to kaby76 for valuable tips leading me in the right direction. The example now can handle an arbitrary number of parameters (probably max 16, since this is the maximum number of input arguments for Func<...>)
The solution to the problem war threefold:
Collect the parameters and supply this collection of parameters to the Lambda
Remove the explicit type arguments from the Lambda, letting it infer types
Use DynamicInvoke to execute the resulting Delegate
The problematic statement then looks like this, for an expression with two parameters:
Console.WriteLine(Ex.Lambda(n, para).Compile().DynamicInvoke(3, 4));
I just lost my library and i have a lot of lines of unnecesary code once time i decompile it. It's possible recover or understand more or less the code to rewrite completely to the original source?
For example if i check one part of my projects, i can put one example of how will be the original source before try to decompile it:
private static bool ByteArrayCompare(byte[] b1, byte[] b2)
{
return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
}
Once time i decompile it, the code is completely different:
private static bool ByteArrayCompare(byte[] b1, byte[] b2)
{
if (b1.Length == b2.Length)
{
while (true)
{
int num = 719326676;
goto IL_000d;
IL_000d:
uint num2;
switch ((num2 = (uint)(num ^ 0x6B86E2FE)) % 3u)
{
case 0u:
break;
case 1u:
return SMPTR.memcmp(b1, b2, b1.Length) == 0;
default:
goto IL_0048;
}
continue;
IL_0039:
num = ((int)num2 * -1900091540 ^ 0x4DAC5FA);
goto IL_000d;
}
}
goto IL_0048;
IL_0048:
return false;
}
Now happen the same with these classes.
Run:
public static bool Run()
{
header.header.(), header.CurrentDomain_AssemblyResolve);
while (true)
{
int num = 1039260164;
goto IL_001b;
IL_001b:
uint num2;
while (true)
{
int num3 = default(int);
switch ((num2 = (uint)(num ^ 0x3472CD9D)) % 8u)
{
case 0u:
break;
case 3u:
return true;
case 4u:
num = ((int)num2 * -188976845 ^ -260518362);
continue;
case 7u:
goto IL_006c;
case 6u:
goto IL_0081;
case 1u:
num3 = 1;
num = ((int)num2 * -1593869856 ^ 0x44175289);
continue;
case 2u:
num3++;
num = 179700154;
continue;
default:
return false;
}
break;
IL_0081:
int num4;
if (!RunHandler.HandleRun())
{
num = 311890711;
num4 = num;
}
else
{
num = 483767846;
num4 = num;
}
continue;
IL_006c:
int num5;
if (num3 > 5)
{
num = 822334488;
num5 = num;
}
else
{
num = 1601113283;
num5 = num;
}
}
continue;
IL_004e:
num = ((int)num2 * -75964955 ^ 0x5EDD0E50);
goto IL_001b;
}
}
And the second class:
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (header.id != null)
{
goto IL_0007;
}
goto IL_003e;
IL_003e:
header.id = header.(args);
int num = -782795078;
goto IL_000c;
IL_000c:
uint num2;
switch ((num2 = (uint)(num ^ -504079301)) % 4u)
{
case 0u:
break;
case 2u:
return null;
case 3u:
goto IL_003e;
default:
return header.(Resources.MyDLL;
}
goto IL_0007;
IL_0007:
num = -1157763175;
goto IL_000c;
IL_002f:
num = ((int)num2 * -823262644 ^ -680184524);
goto IL_000c;
}
It's possible to recover the code of the last two classes. why the code compiled add a lot of loops and make very hard to understand what really happen? It's possible to recover it?
I have the following code:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void PanicFuncDelegate(string str, IntPtr args);
private void PanicFunc(string str, IntPtr args)
{
LogFunc("PANIC", str, args);
}
public void LogFunc(string severity, string str, IntPtr args)
{
vprintf($"[{severity}] "+ str,args);
}
[DllImport("libc.so.6")]
private static extern int vprintf(string format, IntPtr args);
This prints to the console the messages correctly formatted. I want to retrieve the values from args to use them in my own logger.
If I try to get the value of each pointer from the array in args (as suggested here: Marshal va_list in C# delegate) I get segmentation fault.
Any suggestions?
I have a function call with this working, here's what I do:
For the DLLImport I use an __arglist to marshall to the va_list,
[DllImport("libc.so.6")]
private static extern int vprintf(string format, __arglist);
Then when calling the function I create the __arglist,
vprintf(string format, __arglist(arg1, arg2, arg3...))
Ofcourse you would need to either call the function with all the arguments statically or build that __arglist dynamically, I don't have the code here but it's possible.
I wonder if you get a segmentation fault because the elements in the object[] are not pinned? Maybe if you pin the object[] and all elements within that would help? Just a guess though.
Just think on how C program gets variables from va_list, and there is the solution:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace VaTest
{
class Program
{
static void Main(string[] args)
{
MarshalVaArgs(vaList => vprintf("%c%d%s", vaList), false, 'a', 123, "bc");
}
[DllImport("msvcrt")] //windows
//[DllImport("c")] //linux
private static extern int vprintf(string format, IntPtr vaList);
private static int IntSizeOf(Type t)
{
return (Marshal.SizeOf(t) + IntPtr.Size - 1) & ~(IntPtr.Size - 1);
}
public static void MarshalVaArgs(Action<IntPtr> action, bool? isUnicode, params object[] args)
{
var sizes = new int[args.Length];
for (var i = 0; i < args.Length; i++)
{
sizes[i] = args[i] is string ? IntPtr.Size : IntSizeOf(args[i].GetType());
}
var allocs = new List<IntPtr>();
var offset = 0;
var result = Marshal.AllocHGlobal(sizes.Sum());
allocs.Add(result);
for (var i = 0; i < args.Length; i++)
{
if (args[i] is string)
{
var s = (string)args[i];
var data = default(IntPtr);
if (isUnicode.HasValue)
{
if (isUnicode.Value)
{
data = Marshal.StringToHGlobalUni(s);
}
else
{
data = Marshal.StringToHGlobalAnsi(s);
}
}
else
{
data = Marshal.StringToHGlobalAuto(s);
}
allocs.Add(data);
Marshal.WriteIntPtr(result, offset, data);
offset += sizes[i];
}
else
{
Marshal.StructureToPtr(args[i], result + offset, false);
offset += sizes[i];
}
}
action(result);
foreach (var ptr in allocs)
{
Marshal.FreeHGlobal(ptr);
}
}
}
}
The code is written and tested with .NET Core 3.0 preview 5, compatible with .NET Framework 4.0 and C# 3.0.
Outputs:
a123bc
As this isn't solved yet i post a long solution that worked for me.
I found the solution in an abandoned project
https://github.com/GoaLitiuM/libobs-sharp
Use like this (tested with FFmpeg):
var objects = va_list_Helper.VaListToArray(format, va_List_Ptr);
// format: frame=%4d QP=%.2f NAL=%d Slice:%c Poc:%-3d I:%-4d P:%-4d SKIP:%-4d size=%d bytes%s
// format (filled): frame= 3 QP=13.00 NAL=0 Slice:B Poc:4 I:0 P:8 SKIP:912 size=32 bytes
// va_List objects: 3, 13, 0, 'B', 4, 0, 8, 912, 32
The classes needed:
public class va_list_Helper
{
public static unsafe object[] VaListToArray(string format, byte* va_list)
{
var vaList = new va_list((IntPtr)va_list);
return vaList.GetObjectsByFormat(format);
}
}
public static class Printf
{
// used
public static string[] GetFormatSpecifiers(string format)
{
if (format.IndexOf('%') == -1)
return null;
// find specifiers from format string
List<int> indices = new List<int>();
for (int j = 0; j < format.Length; j++)
{
j = format.IndexOf('%', j);
if (j == -1)
break;
indices.Add(j);
if (format[j + 1] == '%') // ignore "%%"
j++;
}
if (indices.Count == 0)
return null;
List<string> formats = new List<string>(indices.Count);
for (int mi = 0; mi < indices.Count; mi++)
{
string formatSpecifier = format.Substring(indices[mi], (mi + 1 < indices.Count ? indices[mi + 1] : format.Length) - indices[mi]);
if (!string.IsNullOrWhiteSpace(formatSpecifier))
formats.Add(formatSpecifier);
}
return formats.ToArray();
}
public class FormatSpecificationInfo
{
public string specification;
//public int parameter;
public char type;
public int width;
public int precision;
public FormatFlags flags;
};
[Flags]
public enum FormatFlags
{
// Type length
IsLong = 0x0001, // l
IsLongLong = 0x0002, // ll
IsShort = 0x0004, // h
IsChar = 0x0008, // hh
IsLongDouble = 0x0016, // L
// Flags
LeftAlign = 0x0100, // '-' left align within the width
Sign = 0x0200, // '+' use - or + signs for signed types
Alternate = 0x0400, // '#' prefix non-zero values with hex types
ZeroPad = 0x0800, // '0' pad with zeros
Blank = 0x1000, // ' ' pad sign with blank
Grouping = 0x2000, // '\' group by thousands
ArchSize = 0x4000, // '?' use arch precision
// Dynamic parameters
DynamicWidth = 0x10000,
DynamicPrecision = 0x20000,
};
// used
public static FormatSpecificationInfo GetFormatSpecifierInfo(string specification)
{
if (string.IsNullOrWhiteSpace(specification))
return null;
FormatSpecificationInfo info = new FormatSpecificationInfo()
{
type = '\0',
width = int.MinValue,
precision = 6,
};
string width = "";
string precision = "";
int start = -1;
int fsLength = 1;
// TODO: parse parameter index
for (int i = 0; i < specification.Length && info.type == '\0'; i++)
{
char c = specification[i];
switch (c)
{
case '%':
if (start == -1)
start = i;
else
info.type = c;
info.specification = specification.Substring(start, i + 1 - start);
fsLength = i + 1;
break;
// flags
case '-':
info.flags |= FormatFlags.LeftAlign;
break;
case '+':
info.flags |= FormatFlags.Sign;
break;
case ' ':
info.flags |= FormatFlags.Blank;
break;
case '#':
info.flags |= FormatFlags.Alternate;
break;
case '\'':
info.flags |= FormatFlags.Grouping;
break;
case '?':
info.flags |= FormatFlags.ArchSize;
break;
// precision
case '.':
{
for (int j = i + 1; j < specification.Length; j++)
{
if (specification[j] == '*')
info.flags |= FormatFlags.DynamicPrecision;
else if (char.IsNumber(specification[j]))
precision += specification[j];
else
break;
i++;
}
}
break;
// length flags
case 'h':
info.flags += (int)FormatFlags.IsShort;
break;
case 'l':
info.flags += (int)FormatFlags.IsLong;
break;
case 'L':
info.flags |= FormatFlags.IsLongDouble;
break;
case 'z':
case 'j':
case 't':
// not supported
break;
// dynamic width
case '*':
info.flags |= FormatFlags.DynamicWidth;
break;
default:
{
if (char.IsNumber(c))
{
if (width == "" && c == '0')
info.flags |= FormatFlags.ZeroPad;
else
width += c;
}
else if (char.IsLetter(c) && info.type == '\0')
{
info.type = c;
info.specification = specification.Substring(start, i + 1 - start);
fsLength = i + 1;
}
}
break;
}
}
// sign overrides space
if (info.flags.HasFlag(FormatFlags.Sign) && info.flags.HasFlag(FormatFlags.Blank))
info.flags &= ~FormatFlags.Blank;
if (info.flags.HasFlag(FormatFlags.LeftAlign) && info.flags.HasFlag(FormatFlags.ZeroPad))
info.flags &= ~FormatFlags.ZeroPad;
// unsupported precision for these types
if (info.type == 's' ||
info.type == 'c' ||
Char.ToUpper(info.type) == 'X' ||
info.type == 'o')
{
info.precision = int.MinValue;
}
if (!string.IsNullOrWhiteSpace(precision))
info.precision = Convert.ToInt32(precision);
if (!string.IsNullOrWhiteSpace(width))
info.width = Convert.ToInt32(width);
return info;
}
}
public class va_list
{
internal IntPtr instance; //unmanaged pointer to va_list
public va_list(IntPtr ptr)
{
instance = ptr;
}
/// <summary> Returns unmanaged pointer to argument list. </summary>
public IntPtr GetPointer()
{
return instance;
}
/// <summary> Returns array of objects with help of printf format string. </summary>
/// <param name="format"> printf format string. </param>
public object[] GetObjectsByFormat(string format)
{
return GetObjectsByFormat(format, this);
}
public static unsafe object[] GetObjectsByFormat(string format, va_list va_list)
{
string[] formatSpecifiers = Printf.GetFormatSpecifiers(format);
if (formatSpecifiers == null || va_list == null || va_list.GetPointer() == IntPtr.Zero)
return null;
IntPtr args = va_list.GetPointer();
List<object> objects = new List<object>(formatSpecifiers.Length);
//var bytesDebug = new byte[format.Length];
//Marshal.Copy(va_list.GetPointer(), bytesDebug, 0, bytesDebug.Length);
int offset = 0;
foreach (string spec in formatSpecifiers)
{
var info = Printf.GetFormatSpecifierInfo(spec);
if (info.type == '\0')
continue;
// dynamic width and precision arguments
// these are stored in stack before the actual value
if (info.flags.HasFlag(Printf.FormatFlags.DynamicWidth))
{
int widthArg = Marshal.ReadInt32(args, offset);
objects.Add(widthArg);
offset += Marshal.SizeOf(typeof(IntPtr));
}
if (info.flags.HasFlag(Printf.FormatFlags.DynamicPrecision))
{
int precArg = Marshal.ReadInt32(args, offset);
objects.Add(precArg);
offset += Marshal.SizeOf(typeof(IntPtr));
}
int iSize = info.flags.HasFlag(Printf.FormatFlags.IsLongLong)
? Marshal.SizeOf(typeof(Int64)) : Marshal.SizeOf(typeof(IntPtr));
// marshal objects from pointer
switch (info.type)
{
// 8/16-bit integers
// char / wchar_t (promoted to int)
case 'c':
char c = (char)Marshal.ReadByte(args, offset);
objects.Add(c);
//offset += Marshal.SizeOf(typeof(Int32));
offset += Marshal.SizeOf(typeof(IntPtr));
break;
// signed integers
case 'd':
case 'i':
{
if (info.flags.HasFlag(Printf.FormatFlags.IsShort)) // h
{
short sh = (short)Marshal.ReadInt32(args, offset);
objects.Add(sh);
offset += Marshal.SizeOf(typeof(Int32));
}
else if (info.flags.HasFlag(Printf.FormatFlags.IsLongLong)) // ll
{
long l = Marshal.ReadInt64(args, offset);
objects.Add(l);
offset += iSize;
}
else // int and long types
{
var i = Marshal.ReadInt32(args, offset);
objects.Add(i);
offset += iSize;
}
}
break;
// unsigned integers
case 'u':
case 'o':
case 'x':
case 'X':
{
if (info.flags.HasFlag(Printf.FormatFlags.IsShort)) // h
{
ushort su = (ushort)Marshal.ReadInt32(args, offset);
objects.Add(su);
offset += Marshal.SizeOf(typeof(Int32));
}
else if (info.flags.HasFlag(Printf.FormatFlags.IsLongLong)) // ll
{
ulong lu = (ulong)(long)Marshal.ReadInt64(args, offset);
objects.Add(lu);
offset += iSize;
}
else // uint and ulong types
{
uint u = (uint)Marshal.ReadInt32(args, offset);
objects.Add(u);
offset += iSize;
}
}
break;
// floating-point types
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
{
if (info.flags.HasFlag(Printf.FormatFlags.IsLongDouble)) // L
{
// not really supported but read it as long
long lfi = Marshal.ReadInt64(args, offset);
double d = *(double*)(void*)&lfi;
objects.Add(d);
offset += Marshal.SizeOf(typeof(double));
}
else // double
{
long lfi = Marshal.ReadInt64(args, offset);
double d = *(double*)(void*)&lfi;
objects.Add(d);
offset += Marshal.SizeOf(typeof(double));
}
}
break;
// string
case 's':
{
string s = null;
// same:
//var addr1 = new IntPtr(args.ToInt64() + offset);
//var intPtr4 = Marshal.ReadIntPtr(addr1);
var intPtr3 = Marshal.ReadIntPtr(args, offset);
if (info.flags.HasFlag(Printf.FormatFlags.IsLong))
{
s = Marshal.PtrToStringUni(intPtr3);
}
else
{
s = Marshal.PtrToStringAnsi(intPtr3);
}
objects.Add(s);
offset += Marshal.SizeOf(typeof(IntPtr));
}
break;
// pointer
case 'p':
IntPtr ptr = Marshal.ReadIntPtr(args, offset);
objects.Add(ptr);
offset += Marshal.SizeOf(typeof(IntPtr));
break;
// non-marshallable types, ignored
case ' ':
case '%':
case 'n':
break;
default:
throw new ApplicationException("printf specifier '%" + info.type + "' not supported");
}
}
return objects.ToArray();
}
}
I have some decimal data that I am pushing into a SharePoint list where it is to be viewed. I'd like to restrict the number of significant figures displayed in the result data based on my knowledge of the specific calculation. Sometimes it'll be 3, so 12345 will become 12300 and 0.012345 will become 0.0123. Occasionally it will be 4 or 5. Is there any convenient way to handle this?
See: RoundToSignificantFigures by "P Daddy".
I've combined his method with another one I liked.
Rounding to significant figures is a lot easier in TSQL where the rounding method is based on rounding position, not number of decimal places - which is the case with .Net math.round. You could round a number in TSQL to negative places, which would round at whole numbers - so the scaling isn't needed.
Also see this other thread. Pyrolistical's method is good.
The trailing zeros part of the problem seems like more of a string operation to me, so I included a ToString() extension method which will pad zeros if necessary.
using System;
using System.Globalization;
public static class Precision
{
// 2^-24
public const float FLOAT_EPSILON = 0.0000000596046448f;
// 2^-53
public const double DOUBLE_EPSILON = 0.00000000000000011102230246251565d;
public static bool AlmostEquals(this double a, double b, double epsilon = DOUBLE_EPSILON)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
if (a == b)
{
return true;
}
// ReSharper restore CompareOfFloatsByEqualityOperator
return (System.Math.Abs(a - b) < epsilon);
}
public static bool AlmostEquals(this float a, float b, float epsilon = FLOAT_EPSILON)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
if (a == b)
{
return true;
}
// ReSharper restore CompareOfFloatsByEqualityOperator
return (System.Math.Abs(a - b) < epsilon);
}
}
public static class SignificantDigits
{
public static double Round(this double value, int significantDigits)
{
int unneededRoundingPosition;
return RoundSignificantDigits(value, significantDigits, out unneededRoundingPosition);
}
public static string ToString(this double value, int significantDigits)
{
// this method will round and then append zeros if needed.
// i.e. if you round .002 to two significant figures, the resulting number should be .0020.
var currentInfo = CultureInfo.CurrentCulture.NumberFormat;
if (double.IsNaN(value))
{
return currentInfo.NaNSymbol;
}
if (double.IsPositiveInfinity(value))
{
return currentInfo.PositiveInfinitySymbol;
}
if (double.IsNegativeInfinity(value))
{
return currentInfo.NegativeInfinitySymbol;
}
int roundingPosition;
var roundedValue = RoundSignificantDigits(value, significantDigits, out roundingPosition);
// when rounding causes a cascading round affecting digits of greater significance,
// need to re-round to get a correct rounding position afterwards
// this fixes a bug where rounding 9.96 to 2 figures yeilds 10.0 instead of 10
RoundSignificantDigits(roundedValue, significantDigits, out roundingPosition);
if (Math.Abs(roundingPosition) > 9)
{
// use exponential notation format
// ReSharper disable FormatStringProblem
return string.Format(currentInfo, "{0:E" + (significantDigits - 1) + "}", roundedValue);
// ReSharper restore FormatStringProblem
}
// string.format is only needed with decimal numbers (whole numbers won't need to be padded with zeros to the right.)
// ReSharper disable FormatStringProblem
return roundingPosition > 0 ? string.Format(currentInfo, "{0:F" + roundingPosition + "}", roundedValue) : roundedValue.ToString(currentInfo);
// ReSharper restore FormatStringProblem
}
private static double RoundSignificantDigits(double value, int significantDigits, out int roundingPosition)
{
// this method will return a rounded double value at a number of signifigant figures.
// the sigFigures parameter must be between 0 and 15, exclusive.
roundingPosition = 0;
if (value.AlmostEquals(0d))
{
roundingPosition = significantDigits - 1;
return 0d;
}
if (double.IsNaN(value))
{
return double.NaN;
}
if (double.IsPositiveInfinity(value))
{
return double.PositiveInfinity;
}
if (double.IsNegativeInfinity(value))
{
return double.NegativeInfinity;
}
if (significantDigits < 1 || significantDigits > 15)
{
throw new ArgumentOutOfRangeException("significantDigits", value, "The significantDigits argument must be between 1 and 15.");
}
// The resulting rounding position will be negative for rounding at whole numbers, and positive for decimal places.
roundingPosition = significantDigits - 1 - (int)(Math.Floor(Math.Log10(Math.Abs(value))));
// try to use a rounding position directly, if no scale is needed.
// this is because the scale mutliplication after the rounding can introduce error, although
// this only happens when you're dealing with really tiny numbers, i.e 9.9e-14.
if (roundingPosition > 0 && roundingPosition < 16)
{
return Math.Round(value, roundingPosition, MidpointRounding.AwayFromZero);
}
// Shouldn't get here unless we need to scale it.
// Set the scaling value, for rounding whole numbers or decimals past 15 places
var scale = Math.Pow(10, Math.Ceiling(Math.Log10(Math.Abs(value))));
return Math.Round(value / scale, significantDigits, MidpointRounding.AwayFromZero) * scale;
}
}
This might do the trick:
double Input1 = 1234567;
string Result1 = Convert.ToDouble(String.Format("{0:G3}",Input1)).ToString("R0");
double Input2 = 0.012345;
string Result2 = Convert.ToDouble(String.Format("{0:G3}", Input2)).ToString("R6");
Changing the G3 to G4 produces the oddest result though.
It appears to round up the significant digits?
I ended up snagging some code from http://ostermiller.org/utils/SignificantFigures.java.html. It was in java, so I did a quick search/replace and some resharper reformatting to make the C# build. It seems to work nicely for my significant figure needs. FWIW, I removed his javadoc comments to make it more concise here, but the original code is documented quite nicely.
/*
* Copyright (C) 2002-2007 Stephen Ostermiller
* http://ostermiller.org/contact.pl?regarding=Java+Utilities
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* See COPYING.TXT for details.
*/
public class SignificantFigures
{
private String original;
private StringBuilder _digits;
private int mantissa = -1;
private bool sign = true;
private bool isZero = false;
private bool useScientificNotation = true;
public SignificantFigures(String number)
{
original = number;
Parse(original);
}
public SignificantFigures(double number)
{
original = Convert.ToString(number);
try
{
Parse(original);
}
catch (Exception nfe)
{
_digits = null;
}
}
public bool UseScientificNotation
{
get { return useScientificNotation; }
set { useScientificNotation = value; }
}
public int GetNumberSignificantFigures()
{
if (_digits == null) return 0;
return _digits.Length;
}
public SignificantFigures SetLSD(int place)
{
SetLMSD(place, Int32.MinValue);
return this;
}
public SignificantFigures SetLMSD(int leastPlace, int mostPlace)
{
if (_digits != null && leastPlace != Int32.MinValue)
{
int significantFigures = _digits.Length;
int current = mantissa - significantFigures + 1;
int newLength = significantFigures - leastPlace + current;
if (newLength <= 0)
{
if (mostPlace == Int32.MinValue)
{
original = "NaN";
_digits = null;
}
else
{
newLength = mostPlace - leastPlace + 1;
_digits.Length = newLength;
mantissa = leastPlace;
for (int i = 0; i < newLength; i++)
{
_digits[i] = '0';
}
isZero = true;
sign = true;
}
}
else
{
_digits.Length = newLength;
for (int i = significantFigures; i < newLength; i++)
{
_digits[i] = '0';
}
}
}
return this;
}
public int GetLSD()
{
if (_digits == null) return Int32.MinValue;
return mantissa - _digits.Length + 1;
}
public int GetMSD()
{
if (_digits == null) return Int32.MinValue;
return mantissa + 1;
}
public override String ToString()
{
if (_digits == null) return original;
StringBuilder digits = new StringBuilder(this._digits.ToString());
int length = digits.Length;
if ((mantissa <= -4 || mantissa >= 7 ||
(mantissa >= length &&
digits[digits.Length - 1] == '0') ||
(isZero && mantissa != 0)) && useScientificNotation)
{
// use scientific notation.
if (length > 1)
{
digits.Insert(1, '.');
}
if (mantissa != 0)
{
digits.Append("E" + mantissa);
}
}
else if (mantissa <= -1)
{
digits.Insert(0, "0.");
for (int i = mantissa; i < -1; i++)
{
digits.Insert(2, '0');
}
}
else if (mantissa + 1 == length)
{
if (length > 1 && digits[digits.Length - 1] == '0')
{
digits.Append('.');
}
}
else if (mantissa < length)
{
digits.Insert(mantissa + 1, '.');
}
else
{
for (int i = length; i <= mantissa; i++)
{
digits.Append('0');
}
}
if (!sign)
{
digits.Insert(0, '-');
}
return digits.ToString();
}
public String ToScientificNotation()
{
if (_digits == null) return original;
StringBuilder digits = new StringBuilder(this._digits.ToString());
int length = digits.Length;
if (length > 1)
{
digits.Insert(1, '.');
}
if (mantissa != 0)
{
digits.Append("E" + mantissa);
}
if (!sign)
{
digits.Insert(0, '-');
}
return digits.ToString();
}
private const int INITIAL = 0;
private const int LEADZEROS = 1;
private const int MIDZEROS = 2;
private const int DIGITS = 3;
private const int LEADZEROSDOT = 4;
private const int DIGITSDOT = 5;
private const int MANTISSA = 6;
private const int MANTISSADIGIT = 7;
private void Parse(String number)
{
int length = number.Length;
_digits = new StringBuilder(length);
int state = INITIAL;
int mantissaStart = -1;
bool foundMantissaDigit = false;
// sometimes we don't know if a zero will be
// significant or not when it is encountered.
// keep track of the number of them so that
// the all can be made significant if we find
// out that they are.
int zeroCount = 0;
int leadZeroCount = 0;
for (int i = 0; i < length; i++)
{
char c = number[i];
switch (c)
{
case '.':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
{
state = LEADZEROSDOT;
}
break;
case MIDZEROS:
{
// we now know that these zeros
// are more than just trailing place holders.
for (int j = 0; j < zeroCount; j++)
{
_digits.Append('0');
}
zeroCount = 0;
state = DIGITSDOT;
}
break;
case DIGITS:
{
state = DIGITSDOT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '+':
{
switch (state)
{
case INITIAL:
{
sign = true;
state = LEADZEROS;
}
break;
case MANTISSA:
{
state = MANTISSADIGIT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '-':
{
switch (state)
{
case INITIAL:
{
sign = false;
state = LEADZEROS;
}
break;
case MANTISSA:
{
state = MANTISSADIGIT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '0':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
{
// only significant if number
// is all zeros.
zeroCount++;
leadZeroCount++;
state = LEADZEROS;
}
break;
case MIDZEROS:
case DIGITS:
{
// only significant if followed
// by a decimal point or nonzero digit.
mantissa++;
zeroCount++;
state = MIDZEROS;
}
break;
case LEADZEROSDOT:
{
// only significant if number
// is all zeros.
mantissa--;
zeroCount++;
state = LEADZEROSDOT;
}
break;
case DIGITSDOT:
{
// non-leading zeros after
// a decimal point are always
// significant.
_digits.Append(c);
}
break;
case MANTISSA:
case MANTISSADIGIT:
{
foundMantissaDigit = true;
state = MANTISSADIGIT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
case DIGITS:
{
zeroCount = 0;
_digits.Append(c);
mantissa++;
state = DIGITS;
}
break;
case MIDZEROS:
{
// we now know that these zeros
// are more than just trailing place holders.
for (int j = 0; j < zeroCount; j++)
{
_digits.Append('0');
}
zeroCount = 0;
_digits.Append(c);
mantissa++;
state = DIGITS;
}
break;
case LEADZEROSDOT:
case DIGITSDOT:
{
zeroCount = 0;
_digits.Append(c);
state = DIGITSDOT;
}
break;
case MANTISSA:
case MANTISSADIGIT:
{
state = MANTISSADIGIT;
foundMantissaDigit = true;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case 'E':
case 'e':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
case DIGITS:
case LEADZEROSDOT:
case DIGITSDOT:
{
// record the starting point of the mantissa
// so we can do a substring to get it back later
mantissaStart = i + 1;
state = MANTISSA;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
if (mantissaStart != -1)
{
// if we had found an 'E'
if (!foundMantissaDigit)
{
// we didn't actually find a mantissa to go with.
throw new Exception(
"No digits in mantissa."
);
}
// parse the mantissa.
mantissa += Convert.ToInt32(number.Substring(mantissaStart));
}
if (_digits.Length == 0)
{
if (zeroCount > 0)
{
// if nothing but zeros all zeros are significant.
for (int j = 0; j < zeroCount; j++)
{
_digits.Append('0');
}
mantissa += leadZeroCount;
isZero = true;
sign = true;
}
else
{
// a hack to catch some cases that we could catch
// by adding a ton of extra states. Things like:
// "e2" "+e2" "+." "." "+" etc.
throw new Exception(
"No digits in number."
);
}
}
}
public SignificantFigures SetNumberSignificantFigures(int significantFigures)
{
if (significantFigures <= 0)
throw new ArgumentException("Desired number of significant figures must be positive.");
if (_digits != null)
{
int length = _digits.Length;
if (length < significantFigures)
{
// number is not long enough, pad it with zeros.
for (int i = length; i < significantFigures; i++)
{
_digits.Append('0');
}
}
else if (length > significantFigures)
{
// number is too long chop some of it off with rounding.
bool addOne; // we need to round up if true.
char firstInSig = _digits[significantFigures];
if (firstInSig < '5')
{
// first non-significant digit less than five, round down.
addOne = false;
}
else if (firstInSig == '5')
{
// first non-significant digit equal to five
addOne = false;
for (int i = significantFigures + 1; !addOne && i < length; i++)
{
// if its followed by any non-zero digits, round up.
if (_digits[i] != '0')
{
addOne = true;
}
}
if (!addOne)
{
// if it was not followed by non-zero digits
// if the last significant digit is odd round up
// if the last significant digit is even round down
addOne = (_digits[significantFigures - 1] & 1) == 1;
}
}
else
{
// first non-significant digit greater than five, round up.
addOne = true;
}
// loop to add one (and carry a one if added to a nine)
// to the last significant digit
for (int i = significantFigures - 1; addOne && i >= 0; i--)
{
char digit = _digits[i];
if (digit < '9')
{
_digits[i] = (char) (digit + 1);
addOne = false;
}
else
{
_digits[i] = '0';
}
}
if (addOne)
{
// if the number was all nines
_digits.Insert(0, '1');
mantissa++;
}
// chop it to the correct number of figures.
_digits.Length = significantFigures;
}
}
return this;
}
public double ToDouble()
{
return Convert.ToDouble(original);
}
public static String Format(double number, int significantFigures)
{
SignificantFigures sf = new SignificantFigures(number);
sf.SetNumberSignificantFigures(significantFigures);
return sf.ToString();
}
}
I have a shorted answer to calculating significant figures of a number. Here is the code & the test results...
using System;
using System.Collections.Generic;
namespace ConsoleApplicationRound
{
class Program
{
static void Main(string[] args)
{
//char cDecimal = '.'; // for English cultures
char cDecimal = ','; // for German cultures
List<double> l_dValue = new List<double>();
ushort usSignificants = 5;
l_dValue.Add(0);
l_dValue.Add(0.000640589);
l_dValue.Add(-0.000640589);
l_dValue.Add(-123.405009);
l_dValue.Add(123.405009);
l_dValue.Add(-540);
l_dValue.Add(540);
l_dValue.Add(-540911);
l_dValue.Add(540911);
l_dValue.Add(-118.2);
l_dValue.Add(118.2);
l_dValue.Add(-118.18);
l_dValue.Add(118.18);
l_dValue.Add(-118.188);
l_dValue.Add(118.188);
foreach (double d in l_dValue)
{
Console.WriteLine("d = Maths.Round('" +
cDecimal + "', " + d + ", " + usSignificants +
") = " + Maths.Round(
cDecimal, d, usSignificants));
}
Console.Read();
}
}
}
The Maths class used is as follows:
using System;
using System.Text;
namespace ConsoleApplicationRound
{
class Maths
{
/// <summary>
/// The word "Window"
/// </summary>
private static String m_strZeros = "000000000000000000000000000000000";
/// <summary>
/// The minus sign
/// </summary>
public const char m_cDASH = '-';
/// <summary>
/// Determines the number of digits before the decimal point
/// </summary>
/// <param name="cDecimal">
/// Language-specific decimal separator
/// </param>
/// <param name="strValue">
/// Value to be scrutinised
/// </param>
/// <returns>
/// Nr. of digits before the decimal point
/// </returns>
private static ushort NrOfDigitsBeforeDecimal(char cDecimal, String strValue)
{
short sDecimalPosition = (short)strValue.IndexOf(cDecimal);
ushort usSignificantDigits = 0;
if (sDecimalPosition >= 0)
{
strValue = strValue.Substring(0, sDecimalPosition + 1);
}
for (ushort us = 0; us < strValue.Length; us++)
{
if (strValue[us] != m_cDASH) usSignificantDigits++;
if (strValue[us] == cDecimal)
{
usSignificantDigits--;
break;
}
}
return usSignificantDigits;
}
/// <summary>
/// Rounds to a fixed number of significant digits
/// </summary>
/// <param name="d">
/// Number to be rounded
/// </param>
/// <param name="usSignificants">
/// Requested significant digits
/// </param>
/// <returns>
/// The rounded number
/// </returns>
public static String Round(char cDecimal,
double d,
ushort usSignificants)
{
StringBuilder value = new StringBuilder(Convert.ToString(d));
short sDecimalPosition = (short)value.ToString().IndexOf(cDecimal);
ushort usAfterDecimal = 0;
ushort usDigitsBeforeDecimalPoint =
NrOfDigitsBeforeDecimal(cDecimal, value.ToString());
if (usDigitsBeforeDecimalPoint == 1)
{
usAfterDecimal = (d == 0)
? usSignificants
: (ushort)(value.Length - sDecimalPosition - 2);
}
else
{
if (usSignificants >= usDigitsBeforeDecimalPoint)
{
usAfterDecimal =
(ushort)(usSignificants - usDigitsBeforeDecimalPoint);
}
else
{
double dPower = Math.Pow(10,
usDigitsBeforeDecimalPoint - usSignificants);
d = dPower*(long)(d/dPower);
}
}
double dRounded = Math.Round(d, usAfterDecimal);
StringBuilder result = new StringBuilder();
result.Append(dRounded);
ushort usDigits = (ushort)result.ToString().Replace(
Convert.ToString(cDecimal), "").Replace(
Convert.ToString(m_cDASH), "").Length;
// Add lagging zeros, if necessary:
if (usDigits < usSignificants)
{
if (usAfterDecimal != 0)
{
if (result.ToString().IndexOf(cDecimal) == -1)
{
result.Append(cDecimal);
}
int i = (d == 0) ? 0 : Math.Min(0, usDigits - usSignificants);
result.Append(m_strZeros.Substring(0, usAfterDecimal + i));
}
}
return result.ToString();
}
}
}
Any answer with a shorter code?
You can get an elegant bit perfect rounding by using the GetBits method on Decimal and leveraging BigInteger to perform masking.
Some utils
public static int CountDigits
(BigInteger number) => ((int)BigInteger.Log10(number))+1;
private static readonly BigInteger[] BigPowers10
= Enumerable.Range(0, 100)
.Select(v => BigInteger.Pow(10, v))
.ToArray();
The main function
public static decimal RoundToSignificantDigits
(this decimal num,
short n)
{
var bits = decimal.GetBits(num);
var u0 = unchecked((uint)bits[0]);
var u1 = unchecked((uint)bits[1]);
var u2 = unchecked((uint)bits[2]);
var i = new BigInteger(u0)
+ (new BigInteger(u1) << 32)
+ (new BigInteger(u2) << 64);
var d = CountDigits(i);
var delta = d - n;
if (delta < 0)
return num;
var scale = BigPowers10[delta];
var div = i/scale;
var rem = i%scale;
var up = rem > scale/2;
if (up)
div += 1;
var shifted = div*scale;
bits[0] =unchecked((int)(uint) (shifted & BigUnitMask));
bits[1] =unchecked((int)(uint) (shifted>>32 & BigUnitMask));
bits[2] =unchecked((int)(uint) (shifted>>64 & BigUnitMask));
return new decimal(bits);
}
test case 0
public void RoundToSignificantDigits()
{
WMath.RoundToSignificantDigits(0.0012345m, 2).Should().Be(0.0012m);
WMath.RoundToSignificantDigits(0.0012645m, 2).Should().Be(0.0013m);
WMath.RoundToSignificantDigits(0.040000000000000008, 6).Should().Be(0.04);
WMath.RoundToSignificantDigits(0.040000010000000008, 6).Should().Be(0.04);
WMath.RoundToSignificantDigits(0.040000100000000008, 6).Should().Be(0.0400001);
WMath.RoundToSignificantDigits(0.040000110000000008, 6).Should().Be(0.0400001);
WMath.RoundToSignificantDigits(0.20000000000000004, 6).Should().Be(0.2);
WMath.RoundToSignificantDigits(0.10000000000000002, 6).Should().Be(0.1);
WMath.RoundToSignificantDigits(0.0, 6).Should().Be(0.0);
}
test case 1
public void RoundToSigFigShouldWork()
{
1.2m.RoundToSignificantDigits(1).Should().Be(1m);
0.01235668m.RoundToSignificantDigits(3).Should().Be(0.0124m);
0.01m.RoundToSignificantDigits(3).Should().Be(0.01m);
1.23456789123456789123456789m.RoundToSignificantDigits(4)
.Should().Be(1.235m);
1.23456789123456789123456789m.RoundToSignificantDigits(16)
.Should().Be(1.234567891234568m);
1.23456789123456789123456789m.RoundToSignificantDigits(24)
.Should().Be(1.23456789123456789123457m);
1.23456789123456789123456789m.RoundToSignificantDigits(27)
.Should().Be(1.23456789123456789123456789m);
}
I found this article doing a quick search on it. Basically this one converts to a string and goes by the characters in that array one at a time, till it reached the max. significance. Will this work?
The following code doesn't quite meet the spec, since it doesn't try to round anything to the left of the decimal point. But it's simpler than anything else presented here (so far). I was quite surprised that C# doesn't have a built-in method to handle this.
static public string SignificantDigits(double d, int digits=10)
{
int magnitude = (d == 0.0) ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(d))) + 1;
digits -= magnitude;
if (digits < 0)
digits = 0;
string fmt = "f" + digits.ToString();
return d.ToString(fmt);
}
This method is dead simple and works with any number, positive or negative, and only uses a single transcendental function (Log10). The only difference (which may/may-not matter) is that it will not round the integer component. This is perfect however for currency processing where you know the limits are within certain bounds, because you can use doubles for much faster processing than the dreadfully slow Decimal type.
public static double ToDecimal( this double x, int significantFigures = 15 ) {
// determine # of digits before & after the decimal
int digitsBeforeDecimal = (int)x.Abs().Log10().Ceil().Max( 0 ),
digitsAfterDecimal = (significantFigures - digitsBeforeDecimal).Max( 0 );
// round it off
return x.Round( digitsAfterDecimal );
}
As I remember it "significant figures" means the number of digits after the dot separator so 3 significant digits for 0.012345 would be 0.012 and not 0.0123, but that really doesnt matter for the solution.
I also understand that you want to "nullify" the last digits to a certain degree if the number is > 1. You write that 12345 would become 12300 but im not sure whether you want 123456 to become 1230000 or 123400 ? My solution does the last. Instead of calculating the factor you could ofcourse make a small initialized array if you only have a couple of variations.
private static string FormatToSignificantFigures(decimal number, int amount)
{
if (number > 1)
{
int factor = Factor(amount);
return ((int)(number/factor)*factor).ToString();
}
NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
nfi.NumberDecimalDigits = amount;
return(number.ToString("F", nfi));
}
private static int Factor(int x)
{
return DoCalcFactor(10, x-1);
}
private static int DoCalcFactor(int x, int y)
{
if (y == 1) return x;
return 10*DoCalcFactor(x, y - 1);
}
Kind regards
Carsten