Can anyone explain this code's output?
<?php $x = (15/100) * 10.7; $y = 1.605; var_dump($x, $y, round($x,2), round($y,2),round(1.605,2)); ?>
Result: float(1.605) float(1.605) float(1.6) float(1.61) float(1.61)
In other words there is a difference between the values of $x and $y where they should both be 1.605 (and var_dump shows them both to be 1.605).
I assume this is because $x isn't really 1.605, but something very close to (and slightly below) 1.605 due to a rounding issue, so rounding to 2 decimal places causes it to round down not up. But I'm not sure how to change the code to give the correct results?
(15/100 you may notice as VAT; this is a VAT calculation, therefore I'm kinda duty bound to get the right result!)
Mark Rogers wrote:
I assume this is because $x isn't really 1.605, but something very close to (and slightly below) 1.605 due to a rounding issue, so rounding to 2 decimal places causes it to round down not up. But I'm not sure how to change the code to give the correct results?
Something I have found that works here is: round( round($x, 3) , 2) .. which forces the value to "round" to 1.605 then to 1.61, but I'm not sure if this would be a general solution (I'm guessing there are values of $x this would fail for, and maybe even values of $x where round($x,2) would be right but the 2-stage rounding would be wrong).
On Mon, 2009-10-12 at 11:08 +0100, Mark Rogers wrote:
Can anyone explain this code's output?
<?php $x = (15/100) * 10.7; $y = 1.605; var_dump($x, $y, round($x,2), round($y,2),round(1.605,2)); ?>
Result: float(1.605) float(1.605) float(1.6) float(1.61) float(1.61)
In other words there is a difference between the values of $x and $y where they should both be 1.605 (and var_dump shows them both to be 1.605).
I assume this is because $x isn't really 1.605, but something very close to (and slightly below) 1.605 due to a rounding issue, so rounding to 2 decimal places causes it to round down not up. But I'm not sure how to change the code to give the correct results?
(15/100 you may notice as VAT; this is a VAT calculation, therefore I'm kinda duty bound to get the right result!)
As you suspect, it's because 15/100 and 10.7 (and 1.605 infact) cannot be exactly represented in IEEE 784 format binary floating point. Thus the calculated value is very slightly below 1.605 and is thus rounded down whereas the constant 1.605 is very slightly above (this is just how the compiler chooses the represent the value of course) and gets rounded up.
Borland had a solution to this problem 25 years ago: they produced a version of Turbo Pascal that worked in binary coded decimal (BCD) instead of base 2, and thus has no problem with decimal representation or calculations. A similar solution is in use today in packages like Sage... perhaps you can find a BCD maths library or compiler switch?
P.
On Mon, Oct 12, 2009 at 11:19 AM, Phil Ashby phil.ashby@bt.com wrote:
On Mon, 2009-10-12 at 11:08 +0100, Mark Rogers wrote:
Can anyone explain this code's output?
<?php $x = (15/100) * 10.7; $y = 1.605; var_dump($x, $y, round($x,2), round($y,2),round(1.605,2)); ?>
Result: float(1.605) float(1.605) float(1.6) float(1.61) float(1.61)
As you suspect, it's because 15/100 and 10.7 (and 1.605 infact) cannot be exactly represented in IEEE 784 format binary floating point. Thus the calculated value is very slightly below 1.605 and is thus rounded down whereas the constant 1.605 is very slightly above (this is just how the compiler chooses the represent the value of course) and gets rounded up.
Borland had a solution to this problem 25 years ago: they produced a version of Turbo Pascal that worked in binary coded decimal (BCD) instead of base 2, and thus has no problem with decimal representation or calculations. A similar solution is in use today in packages like Sage... perhaps you can find a BCD maths library or compiler switch?
I am so sorry, I did my replies straight to Mark and not to the group: You are absolutely correct, PHP's manual states quite clearly a warning for 0.7 in particular (http://us2.php.net/manual/en/language.types.float.php ) and gives a link to their higher precisions BC library: http://us2.php.net/manual/en/ref.bc.php
Also I have suggested not using floats to represent currency instead representing by integer of lowest denomination (i.e. pence:
$x = 10.7 * 100; $x = $x * .15 / 100; var_dump( round($x,2));
produces float(1.61)
JT
On 12 Oct 11:08, Mark Rogers wrote:
Can anyone explain this code's output?
<?php $x = (15/100) * 10.7; $y = 1.605; var_dump($x, $y, round($x,2), round($y,2),round(1.605,2)); ?>
Result: float(1.605) float(1.605) float(1.6) float(1.61) float(1.61)
In other words there is a difference between the values of $x and $y where they should both be 1.605 (and var_dump shows them both to be 1.605).
Can I make a simple suggestion...
Rather than doing the maths in the order that you have in $x do: $x = 10.7 * 15 * 0.01;
I.e. you're doing the division at the end, rather than the beginning, and so the float is "more accurate".
Hope that helps,
Brett Parker wrote:
Can I make a simple suggestion...
Rather than doing the maths in the order that you have in $x do: $x = 10.7 * 15 * 0.01;
I.e. you're doing the division at the end, rather than the beginning, and so the float is "more accurate".
Ah, good point. That's probably why I don't see this all that often; I normally do things that way around for precisely that reason.
Given that the price (10.7) will always be to a maximum of 2 decimal places, how confident can I be that I won't find other cases that fail even though this one now works?
On 12 Oct 12:42, Mark Rogers wrote:
Brett Parker wrote:
Can I make a simple suggestion...
Rather than doing the maths in the order that you have in $x do: $x = 10.7 * 15 * 0.01;
I.e. you're doing the division at the end, rather than the beginning, and so the float is "more accurate".
Ah, good point. That's probably why I don't see this all that often; I normally do things that way around for precisely that reason.
Given that the price (10.7) will always be to a maximum of 2 decimal places, how confident can I be that I won't find other cases that fail even though this one now works?
Well, how about giving a range that we can play in, and then I can tell you ;)
Of course, I'm assuming that you're actually pulling these from a database engine, so you could just get that to do the maths... you shouldn't *ever* hardcode VAT rate (as Jenny found out with her invoicing system ;)
Cheers,
Can I make a simple suggestion...
Rather than doing the maths in the order that you have in $x do: $x = 10.7 * 15 * 0.01;
I.e. you're doing the division at the end, rather than the beginning, and so the float is "more accurate".
I'm not sure this will always be true. If you separated it out and put brackets in then maybe you could force it. I'm not sure how PHP would interpret and its order of execution on these.
Ah, good point. That's probably why I don't see this all that often; I normally do things that way around for precisely that reason.
Of course you run the risk of going buffer *overflow* on the left - for very big shopping baskets of course!
Of course, I'm assuming that you're actually pulling these from a database engine, so you could just get that to do the maths... you shouldn't *ever* hardcode VAT rate (as Jenny found out with her invoicing system ;)
The only other suggestion I would have is why do you have the vat as a fraction (15/100) ? - if its a constant (which it is (bearing in mind iDunno's hardcode comment) wouldnt it be better to have it as 0.15 and :. reducing one calculation by the PHP?
No ones said it yet, so I will. Use a different language!!!
JT
James Taylor wrote:
Rather than doing the maths in the order that you have in $x do: $x = 10.7 * 15 * 0.01;
I'm not sure this will always be true. If you separated it out and put brackets in then maybe you could force it. I'm not sure how PHP would interpret and its order of execution on these.
Well: $x = 10.7 * 15; $x *= 0.01; .. would be a good way to ensure the order of the calculation. (I wouldn't personally even trust parenthesis because you never quite know what the intermediate variable types will be.)
The only other suggestion I would have is why do you have the vat as a fraction (15/100) ? - if its a constant (which it is (bearing in mind iDunno's hardcode comment) wouldnt it be better to have it as 0.15 and :. reducing one calculation by the PHP?
The code I posted was simplified from the real code; the VAT rate is stored in a database along with a host of other settings.
No ones said it yet, so I will. Use a different language!!!
Maybe different commerce package (but that's easier said than done). I don't think PHP is the problem here (do other languages[*] have better solutions?)
[*] By which I means languages well suited to web applications, like Perl, Python, Java, etc. I don't know of any Cobol modules for Apache and wouldn't have a clue how to get started building a website with it!
This seems to work:
function safe_round($x, $dp) { $x *= pow(10,$dp); $x = explode('.',(string)$x); $res = (int) $x[0]; if ( (count($x)>1) && ($x[1][0] >= 5) ) { $res++; } return $res/100; }
In other words: safe_round(1.605,2) stepped through gives 1.605 * 10^2 = 160.5 160.5 => "160.5" => ("160","5") $res = 160 if something after "." and the first digit of that is 5 or higher (it is here): $res++ (=161) return $res/100 = 1.61
This relies on the fact that PHP seems to "work" when you convert the float to a string.
Any reasons why this shouldn't work in general? ($x<0 would be a test case I haven't considered).
As a general point re this VAT rounding lark - the VAT rules say that you're allowed to *truncate* fractions of a penny. In other words if the VAT on an item you sell is anywhere between (say) 95.00000001 pence and 95.99999999 pence then the amount you have to send the VAT man is 95p.
*Lots* of accounting packages don't follow this rule though, I regularly had to adjust my invoices when I was a contractor because the agency paid me 1p more than my (truncating) software said was due.
On Mon, Oct 12, 2009 at 2:27 PM, Chris G cl@isbd.net wrote:
As a general point re this VAT rounding lark - the VAT rules say that you're allowed to *truncate* fractions of a penny. In other words if the VAT on an item you sell is anywhere between (say) 95.00000001 pence and 95.99999999 pence then the amount you have to send the VAT man is 95p.
*Lots* of accounting packages don't follow this rule though, I regularly had to adjust my invoices when I was a contractor because the agency paid me 1p more than my (truncating) software said was due.
I am not a lawyer or accountant, but from google (and Ive pasted the section below).
From HMRC: http://customs.hmrc.gov.uk/channelsPortalWebApp/channelsPortalWebApp.portal?...
I take this to mean you can do whatever you like as long as you're consistent - but you can only round down the TOTAL not the individual items (which might equate to more then a penny lost (consider 10 * 0.9p items = 0 p if rounded down separately and 9p if rounded at the end))
JT
----- Paste start -----
17.5 Calculation of VAT on invoices – rounding of amounts
Note: The concession in this paragraph to round down amounts of VAT is designed for invoice traders and applies only where the VAT charged to customers and the VAT paid to Customs and Excise is the same. As a general rule, the concession to round down is not appropriate to retailers, who should see paragraph 17.6.
You may round down the total VAT payable on all goods and services shown on a VAT invoice to a whole penny. You can ignore any fraction of a penny.
17.5.1 Calculation based on lines of goods or services
If you wish to work out the VAT separately for a line of goods or services, which are included with other goods or services in the same invoice, you should calculate the separate amounts of VAT either by rounding:
down to the nearest 0.1p - for example, 86.76p would be rounded down to 86.7p; or to the nearest 1p or 0.5p - for example, 86.76p would be rounded up to 87p. Whatever you decide, you must be consistent.
The final total amount of VAT payable may be rounded down to the nearest whole penny.
17.5.2 Calculation based on tax per unit or per article
If you want to work out the VAT per unit or per article (for example, for use in price lists), you must work out the amounts of VAT either to:
4 digits after the decimal point and then round to 3 digits - for example, if the VAT is £0.0024, it should be rounded to £0.002 (0.2p); or the nearest 1p or 0.5p. If you decide to do this, you must not round the VAT down to “nil” on any unit or article that is liable at the standard or reduced rate - for example, if the VAT is £0.0024, it should be rounded to £0.005 (0.5p).
On Mon, Oct 12, 2009 at 02:37:01PM +0100, James Taylor wrote:
On Mon, Oct 12, 2009 at 2:27 PM, Chris G cl@isbd.net wrote:
As a general point re this VAT rounding lark - the VAT rules say that you're allowed to *truncate* fractions of a penny. In other words if the VAT on an item you sell is anywhere between (say) 95.00000001 pence and 95.99999999 pence then the amount you have to send the VAT man is 95p.
*Lots* of accounting packages don't follow this rule though, I regularly had to adjust my invoices when I was a contractor because the agency paid me 1p more than my (truncating) software said was due.
I am not a lawyer or accountant, but from google (and Ive pasted the section below). From HMRC: http://customs.hmrc.gov.uk/channelsPortalWebApp/channelsPortalWebApp.portal?...
I take this to mean you can do whatever you like as long as you're consistent - but you can only round down the TOTAL not the individual items (which might equate to more then a penny lost (consider 10 * 0.9p items = 0 p if rounded down separately and 9p if rounded at the end))
Yes, you're quite right, that was what applied in my case of course because the invoices I issued were for a single 'item'.
.... but note that the remaining complexities apply only "If you wish to work out the VAT separately for a line of goods.....". Thus if you simply add up the total cost of the invoice you can then add the VAT as a single item (truncationg if you want). This is what nearly all of the invoices I have ever seen do, you rarely see VAT itemised for each line of the invoice.
JT
----- Paste start -----
17.5 Calculation of VAT on invoices – rounding of amounts
Note: The concession in this paragraph to round down amounts of VAT is designed for invoice traders and applies only where the VAT charged to customers and the VAT paid to Customs and Excise is the same. As a general rule, the concession to round down is not appropriate to retailers, who should see paragraph 17.6.
You may round down the total VAT payable on all goods and services shown on a VAT invoice to a whole penny. You can ignore any fraction of a penny.
17.5.1 Calculation based on lines of goods or services
If you wish to work out the VAT separately for a line of goods or services, which are included with other goods or services in the same invoice, you should calculate the separate amounts of VAT either by rounding:
down to the nearest 0.1p - for example, 86.76p would be rounded down to 86.7p; or to the nearest 1p or 0.5p - for example, 86.76p would be rounded up to 87p. Whatever you decide, you must be consistent.
The final total amount of VAT payable may be rounded down to the nearest whole penny.
17.5.2 Calculation based on tax per unit or per article
If you want to work out the VAT per unit or per article (for example, for use in price lists), you must work out the amounts of VAT either to:
4 digits after the decimal point and then round to 3 digits - for example, if the VAT is £0.0024, it should be rounded to £0.002 (0.2p); or the nearest 1p or 0.5p. If you decide to do this, you must not round the VAT down to “nil” on any unit or article that is liable at the standard or reduced rate - for example, if the VAT is £0.0024, it should be rounded to £0.005 (0.5p).
main@lists.alug.org.uk http://www.alug.org.uk/ http://lists.alug.org.uk/mailman/listinfo/main Unsubscribe? See message headers or the web site above!
This may be irrelevant for the aqpplication being considered, but if things are being done on Linux there is the program 'bc', which does its arithmetic using an internal *decimal* representation, and to an arbitrary precision (truncating, rather than rounding, for the purpose of returning numbers to a given number of decimal disgits).
You set the precision (number of digits after the decimal point) by giving a value to the 'scale' variable. Therefore I would expect that (given the VAT specification to work to 4 d.p.) you could use 'bc' with scale=5 or scale=6, and then doing a bit of extra tweaking to evaluate the rounded value.
One more comment (on a point which does not seem to have been mentioned). "Rounding" is usually understood to mean "return the nearest "integer" at the right-hand end of the return range, so that (e.g.)
round(1.234, 1) -> 1.2. round(1.234, 2) -> 1.23 round(1.23456, 2) -> 1.23 round(1.23456, 3) -> 1.235
But what about
round(1.2345, 3)??
Is this 1.234 or 1.235? There are different conventions. A: Round "5" up: round(1.2345, 3) -> 1.235 round(1.2355, 3) -> 1.236
B: Round "5" down: round(1.2345, 3) -> 1.234 round(1.2355, 3) -> 1.235
C: Round "5" to the nearest *even* result: round(1.2345, 3) -> 1.234 round(1.2355, 3) -> 1.236
(You can never get an odd last digit with method C). This is the method of the IEC 60559 standard.
The VAT rules seem to say that *any* fraction (in the final result) may be rounded down, though there seem to be ifs & buts about it! This is rounding by truncation (as also done in 'bc').
Just a few thoughts ... Ted.
-------------------------------------------------------------------- E-Mail: (Ted Harding) Ted.Harding@manchester.ac.uk Fax-to-email: +44 (0)870 094 0861 Date: 12-Oct-09 Time: 15:15:28 ------------------------------ XFMail ------------------------------
2009/10/12 Chris G cl@isbd.net
As a general point re this VAT rounding lark - the VAT rules say that you're allowed to *truncate* fractions of a penny. In other words if the VAT on an item you sell is anywhere between (say) 95.00000001 pence and 95.99999999 pence then the amount you have to send the VAT man is 95p.
How do 99p shops operate (e.g. http://www.99pstoresltd.com/)? If something costs pre-VAT 86p, VAT = 12p, retail price = 98p If something costs pre-VAT 87p, VAT = 13p, retail price = £1.00
Greg
Brett Parker wrote:
Well, how about giving a range that we can play in, and then I can tell you ;)
Well the site will sell products from a few pence up to a few hundred pounds, and people buy between one and several hundred items at a time. VAT is obviously within a fairly narrow range (15-17.5%).
Of course, I'm assuming that you're actually pulling these from a database engine, so you could just get that to do the maths... you shouldn't *ever* hardcode VAT rate (as Jenny found out with her invoicing system ;)
The problem I have is that I'm trying to fix one problem in a large system so I don't get to re-make initial design decisions like these, and no, at present these are not coming directly from a database. VAT is not hardcoded, and the system doesn't even assume that the number of decimal places is always going to be 2 (which is part of the problem - if it were always 2 then making a design decision to doing everything in pence/cents would probably have been made by the original developers).
The cart I'm working with (ZenCart) has some very weird and wonderful ideas when it comes to calculating the total of a shopping cart (particularly when discounts come into play). I'd dearly like to be in a position to rewrite it!
Mark Rogers wrote:
The cart I'm working with (ZenCart) has some very weird and wonderful ideas when it comes to calculating the total of a shopping cart (particularly when discounts come into play). I'd dearly like to be in a position to rewrite it!
I've seen it, most recently when consulting on some server break-in a week or two ago. Getting you into VAT problems is only one of a large number of reasons why I'd look for an exit plan. Ideally, I'd get out before the VAT changes back in December, but if it's not started yet, that's probably going to be tricky.
So, what ecommerce packages do ALUGgers like? I think our co-op likes http://www.satchmoproject.com/ which uses a decimal data type for the money.
Curious,
2009/10/12 Brett Parker iDunno@sommitrealweird.co.uk:
Of course, I'm assuming that you're actually pulling these from a database engine, so you could just get that to do the maths... you shouldn't *ever* hardcode VAT rate (as Jenny found out with her invoicing system ;)
Pah, it had been the same for donkey's years: they changed it just to spite me.
Jenny
On Wed, Oct 14, 2009 at 09:38:27PM +0100, Jenny Hopkins wrote:
2009/10/12 Brett Parker iDunno@sommitrealweird.co.uk:
Of course, I'm assuming that you're actually pulling these from a database engine, so you could just get that to do the maths... you shouldn't *ever* hardcode VAT rate (as Jenny found out with her invoicing system ;)
Pah, it had been the same for donkey's years: they changed it just to spite me.
:-)
It was 17.5% for so long that it took me a while to *find* it in my accounting system. ..... but it was in the database, each transaction type (for analysis) has its VAT rate.
Mark Rogers wrote:
Can anyone explain this code's output?
<?php $x = (15/100) * 10.7; $y = 1.605; var_dump($x, $y, round($x,2), round($y,2),round(1.605,2)); ?>
Result: float(1.605) float(1.605) float(1.6) float(1.61) float(1.61)
In other words there is a difference between the values of $x and $y where they should both be 1.605 (and var_dump shows them both to be 1.605).
I assume this is because $x isn't really 1.605, but something very close to (and slightly below) 1.605 due to a rounding issue, so rounding to 2 decimal places causes it to round down not up. But I'm not sure how to change the code to give the correct results?
(15/100 you may notice as VAT; this is a VAT calculation, therefore I'm kinda duty bound to get the right result!)
Hi, I'm coming to this discussion a bit late but the following may help.
It's an old COBOL dodge that is still used in many systems to fix rounding problems in currency calculations.
Do no rounding at all during the calculation but use the best precision you can. Then just before the final stage where you round the result add half a penny.
So your code above would become:
$x = ((15/100) * 10.7) + 0.005); $y = 1.605; var_dump($x, $y, round($x,2), round($y,2),round(1.605,2));
which should give: float(1.61) float(1.605) float(1.61) float(1.61) float(1.61)
It may amuse you to see the calculation of (15/100) * 10.7 done in 32 bit binary where the rounding issue becomes obvious.
(15/100)*10.7 -> (1111 / 110010) * 1010.1001100110011001100110011001 because of binary rounding this becomes (15/100)*10.699951171875 and (0.15) -> 0.0010011001100110011001100110011 which is 0.14996337890625 0.0010011001100110011001100110011 * 1010.1001100110011001100110011001 (I'm not going to do that one! but it gives a decimal result of: 1.60460083186626434326
note that both 0.15 and 10.7 can not be represented in binary as they create recurring values after the binary point.
and now you can see that $x = 1.60460083186626434326 so round($x,2) is correctly 1.6
using the half penny fixup would give $x = 1.60960083186626434326 so round($x,2) would now be 1.61 as required.
You know, I never thought my time spent debugging the Spectrum's 32bit floating point calculator would ever come in handy again. Or for that matter my time spent writing COBOL systems. :-)
In case you're wondering I am older than COBOL.
I think I need to go lie down now :-)
Nev