I am writing an app in C# to keep details of expenditure in relation to a
small block of flats.
There are 9 flats and costs are apportioned based on the lease terms, in
all about 12 different categories each with a different percentage cost
for each flat. I store the amounts and the proportions as decimal numbers
with 2 decimal places in an Access database.
I am using one central function to do the calculations:
public static Decimal CalculateProportion(Decimal amount, Decimal
proportion)
{
double famount = (double)amount;
double fproportion = (double)proportion;
double divisor = 100F;
double result = (famount * fproportion) / divisor;
double result1 = Math.Round(result, 2, MidpointRounding.AwayFromZero);
// For comparison
double result2 = Math.Round(result, 2, MidpointRounding.ToEven);
return Convert.ToDecimal(result1);
}
I appreciate the issues around computers representing numbers and I want
to get as close as I can to the result that would be produced by a
calculator or an Excel spreadsheet - at the moment although some results
are OK other are out by a penny (compared to Excel) which will raise
questions among the tenants.
Can I improve the accuracy of this function in any way? As I said all the
calculations are done in the one function so improving that function will
improve the accuracy of the whole app.
Many thanks.
I wrote a class several years back when at Uni that might help.
I dont know if you require this amount of decimal accuracy.
The class below requires the use of
http://www.codeproject.com/KB/cs/biginteger.aspx
// BigDecimal.cs created with MonoDevelop
// User: j1mb0jay at 04:38Â 10/04/2009
using System;
using System.Text;
namespace JJMath.Big
{
public class BigDecimal
{
#region Finals
private static int SCALE =150;
#endregion
#region Instance Variables
private BigInteger[] decimal_as_fraction;
public BigInteger[] Fraction { get { return this.decimal_as_fraction; } }
#endregion
#region Constructors
public BigDecimal(String decimal_as_string)
{
this.decimal_as_fraction = this.ConvertDecimalToFraction(decimal_as_string).Fraction;
}
public BigDecimal(BigInteger[] fraction)
{
this.decimal_as_fraction = fraction;
}
public BigDecimal(int number)
{
this.decimal_as_fraction = this.ConvertDecimalToFraction(number.ToString()).Fraction;
}
public BigDecimal(long number)
{
this.decimal_as_fraction = this.ConvertDecimalToFraction(number.ToString()).Fraction;
}
public BigDecimal(BigInteger number)
{
this.decimal_as_fraction = this.ConvertDecimalToFraction(number.ToString()).Fraction;
}
#endregion
#region Conversions
private BigDecimal ConvertDecimalToFraction(String decimal_as_string)
{
if(decimal_as_string.IndexOf('.') != -1)
{
bool isNegative = false;
if(decimal_as_string.StartsWith("-"))
{
isNegative = true;
decimal_as_string = decimal_as_string.Replace("-","");
}
int digits_after_decimal_point = ((decimal_as_string.Length - decimal_as_string.IndexOf('.')) - 1);
BigInteger numerator = new BigInteger(decimal_as_string.Substring( (decimal_as_string.IndexOf(".") + 1)),10);
BigInteger denominator = BigInteger.Pow(10,digits_after_decimal_point);
BigInteger whole_num = new BigInteger(decimal_as_string.Substring(0,decimal_as_string.IndexOf(".")),10);
numerator+= (denominator * whole_num);
if(isNegative)
numerator*=-1;
return Simplify(new BigInteger[] { numerator , denominator });
//return new BigDecimal(new BigInteger[] { numerator , denominator });
}
else
{
return new BigDecimal(new BigInteger[] { new BigInteger(decimal_as_string,10) , 1 });
}
}
#endregion
#region Simplifications
private BigDecimal Simplify(BigInteger[] fraction)
{
BigInteger gcd = BigInteger.GCD(fraction[0],fraction[1]);
while(gcd != 1)
{
fraction[0] = (fraction[0] / gcd);
fraction[1] = (fraction[1] / gcd);
gcd = BigInteger.GCD(fraction[0],fraction[1]);
}
if(fraction[0] > 0 && fraction[1] < 0)
{
fraction[0]*= -1;
fraction[1]*= -1;
}
return new BigDecimal(fraction);
}
#endregion
#region Basic Maths Functions
public BigDecimal Multiply(BigDecimal multiply)
{
return Simplify( new BigInteger[] { (this.decimal_as_fraction[0] * multiply.Fraction[0]) ,
(this.decimal_as_fraction[1] * multiply.Fraction[1]) });
//return new BigDecimal(new BigInteger[] { (this.decimal_as_fraction[0] * multiply.Fraction[0]) ,
//(this.decimal_as_fraction[1] * multiply.Fraction[1]) });
}
public BigDecimal Divide(BigDecimal divide)
{
return Multiply(new BigDecimal(new BigInteger[] { divide.Fraction[1] , divide.Fraction[0] }));
}
public BigDecimal Add(BigDecimal adder)
{
return this.Simplify(new BigInteger[] { ((this.decimal_as_fraction[0] * adder.Fraction[1]) +
(this.decimal_as_fraction[1] * adder.Fraction[0])) ,
adder.Fraction[1] * this.decimal_as_fraction[1] });
}
public BigDecimal Subtract(BigDecimal adder)
{
return this.Simplify(new BigInteger[] { ((this.decimal_as_fraction[0] * adder.Fraction[1]) -
(this.decimal_as_fraction[1] * adder.Fraction[0])) ,
adder.Fraction[1] * this.decimal_as_fraction[1] });
}
#endregion
#region Complex Maths
private BigInteger[][] CalculateAlpha(int n)
{
StringBuilder test = new StringBuilder(this.ToDecimal());
if(!test.ToString().Contains("."))
{
test.Append(".");
for(int i = 0; i < ((BigDecimal.SCALE + 1) * n); i++)
{
test.Append("0");
}
}
else
{
int current_decimal_places = test.ToString().Split('.')[1].Length;
int required_decimal_places = (BigDecimal.SCALE + 1) * n;
if(current_decimal_places < required_decimal_places)
{
for(int i = 0; i < (required_decimal_places - current_decimal_places); i++)
{
test.Append("0");
}
}
}
String[] parts = test.ToString().Split('.');
StringBuilder whole_number = new StringBuilder(parts[0]);
while(whole_number.ToString().Length % n != 0)
{
whole_number.Insert(0,"0");
}
parts[0] = whole_number.ToString();
//Console.WriteLine(parts[0] + "." + parts[1]);
BigInteger[] whole_number_blocks = new BigInteger[(parts[0].Length / n)];
int string_index = 0;
int array_index = 0;
while(string_index < parts[0].Length)
{
whole_number_blocks[array_index] = new BigInteger(parts[0].Substring(string_index,n),10);
array_index++;
string_index+=n;
}
BigInteger[] decimal_number_blocks = new BigInteger[(parts[1].Length / n)];
string_index = 0;
array_index = 0;
while(string_index < parts[1].Length)
{
decimal_number_blocks[array_index] = new BigInteger(parts[1].Substring(string_index,n),10);
array_index++;
string_index+=n;
}
return new BigInteger[][] { whole_number_blocks, decimal_number_blocks};
}
public BigDecimal NthRoot(int n)
{
//Console.WriteLine(n + "th root of " + this.ToDecimal());
BigInteger ten = new BigInteger(10);
BigInteger gamma = new BigInteger(0);
BigInteger new_gamma = new BigInteger(0);
BigInteger beta = new BigInteger(0);
BigInteger[][] alpha_blocks = this.CalculateAlpha(n);
int alpha_index = 0;
int alpha_part = 0;
BigInteger alpha = new BigInteger(alpha_blocks[alpha_part][alpha_index]);
BigInteger r = new BigInteger(0);
BigInteger new_r = new BigInteger(0);
StringBuilder ans = new StringBuilder();
bool hasStarted = false;
while(true)
{
//Console.WriteLine("ALPHA : " + alpha);
while(true)
{
if((BigInteger.Pow(((ten * gamma) + beta),n)) <= (((BigInteger.Pow(ten,n)) * r) + alpha) + ((BigInteger.Pow(ten,n)) * (BigInteger.Pow(gamma,n))))
{
beta++;
}
else
{
beta--;
break;
}
}
//Console.WriteLine("BETA : " + beta);
ans.Append(beta);
new_gamma = ((ten * gamma) + beta);
r = (((BigInteger.Pow(ten,n)) * r) + alpha) - (BigInteger.Pow(((ten * gamma) + beta),n) - ((BigInteger.Pow(ten,n)) * (BigInteger.Pow(gamma,n))));
gamma = new_gamma;
//Console.WriteLine("GAMMA : " + gamma);
//Console.WriteLine("R : " + r);
if(r == 0 && hasStarted)
return new BigDecimal(ans.ToString());
alpha_index++;
if(alpha_part == 1 && alpha_index >= alpha_blocks[1].Length)
return new BigDecimal(ans.ToString());
if(alpha_part == 0 && alpha_index >= alpha_blocks[0].Length)
{
alpha_part = 1;
alpha_index = 0;
ans.Append(".");
}
alpha = alpha_blocks[alpha_part][alpha_index];
if(alpha > 0)
hasStarted = true;
beta = new BigInteger(0);
//Console.WriteLine("\r\n\r\n");
}
}
public BigDecimal Pow(BigInteger power)
{
if(power < 0)
{
power*=-1;
BigInteger tmp = this.Fraction[0];
this.Fraction[0] = this.Fraction[1];
this.Fraction[1] = tmp;
}
return new BigDecimal(new BigInteger[] { BigInteger.Pow(this.Fraction[0],power),
BigInteger.Pow(this.Fraction[1],power) });
}
public BigDecimal Pow(BigDecimal power)
{
//Console.WriteLine(power.ToString());
BigDecimal ans = this.NthRoot( power.Fraction[1].IntValue() ).Pow( power.Fraction[0].IntValue() );
return ans;
}
public BigDecimal Exponential()
{
BigInteger n = 0;
BigDecimal ans = new BigDecimal("0");
BigDecimal.SCALE+=1;
String last_decimal_value = string.Empty;
while(true)
{
ans = ans.Add( (this.Pow(n)).Divide(new BigDecimal(BigInteger.Factorial(n))) );
if(ans.ToDecimal().CompareTo(last_decimal_value) == 0)
break;
n++;
last_decimal_value = ans.ToDecimal();
//Console.WriteLine(last_decimal_value);
}
BigDecimal.SCALE-=1;
return ans;
}
public BigDecimal SIN ()
{
BigInteger n = 0;
BigDecimal ans = new BigDecimal("0");
String last_decimal_value = string.Empty;
int one = 1;
while(true)
{
ans = ans.Add(new BigDecimal( one ).Multiply( this.Pow( (n*2)+1) ).Divide(new BigDecimal(BigInteger.Factorial((2*n)+1))));
//Console.WriteLine(ans.ToDecimal());
if(ans.ToDecimal().CompareTo(last_decimal_value) == 0)
break;
n++;
last_decimal_value = ans.ToDecimal();
one *= -1;
}
return ans;
}
public BigDecimal COS ()
{
BigInteger n = 0;
BigDecimal ans = new BigDecimal("0");
String last_decimal_value = string.Empty;
int one = 1;
while(true)
{
ans = ans.Add(new BigDecimal( one ).Multiply( this.Pow((n*2)) ).Divide(new BigDecimal(BigInteger.Factorial((2*n)))));
//Console.WriteLine(ans.ToDecimal());
if(ans.ToDecimal().CompareTo(last_decimal_value) == 0)
break;
n++;
last_decimal_value = ans.ToDecimal();
one *= -1;
}
return ans;
}
public BigDecimal TAN()
{
return this.SIN().Divide(this.COS());
}
public BigDecimal ARCTAN()
{
BigInteger n = 0;
BigDecimal ans = new BigDecimal("0");
String last_decimal_value = string.Empty;
int one = 1;
while (true)
{
ans = ans.Add(new BigDecimal(one).Multiply(this.Pow((n * 2) + 1)).Divide(new BigDecimal((2 * n) + 1)));
//Console.WriteLine(ans.ToDecimal());
if (ans.ToDecimal().CompareTo(last_decimal_value) == 0)
break;
n++;
last_decimal_value = ans.ToDecimal();
//Console.WriteLine(Environment.TickCount - timer + "ms");
one *= -1;
}
return ans;
}
#endregion
#region Overrides
public override string ToString ()
{
return this.decimal_as_fraction[0] + "\r\n----\r\n" + this.decimal_as_fraction[1] + "\r\n";
}
#endregion
#region Helpers
public bool Equals(BigDecimal toCheck)
{
if (this.Fraction[0] == toCheck.Fraction[0] &&
this.Fraction[1] == toCheck.Fraction[1])
{
return true;
}
return false;
}
public bool IsNegative()
{
if( (this.decimal_as_fraction[0] < 0 && this.decimal_as_fraction[1] > 0) ||
(this.decimal_as_fraction[1] < 0 && this.decimal_as_fraction[0] > 0) )
{
return true;
}
return false;
}
public String ToDecimal()
{
//Console.WriteLine("------------------------");
//Console.WriteLine("Converting to Decimal");
//long start = System.Environment.TickCount;
BigInteger top = this.Fraction[0];
BigInteger bottom = this.Fraction[1];
if(top < 0)
{
top = (top * -1);
}
if(bottom < 0)
{
bottom = (bottom * -1);
}
BigInteger whole_number;
if(top >= bottom)
{
whole_number = top / bottom;
}
else
{
whole_number = 0;
}
BigInteger remainder = top - (whole_number * bottom);
int[] output = new int[BigDecimal.SCALE];
int index = 0;
int nextDigit = -1;
while(remainder != 0)
{
output[index] = ((remainder * 10) / bottom).IntValue();
remainder = (remainder * 10) % bottom;
index++;
if(index >= BigDecimal.SCALE)
{
nextDigit = ((remainder * 10) / bottom).IntValue();
break;
}
}
bool roundFixed = false;
if(nextDigit >= 5)
{
for(int i = (output.Length - 1); i >= 0; i --)
{
if(output
!= 9)
{
output++;
roundFixed = true;
break;
}
else
{
output = 0;
}
}
if(!roundFixed)
whole_number++;
}
StringBuilder decs = new StringBuilder();
foreach(int i in output)
{
decs.Append(i);
}
String retVal = (whole_number.ToString() + "." + decs.ToString()).Replace("-","").TrimEnd('0');
if(retVal.EndsWith("."))
{
retVal = retVal.Replace(".","");
}
//Console.WriteLine((System.Environment.TickCount - start) + "ms");
//Console.WriteLine("------------------------");
if(this.IsNegative())
return "-"+retVal;
else
return retVal;
}
public BigInteger WholeNumber()
{
return new BigInteger(this.ToDecimal().Split('.')[0],10);
}
public String ToHex()
{
String base10 = this.ToDecimal();
String[] parts = base10.Split('.');
return new BigInteger(parts[0],10).ToHexString() + "." + new BigInteger(parts[1],10).ToHexString();
}
public String ToHexFraction()
{
return this.Fraction[0].ToHexString() + "\r\n-------------\r\n" + this.Fraction[1].ToHexString();
}
public BigDecimal Invert()
{
return new BigDecimal(new BigInteger[] { this.Fraction[1], this.Fraction[0] });
}
#endregion
#region Pi
public static BigDecimal Pi(int loops)
{
BigInteger k = 0;
BigInteger big13 = 13591409;
BigInteger big54 = 545140134;
BigInteger big64 = 640320;
int switching_one = 1;
BigDecimal ans = new BigDecimal(0);
BigDecimal two_over_three = new BigDecimal(3).Divide(new BigDecimal(2));
while (loops > 0)
{
BigInteger three_times_k = (3 * k);
BigInteger top = ((switching_one * BigInteger.Factorial(6 * k)) * (big13 + (big54 * k)));
BigDecimal bottom = (new BigDecimal(BigInteger.Factorial(three_times_k) * BigInteger.Pow((BigInteger.Factorial(k)), 3))).Multiply(
(new BigDecimal(big64).Pow( new BigDecimal(three_times_k).Add(two_over_three))));
ans = ans.Add(new BigDecimal(top).Divide(bottom));
k++;
switching_one *= -1;
loops--;
}
ans = ans.Multiply(new BigDecimal(12));
ans = ans.Invert();
return ans;
}
#endregion
}
}
j1mb0jay