php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #65637 Using division PHP_ROUND_HALF_UP rounds DOWN when working with 2 precision
Submitted: 2013-09-09 00:24 UTC Modified: 2013-09-09 12:31 UTC
From: productivepc at hotmail dot com Assigned:
Status: Not a bug Package: Math related
PHP Version: 5.5.3 OS: Windows 7
Private report: No CVE-ID: None
 [2013-09-09 00:24 UTC] productivepc at hotmail dot com
Description:
------------
PHP Version 5.5.0
Results on Screen when precision is set to 2 for $additionalPcCostPerDay.

mainPcCost: 169
additionalPcCost: 3.3328571428571
WorkingDaysLeft: 16
totalWorkingDays: 21
mainPcCostPerDay: 8.05
additionalPcCostPerDay: 3.333
mainPcProratedCost: 128.8
additionalPcProratedCost: 53.33
totalProratedCost: 182.13

=============================================
Results on screen when I change the precision to 2.  Please notice everything 
has stayed the same but the totalProratedCost has changed.  
contracttermid: 2
businesstypeid: 1
mainPcCost: 169
additionalPcCost: 3.3328571428571
WorkingDaysLeft: 16
totalWorkingDays: 21
mainPcCostPerDay: 8.05
additionalPcCostPerDay: 3.33
mainPcProratedCost: 128.8
additionalPcProratedCost: 53.28
totalProratedCost: 182.08

I noticed something because I have this same thing programmed in excel.  I 
decided to add the ROUNDUP function to my excel document just like I have it in 
PHP to ensure that everything worked exactly the same and I noticed that when I 
went to 2 decimals excel actually changed the additionalPcCostPerDay to 3.34 
instead of keeping it 3.33 therefore raising the price to 182.24 and PHP brought 
the price down to 182.08 instead of keeping it 182.13.  Looking in to this 
further, if I use the ROUNDDOWN function 
within excel then it too brings the price to 182.08 which is what makes me 
believe that the PHP function PHP_ROUND_HALF_UP is actually ROUNDING DOWN 
instead of UP.  The 
expected behavior for both excel and PHP would be to leave the price at 182.13.  
When I use 3 decimals for both then both PHP and Excel agree that the 
totalProratedPrice is 182.13.  I am not sure if this is a bug however I wanted 
to report it here before making a comment on the ROUND page with an example of 
this.

The example below I do not have settype() in it however I have attempted this by 
changing everything over to a float with settype before I did any math and the 
result was still the same.

Wayne

Test script:
---------------
$workingDaysLeft = 16;
$totalWorkingDays = 21;
$mainPcCost = 169;
$additionalPcCost = 3.3328571428571;
$mainPcCostPerDay = round($mainPcCost/$totalWorkingDays,2, PHP_ROUND_HALF_UP);
$additionalPcCost = $additionalPcCost/$totalWorkingDays;

// The next line is the problem area.  When I change it to 2 precision the expected behavior is for the total prorated cost to stay at 182.13 however it drops to 182.08.  If you change the below line to PHP_ROUND_HALF_DOWN then you also get 182.08 which would indicate that the behavior is not behaving as expected.
$additionalPcCostPerDay = round($additionalPcCost,3,PHP_ROUND_HALF_UP);

$mainPcProratedCost = round($mainPcCostPerDay*$workingDaysLeft,2,PHP_ROUND_HALF_UP);
$additionalPcProratedCost = round($additionalPcCostPerDay*$workingDaysLeft,2,PHP_ROUND_HALF_UP);
$totalProratedCost = number_format($mainPcProratedCost+$additionalPcProratedCost,2,'.',',');
echo "<br>mainPcCost: " . $mainPcCost . "<br>additionalPcCost: " . $additionalPcCost;
echo "<br>WorkingDaysLeft: " . $workingDaysLeft . "<br>totalWorkingDays: " . $totalWorkingDays;
echo "<br>mainPcCostPerDay: " . $mainPcCostPerDay . "<br>additionalPcCostPerDay: " . $additionalPcCostPerDay;
echo "<br>mainPcProratedCost: " . $mainPcProratedCost . "<br>additionalPcProratedCost: " . $additionalPcProratedCost;
echo "<br>totalProratedCost: " . $totalProratedCost;

Expected result:
----------------
Whether I have precision for $additionalPcCostPerDay set to 2 or 3 the 
totalProratedCost should remain the 182.13 and not change to 182.08.


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2013-09-09 02:48 UTC] requinix@php.net
-Status: Open +Status: Feedback
 [2013-09-09 02:48 UTC] requinix@php.net
The script you've provided doesn't output the example numbers you gave. I get

additionalPcCostPerDay: 0.159 or 0.16
additionalPcProratedCost: 2.54 or 2.56
totalProratedCost: 131.34 or 131.36

Want to try the repro script again? Hopefully something a bit smaller with fewer 
variables?
 [2013-09-09 02:57 UTC] requinix@php.net
And by the way, you know that rounding numbers too early will throw off future 
calculations, right? 1.45 * 1000 = 1450 but roundup(1.45, 1) * 1000 = 1500. Don't 
round until the very end.
 [2013-09-09 05:34 UTC] productivepc at hotmail dot com
In your example, you are rounding to 1 decimal place and yes absolutely that will 
throw it off however if you do =PRODUCT(ROUNDUP(1.45,2),1000) = 1450. I removed 
all rounding except for the $additionalPcCostPerDay = 
round($additionalPcCost/$totalWorkingDays,3,PHP_ROUND_HALF_UP); and the error 
still happens.  When I attempt to round to 2 decimal places the rounding appears 
to round down and not up.  When I do it to 3 then it works as expected.  It is 
1:33am.  I will write a smaller script tomorrow after I sleep and post it here.
 [2013-09-09 05:49 UTC] productivepc at hotmail dot com
I went ahead and wrote it before I went to bed.  The only thing you have to 
change is the 3 to a 2 in this line:  $additionalPcCostPerDay = 
round($additionalPcCost/$totalWorkingDays,3,PHP_ROUND_HALF_UP);
You will see the price go from 182.09 when it is looking at 3 decimal places to 
182.04 when it is looking at 2 decimal places.  When rounding up even if the 
number previously was a 6 I would expect it to go to 182.10 and not drop $.05.

<?php
	$workingDaysLeft = 16;
	$totalWorkingDays = 21;
	$mainPcCost = 169;
	$additionalPcCost = 69.99;
	$additionalpcs = 1;
	$mainPcCostPerDay = $mainPcCost/$totalWorkingDays;
	$additionalPcCostPerDay = 
round($additionalPcCost/$totalWorkingDays,3,PHP_ROUND_HALF_UP);
	$mainPcProratedCost = $mainPcCostPerDay*$workingDaysLeft;
	$additionalPcProratedCost = $additionalPcCostPerDay*$workingDaysLeft;
	$totalProratedCost = 
number_format($mainPcProratedCost+$additionalPcProratedCost,2,'.',',');
	$costPerFullMonth = $mainPcCost+($additionalpcs*$additionalPcCost);
//	echo "<br>mainPcCost: " . $mainPcCost . "<br>additionalPcCost: " . 
$additionalPcCost;
//	echo "<br>additionalpcs: " . $additionalpcs;
//	echo "<br>WorkingDaysLeft: " . $workingDaysLeft . "<br>totalWorkingDays: 
" . $totalWorkingDays;
//	echo "<br>mainPcCostPerDay: " . $mainPcCostPerDay . "
<br>additionalPcCostPerDay: " . $additionalPcCostPerDay;
//	echo "<br>mainPcProratedCost: " . $mainPcProratedCost . "
<br>additionalPcProratedCost: " . $additionalPcProratedCost;
	echo "<br>totalProratedCost: " . $totalProratedCost;// . "
<br>costPerFullMonth: " . $costPerFullMonth;
?>
 [2013-09-09 06:59 UTC] requinix@php.net
-Status: Feedback +Status: Not a bug
 [2013-09-09 06:59 UTC] requinix@php.net
$additionalPcCostPerDay is either 3.33 (rounded to two) or 3.333 (rounded to 
three). The actual value is ~3.3328. The rounding there is correct.

As for 0.05 drop, the difference "lost" using two decimal places is -0.003 * 16 = 
-0.048 and you round that off to -0.05. So that's where it came from. It sounds 
like you expect the rounding to carry from one number to the next? That's not how 
rounding works. That's why you always round at the very end of the set of 
calculations.
 [2013-09-09 12:31 UTC] productivepc at hotmail dot com
Okay.  Thanks for looking in to it.  This can be closed.
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Thu Feb 27 15:01:28 2020 UTC