views:

503

answers:

8

I need to write an accounting routine for a program I am building that will give me an even division of a decimal by an integer. So that for example:

$143.13 / 5 =

28.62
28.62
28.63
28.63
28.63

I have seen the article here: http://stackoverflow.com/questions/577427/evenly-divide-in-c, but it seems like it only works for integer divisions. Any idea of an elegant solution to this problem?

A: 

You can use the algorithm in the question you're referencing by multipling by 100, using the integer evenly divide function, and then dividing each of the results by 100 (assuming you only want to handle 2 dp, if you want 3dp multiple by 1000 etc)

justinlatimer
As with Nick's response, this will lose a couple of cents (in the given example)
danben
+1  A: 

If you have a float that is guaranteed exactly two digits of precision, what about this (pseudocode):

amount = amount * 100 (convert to cents)
int[] amounts = new int[divisor]
for (i = 0; i < divisor; i++) amounts[i] = amount / divisor
extra = amount % divisor
for (i = 0; i < extra; i++) amounts[i]++

and then do whatever you want with amounts, which are in cents - you could convert back to floats if you absolutely had to, or format as dollars and cents.

If not clear, the point of all this is not just to divide a float value evenly but to divide a monetary amount as evenly as possible, given that cents are an indivisible unit of USD. To the OP: let me know if this isn't what you wanted.

danben
float is bad bad bad... should never recommend float for money
0A0D
It's not my fault that the OP says he is starting with floats. If you had bothered to read my response, rather than blindly modding down, you would see that I immediately converted to int.
danben
A: 

Take a look at this question: http://stackoverflow.com/questions/693372/what-is-the-best-data-type-to-use-for-money-in-c

Wait a second! You want to make sure that not a single cent gets lost, right?

Hamish Grubijan
+11  A: 

Calculate the amounts one at a time, and subtract each amount from the total to make sure that you always have the correct total left:

decimal total = 143.13m;
int divider = 5;
while (divider > 0) {
  decimal amount = Math.Round(total / divider, 2);
  Console.WriteLine(amount);
  total -= amount;
  divider--;
}

result:

28,63
28,62
28,63
28,62
28,63
Guffa
thats a nice solution
Anurag
Very cool! Of course, slap on the Contract.Requires(total > = 0), ... divider > 0 or whatever the syntax is to make this even neater. And then I would unit-test this thing to death ... does not hurt. Unit tests need not run fast, so get an answer, then sort it, and compare against the expected result. Also, I would yield return the amount instead of printing it in the actual implementation. I would add another function which returns the result as an array of ... decimals, I suppose. Since you know the divider, you can allocate the array of the exact size needed.
Hamish Grubijan
Thanks, that looks like a nice, elegant solution!
Amberite
Simple but ineffective for large dividers.
Immortal
@Immortal: Yes, for just getting the values it's ineffective to do it in a loop, but if you are going to loop through them anyway it's quite effective.
Guffa
+9  A: 

You can solve this (in cents) without constructing an array:

int a = 100 * amount;
int low_value = a / n;
int high_value = low_value + 1;
int num_highs = a % n;
int num_lows = n - num_highs;
Marcelo Cantos
No idea why I was the first to up-vote. I think this is the most intuitive and efficient way to do this.
Wallacoloo
+1, this is my answer but better.
danben
+1  A: 

It's easier to deal with cents. I would suggest that instead of 143.13, you divide 14313 into 5 equal parts. Which gives you 2862 and a remainder of 3. You can assign this remainder to the first three parts or any way you like. Finally, convert the cents back to dollars.

Also notice that you will always get a remainder less than the number of parts you want.

fsm
+1  A: 

First of all, make sure you don't use a floating point number to represent dollars and cents (see other posts for why, but the simple reason is that not all decimal numbers can be represented as floats, e.g., $1.79).

Here's one way of doing it:

decimal total = 143.13m;
int numberOfEntries = 5;
decimal unadjustedEntryAmount = total / numberOfEntries;
decimal leftoverAmount = total - (unadjustedEntryAmount * numberOfEntries);
int numberOfPenniesToDistribute = leftoverAmount * 100;
int numberOfUnadjustedEntries = numberOfEntries - numberOfPenniesToDistribute;

So now you have the unadjusted amounts of 28.62, and then you have to decide how to distribute the remainder. You can either distribute an extra penny to each one starting at the top or at the bottom (looks like you want from the bottom).

for (int i = 0; i < numberOfUnadjustedEntries; i++) {
  Console.WriteLine(unadjustedEntryAmount);
}

for (int i = 0; i < numberOfPenniesToDistribute; i++) {
  Console.WriteLine(unadjustedEntryAmount + 0.01m);
}

You could also add the entire remainder to the first or last entries. Finally, depending on the accounting needs, you could also create a separate transaction for the remainder.

Ted M. Young
A: 

It is also possible to use C# iterator generation to make Guffa's answer more convenient:

public static IEnumerable<decimal> Divide(decimal amount, int numBuckets)
{
    while(numBuckets > 0)
    {
        // determine the next amount to return...
        var partialAmount = Math.Round(amount / numBuckets, 2);
        yield return partialAmount;
        // reduce th remaining amount and #buckets
        // to account for previously yielded values
        amount -= partialAmount;
        numBuckets--;
    }
}
LBushkin