views:

351

answers:

4

Martin Fowler has a Money class that has a money allocation routine. This routine allocates money according to a given list of ratios without losing any value through rounding. It spreads any remainder value over the results.

For example, $100 allocated by the "ratios" (1, 1, 1) would yield ($34, $33, $33).

Here is the allocate function:

public long[] allocate(long amount, long[] ratios) {
    long total = 0;
    for (int i = 0; i < ratios.length; i++) total += ratios[i];

    long remainder = amount;
    long[] results = new long[ratios.length];
    for (int i = 0; i < results.length; i++) {
        results[i] = amount * ratios[i] / total;
        remainder -= results[i];
    }

    for (int i = 0; i < remainder; i++) {
        results[i]++;
    }

    return results;
}

(For the sake of this question, to make it simpler, I've taken the liberty of replacing the Money types with longs.)

The question is, how do I know it is correct? It all seems pretty self-evident except for the final for-loop. I think that to prove the function is correct, it would be sufficient to prove that the following relation is true in the final for-loop:

remainder < results.length

Can anyone prove that?

A: 

I'd say it's not correct because some curious ratio could cause a remainder greater then the number of results. Therefore I suggest results[i % results.length].amount++;.

Edit: I withdraw my answer. With longs there's no curious ratio and with floating point modulo doesn't help

DaClown
I would have to disagree. This curious ration is mathematically impossible.
Ivan Zlatanov
For the set of whole numbers there is no "curious" ratio.
James Anderson
Yes, for longs there isn't one. But the OP said longs are used only for the sake of simplicity in this example. And we all know that one should not compare double values without an error ratio. Therefore in a computer program curious ratios can occur, because of this error ratio
DaClown
Money is not _floating_ point. There is no "curious ratio" for a set of fixed-point numbers; integers are just a special case of fixed-point numbers.
MSalters
@DaClown you are right when you are not caring about the rounding and loss of data, but this is almost never the case when working with money. Doubles should not be used for money operations unless there are strictly specified rules and operators.
Ivan Zlatanov
"without losing any value through rounding". Sounds like floating point to me. But you're right, modulo doesn't help with floating point.
DaClown
@DaClown -- Been there done that and bought the t-shirt! A slight mis reading can prompt a very worng answer - Kudos for 'fessing up!
James Anderson
+1  A: 

No proof required.

The base amounts are allocated by simple division, rounding down. So the allocated amount will always be less than or equal to the total.

Remainder contains the unallocated amount. Which will always be a whole number less than 'i'. So he simply gives each receiver 1 until the money is gone.

James Anderson
that the allocated amount is <= the total is clear, but why is it guaranteed to be < than the number of ratios?
Mario
Its not <=, its just < ( 'less' ). If the allocated amount is equal then the devision would split the result perfectly, wouldn't it? 4 % 2 is not 1 with reminder 2 and never will be.
Ivan Zlatanov
If you allocate $99 with ratios (1,1,1), the first step of the algorithm will allocate 99, so the step allocated by the first step can indeed be equal to the total amount. But that's not really my question. What I don't understand is why the unallocated amount (in the first step) is less than #ratios.
Mario
@Mario, the arguments (1,1,1) really stand for 1/3,1/3,1/3 here. That's why I put "ratios" in quotes. The arguments are not strictly speaking ratios, but that is what the argument name was in the function.
dangph
+9  A: 

The key insight is that the total remainder is equal to the sum of the individual remainders when calculating each result[i].

Since each individual remainder is the result of rounding down, it is at most 1. There are results.length such remainders, so the total remainder is at most results.length.

EDIT: Obviously it's not a proof without some pretty symbols, so here are some...
alt text

stevemegson
yeah, that was the part I was missing. Thx.
Mario
+1. Note that the last `<` should be `=`, since `\sum_{i = 1}^k 1 = k`.
Stephan202
So it should - that's what I get for collapsing it onto fewer lines. It did originally say `remainder < k` when I wrote that `<`.
stevemegson
A: 

Simple

just use fact that

a=floor(a/b)*b+(a%b)

ralu