Domanda

Web app coded in PHP with a MySQL database.

I have a system which calculates different costs for a number of people when splitting a cost. For example Person A buys something for 10 and Persons B, C, and D should split the cost.

The system should therefor register a positive record for person A of 10 and negative records of 10/3 for B, C and D.

However, when this is done; B, C and D all have -3.33 after rounding. Which of course doesn't add up to the total of 10. What's the best way of going about this problem? An optimal solution would randomise what person get's the slightly bigger cost as well.

One possible solution is if I just let the last person's debt be 10 - (A + B), but then there's a problem if four persons split a cost of for example 13.34. The different parts would then be 3.34, 3.34, 3.34 and 3.32, whereas the optimal split would be 3.34, 3.34, 3.33, 3.33.

Some might argue that with sufficient decimals this is only a problem when having vast amounts of rows. But in an economical system I think it's important to have a fail-safe system even from the start. It needs to be scalable, and can't have even the slightest error. Unfairness is alright, just not errors.

Similar problem: sum divided values problem (dealing with rounding error)

È stato utile?

Soluzione

This seems to work - http://jsfiddle.net/nQakD/ .

Used jQuery as example, but if you know PHP you should be able to easily convert it to PHP. If you need also php code, tell me, I will wrote it for you.

I'll paste the code here also -

$(document).ready(function() {
    var price = 17.48, people = 4, payment = (price/people).toFixed(2), count=0;
    var payments = [];
    for(i = 0; i < people; i++) {
       payments.push(payment);   
    }

    if(payment*people != price) {
        var currentPayment = payment*people;

        $(payments).each(function() {
            if(currentPayment < price) {
                currentPayment = (currentPayment-this).toFixed(2);
                var newPayment = parseFloat(this)+0.01;
                payments[count] = newPayment.toFixed(2);
                currentPayment = parseFloat(currentPayment)+parseFloat(newPayment);
            }
            else if(currentPayment > price) {
                currentPayment = (currentPayment-this).toFixed(2);
                var newPayment = parseFloat(this)-0.01;
                payments[count] = newPayment.toFixed(2);
                currentPayment = parseFloat(currentPayment)+parseFloat(newPayment);
            }
            count++;
        });   

    }  
    $(payments).each(function() {
        $("#result").append("<b>"+this+"</b><br/>");
    });       
});​

EDIT:

And here is working php code -

$price = 13.34;
$people = 4;
$payment = (float)$price/$people;
$payment = 0.01 * (int)($payment*100);
$count = 0;
$payments = Array();
for($i = 0; $i < $people; $i++) {
    array_push($payments, $payment);
}
if($payment*$people != $price) {
    $currentPayment = $payment*$people;
    foreach($payments as $pay) {
        if($currentPayment < $price) {
            $currentPayment = $currentPayment-$pay;
            $currentPayment = 0.01 * (int)($currentPayment*100);               
            $newPayment = (float)$pay+0.01;
            $newPayment = 0.01 * (int)($newPayment*100);
            $payments[$count] = $newPayment;
            $currentPayment = (float)$currentPayment+$newPayment;
        }
        else if($currentPayment > $price) {
            $currentPayment = $currentPayment-$pay;
            $currentPayment = 0.01 * (int)($currentPayment*100);               
            $newPayment = (float)$pay-0.01;
            $newPayment = 0.01 * (int)($newPayment*100);
            $payments[$count] = $newPayment;
            $currentPayment = (float)$currentPayment+$newPayment;
        }
        $count++;
    }
}
foreach($payments as $payed) {
    echo '<b>'.$payed.'</b><br />';
}​​​

EDIT 2:

This should fix the js issue - http://jsfiddle.net/nQakD/ updated code above also.

EDIT 3:

Edited PHP code and JS code so it does work for all examples - http://jsfiddle.net/nQakD/ .

Altri suggerimenti

Maybe make a new "rounding balance" up for every user. If it was 10 to be split three ways, then each user would pay 3.34. Each user would also have 2/3 of a cent to their rounding balance. (this would have to be stored as text I presume) .

Next time the same thing happens, check the rounding balance.. They have 2/3 of a cent, so now you can take one third off (so they now have 1/3 of a cent in the rounding balance ) and only charge 3.33 .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top