Skip to content

Instantly share code, notes, and snippets.

@Ilshidur
Last active February 21, 2017 14:12
Show Gist options
  • Save Ilshidur/4f0a3590dde5f3f3c06be80fa39464ad to your computer and use it in GitHub Desktop.
Save Ilshidur/4f0a3590dde5f3f3c06be80fa39464ad to your computer and use it in GitHub Desktop.
[PHP] Total values rounding adjustment algorythm.
<?php
/*
* Rounds the given values and add them up in order to fix in the total value.
* It prevents differences of total values caused by the default rounding method.
* Do not use more than 2 decimals for accuracy purposes.
*
* Inspired of : http://stackoverflow.com/a/13483710/4022804
* This function adjusts the rounded values by adding up the ones that have the highest decimals.
*/
public function round($values, $maxValue, $decimals) {
// Round the max value :
// e.g.: 5.801 with 2 decimals => 5.80
$roundedMaxValue = round($maxValue, $decimals);
// Round each value down :
// e.g.:
// 1.234 with 2 decimals => 1.23
// 4.567 with 2 decimals => 4.56
$flooredTotals = [];
foreach ($values as $key => $value) {
$flooredTotals[$key] = floor($value * pow(10, $decimals)) / pow(10, $decimals);
}
// Use the floored values as a basis for the values add up
$roundedTotals = $flooredTotals;
// Calculate the difference caused by the rounding :
// 1.234 + 4.564 = 5.801
// Rounded : 1.23 + 4.56 = 5.80 ===> difference of 1 !
$roundingDifference = floor(round($roundedMaxValue * pow(10, $decimals)));
foreach ($roundedTotals as $key => $value) {
$roundingDifference -= floor(round($value * pow(10, $decimals)));
}
// Find which way the rounding must be done :
// (if the value must be added or substracted 1)
// $roundingSign === 1 if $roundingDifference is positive
// $roundingSign === -1 if $roundingDifference is negative
$roundingSign = ($roundingDifference > 0) - ($roundingDifference < 0);
// Find the N one(s) with the highest decimal values (on the right of the rounding) :
// 1.234 with 2 decimals => 1.23|4
// 4.567 with 2 decimals => 4.56|7 => has the highest value on the right of the rounding
$valuesToAdjust = [];
$valuesToTake = $values;
for ($i = 0; $i < $roundingDifference; $i++) {
if (!isset($valuesToAdjust[$i])) {
reset($valuesToTake);
$valuesToAdjust[$i] = key($valuesToTake);
}
foreach ($valuesToTake as $key => $value) {
// Compare the values on the right the decimals to round
// and save the value that must have a rounding adjustment
$loopValueDecimals = $valuesToTake[$valuesToAdjust[$i]] - floor($valuesToTake[$valuesToAdjust[$i]]);
$loopValueDecimalsAfterRounding = ($loopValueDecimals * pow(10, $decimals)) - floor($loopValueDecimals * pow(10, $decimals));
$valueDecimals = $value - floor($value);
$valueDecimalsAfterRounding = ($valueDecimals * pow(10, $decimals)) - floor($valueDecimals * pow(10, $decimals));
if ($valuesToTake[$key] !== 0) {
$valuesToAdjust[$i] = $key;
$valuesToTake[$key] = 0;
}
}
}
// Recalculate the rounding of the values to adjust
// (there is as much $valuesToAdjust as $roundingDifference(s))
foreach ($valuesToAdjust as $key => $value) {
$newDecimal = ((int)(($values[$value] - floor($values[$value])) * pow(10, $decimals)) + $roundingSign) / pow(10, $decimals);
$newValue = floor($values[$value]) + $newDecimal;
$roundedTotals[$value] = $newValue;
}
return $roundedTotals;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment