Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct sums with dividing sums, countering rounding errors

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)

like image 941
Per Enström Avatar asked Jul 23 '12 11:07

Per Enström


1 Answers

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/ .

like image 60
y2ok Avatar answered Sep 22 '22 06:09

y2ok