0

I have the following code to divide an amount by a number and allocate the result as an amount that needs to be paid per month.

objData.month_per_amount = (Convert.ToDecimal(txtAmount.Value) / Convert.ToInt32(txtMonths.Value));

In a scenario example if I divide 13 by 3 and round off the result to 2 decimal places I get 4.33 for each month. But when I multiply 4.33 by 3 I am getting 12.99, which is not equivalent to 13. There is a discrepancy of 0.01. In this scenario how can I allocate like below:

month 1: 4.33 month 2: 4.33 month 3: 4.34

Hope I made it clear, the preferred code should only be executed if there is such a discrepancy, for example if 14 is to be divided by 2, we get 7 for each month and 7+7=14, so exactly the same figure we are getting here.

Hamza Zahir
  • 69
  • 1
  • 7
  • 1
    This is per standard arithmetic! If you round-off, you definitely lose precision. – Arghya C Oct 11 '15 at 07:44
  • You can apply `Math.Ceiling` to get closest whole number – Haseeb Asif Oct 11 '15 at 07:46
  • yes I know but the result have to be pin point accurate – Hamza Zahir Oct 11 '15 at 07:46
  • If you know that floating point math results in rounding errors, then you should state so clearly in your question, as well as explain what you've tried to do to compensate. Include [a good, _minimal_, _complete_ code example](https://stackoverflow.com/help/mcve) that shows clearly what you've tried, along with a clear, precise explanation of what that code does, how that's different from what you expected, and why. – Peter Duniho Oct 11 '15 at 07:49
  • 1
    Your `.month_per_amount` is just one `variable`/`field`. How can you have multiple values in that? like `4.33` and `4.34` ? – Arghya C Oct 11 '15 at 07:49
  • The maths you've explained is perfectly accurate. 13 divided by 3 is precisely 4.3 recurring. Rounding 4.3 recurring to 2 decimal places is 4.33 precisely. Multiplying 4.33 by 3 is 12.99 precisely. There is no discrepancy. – Enigmativity Oct 11 '15 at 07:50
  • If you want to distribute some amount over some period, and you want to account for rounding error, you need to add a variable to keep track of the remainder of the division, accumulating the error over time, and incorporating that into the result. Please try _something_ and ask a question with a more specific, clear problem statement as suggested above. – Peter Duniho Oct 11 '15 at 07:52
  • 1
    You say that you understand the math, but you want an uneven distribution of the rounding error, as in `4.33, 4.33, 4.34`. So maybe you want to ask, how can I distribute the rounding error unevenly so that the total is still exact? – Jeppe Stig Nielsen Oct 11 '15 at 07:52
  • by getting 12.99 I am loosing 0.01 from the original amount which is13, this is just the starting code, new code needs to identify if any loosing occurs and then give proper value for each month so that it adds up to exactly 13. – Hamza Zahir Oct 11 '15 at 07:55

4 Answers4

3

In accounting you'd often use something called 'reducing balance' for this. The idea is that you calculate the month's total, deduct it from the overall total and reduce the number of months. So something like:

decimal balance = 13m;
int months = 3;
int monthsRemaining = 3;

for (var i = 0; i < months; i++)
{
    decimal thisMonth = Math.Round(balance / monthsRemaining, 2);

    balance -= thisMonth;
    monthsRemaining -= 1;

    Console.WriteLine("Month {0}: {1}", i + 1, thisMonth);
}

This will result in 4.33, 4.34, 4.33.

The benefit of this method is that the rounding errors are distributed fairly evenly throughout the period rather than all in one month. For example, 100 over 24 months using that method would result in 23 payments of 4.17 and 1 of 4.09 whereas reducing balance would be 4.16 or 4.17 each month.

Charles Mager
  • 25,735
  • 2
  • 35
  • 45
1

You do not have to check the remainder. A more efficient C# code (in terms of the required computation) would be like the following.

double amount = 13;
int months = 3;
int precision = 2;

double[] amountForEachMonth = new double[months];

double temp = Math.Round(amount / months, precision);

for (int i = 0 ; i < months - 1 ; i++)
    amountForEachMonth[i] = temp;

amountForEachMonth[months - 1] = amount - (temp * (months - 1)) ;    
Hamed
  • 1,175
  • 3
  • 20
  • 46
  • 1
    Using `double` with this approach is not safe. It would be a bit better with `decimal`. The reason is that a `decimal` represents the result of `Round` with precision `2` ***exactly***. Then the last amount will be exact as well with `decimal`. With `double` as above, you just get other errors. – Jeppe Stig Nielsen Oct 11 '15 at 08:40
1

You don't need to make it a special case when there is a discrepancy, you can simply always calculate the payment of the last month as what's left to pay to reach the total amount. If there is no discrepancy then it will be the same value anyway. Example:

int months = Convert.ToInt32(txtMonths.Value);
decimal amount = Convert.ToDecimal(txtAmount.Value);
month_per_amount = Decimal.Round(amount / months, 2);
decimal last_month = amount - (months - 1) * month_per_amount;

for (int month = 1; month <= months; month++) {
  decimal monthly = month < months ? month_per_amount : last_month;
  Console.WriteLine("Month {0}: {1}", month, monthly);
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • This is a good and simple approach. Note that the last month can get an error that exceeds `0.01` (i.e. one cent/penny/etc.). For example with `months = 7` and `amount = 1000.00`, your algorithm gives `142.86, 142.86, 142.86, 142.86, 142.86, 142.86, 142.84`. Which may be just fine. But a more sophisticated algorithm would give the more uniform `142.86, 142.86, 142.86, 142.86, 142.86, 142.85, 142.85` where the error is distributed over more than one month. – Jeppe Stig Nielsen Oct 11 '15 at 08:49
0

if " amount % month == 0 " , no discrepancy occures. otherwise, the last item should be a little more than others . (The code here may have some syntax issues, I wanted to show you the algorithm.)

decimal amount = Convert.ToDecimal(txtAmount.Value);
int month = Convert.ToInt32(txtMonths.Value);
int n = 3;
decimal amounts[3];//n = 3
for (int i = 0 ; i < n-1 ; i++)
    amounts[i] = amount / month;
if ( amount % month != 0 ) { 
  amounts[n-1] = amount - ( amount / month * (n-1) )  ;
else
   amounts[n-1] = amount / month ;
Nabzi
  • 1,823
  • 1
  • 16
  • 26