Last active
February 21, 2017 14:12
-
-
Save Ilshidur/4f0a3590dde5f3f3c06be80fa39464ad to your computer and use it in GitHub Desktop.
[PHP] Total values rounding adjustment algorythm.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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