Page 1 of 2

Strange answers to simple maths [kinda solved]

Posted: Mon Jun 06, 2005 12:16 am
by Stryks
Hi all, I have a problem with something, and I'm not really of the reason for it.

Basically, I have a routine go and get some prices for me from a database, and I'm just trying to deduct one value from the other. But, lets say that the first value is 2.01 and the second value is 2 (2.01 - 2) the answer shown is 0.0099999999999998.

Can anyone tell me why this is? I tried a bunch of stuff to try and make the answer correct like trying to convert them (I used gettype on them - they are strings), and currently I am simply perforning a round($value, 2) on it to make it correct, but I'd like to know why it is doing this, and if I am handling it the right way.

Cheers

Posted: Mon Jun 06, 2005 2:30 am
by timvw
We tend to think in base 10, (at least normal people do.) which is great for representing some fractions such as a quarter, (0.25) but pretty lame for representing fractions such as a third (0.3333 recurring). You would not have any difficulty storing a third if we used base 3, it would be 0.1. The important thing to note is that a fraction which is recurring in one base might not be in another. 13.825 in binary is 1101.(110100)(110100)(110100) . . .

I put brackets round the part that recurrs forever. So 13.825 is a recurring binary fraction which gets truncated after about 52 digits (there is an 12 bit exponent which in this instance would be 000000000100 which is 4, meaning that the binary point is 4 places in and the fraction part starts after the 1101 (13).

There are 10 types of people in the world, those who understand binary and those who don't.

Forgot to give a workaround: Multiply all numbers by a multitude of 10 so they are all integers. Make the calculations, and divide again by that multitude of 10.

Posted: Mon Jun 06, 2005 9:29 am
by Stryks
Thansk for the reply, but I'm afraid I still dont get it.

I mean, I get what you're saying about being binary and all I think, but I guess the question is ... why is it like that?

I mean,

Code: Select all

echo (2.01 + 2);
gives 4.01, while

Code: Select all

echo (2.01 - 2);
gives 0.0099999999999998.

If you could give me an idea of why this is, and a sample bit of code to fix it, it'd be appreciated. Mainly I'd just like to get my head around it.

Cheers.

Posted: Mon Jun 06, 2005 8:39 pm
by Stryks
Any help anyone?

Posted: Mon Jun 06, 2005 8:49 pm
by John Cartwright
http://php.net/float wrote:It is quite usual that simple decimal fractions like 0.1 or 0.7 cannot be converted into their internal binary counterparts without a little loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8 as the result of the internal representation really being something like 7.9999999999....

This is related to the fact that it is impossible to exactly express some fractions in decimal notation with a finite number of digits. For instance, 1/3 in decimal form becomes 0.3333333. . ..

So never trust floating number results to the last digit and never compare floating point numbers for equality. If you really need higher precision, you should use the arbitrary precision math functions or gmp functions instead.

Posted: Mon Jun 06, 2005 9:04 pm
by Stryks
Ok, that's kind of clarified it a bit more for me, though I am still at a loss about how to work around it.

I mean ...

Code: Select all

$a = 2.01;
$b = 2;

echo "$a - $b = ";

$a = ($a * 100);
$b = ($b * 100);

$c = ($a - $b)/100;

echo $c;
... should work if it's all about being a float, but it doesn't. It still returns 0.0099999999999997.

I mean the sum is 201 - 200. The answer should be 1. I cant see that there should be any problem representing a whole number as binary.

I mean what would you do, if you had a bill of 2.01 and your recieved 2.00. How would you work out what was left, because clearly, I cant just deduct one from the other.

This is really bugging me. Any direction on solution would be great.

Posted: Tue Jun 07, 2005 12:58 am
by Stryks
Having re-read Jcart's post above, I have kind of come to the conclusion that I have to do something like ...

Code: Select all

$a = 2.01;
$b = 2;

echo "$a - $b = ";

$x = ($a * 100);
$y = ($b * 100);

$c = bcsub($a, $b, 2);

echo $c . "<br><br>";
... or else I could just use the round() function?

I mean I dont know why this is so hard to get an answer on. When dealing with money calculations, surely it is common to take one amount from another. I would have thought so anyhow.

So, what is the best solution? Is it reliable? Is this bcmath going to fail on certain servers if it isn't installed? Is float going to break it? Is there an easier way?

Thanks.

Posted: Tue Jun 07, 2005 5:03 am
by timvw
Stryks wrote:Ok, that's kind of clarified it a bit more for me, though I am still at a loss about how to work around it.

I mean ...

Code: Select all

$a = 2.01;
$b = 2;

echo "$a - $b = ";

$a = ($a * 100);
$b = ($b * 100);

$c = ($a - $b)/100;

echo $c;
... should work if it's all about being a float, but it doesn't. It still returns 0.0099999999999997.

I've tested this script on both php4 as php5 and it always outputs:

2.01 - 2 = 0.01

Posted: Tue Jun 07, 2005 5:27 am
by Stryks
Really?

How is that possible?

I'm running PHP 4.3.11 and I get the output ...

Code: Select all

2.01 - 2 = 0.0099999999999997
I even cut and copied the code from here again in case I was losing my mind.

Are there any configuration items which can effect this? What else could cause this?

Posted: Tue Jun 07, 2005 6:12 am
by timvw
php5 is @localhost

php4: http://timvw.madoka.be/test.php i added a call to phpinfo() too so you can compare ;)

Posted: Tue Jun 07, 2005 6:55 am
by Syranide
As a reminder, do always round your floats, especially if comparing (which more or less requires the use of a delta).

Posted: Tue Jun 07, 2005 7:05 am
by Stryks
Thanks for that timvw.

Just so you know, I wasn't trying to imply that you were lying or anything, just that we had different results.

I used beyond compare to see if I could see anything obvious, but nothing that seemed to be of much relevance showed up. I'm running a localhost which is live but probrably not the best testing ground, so I uploaded it to the site of a friend I have been helping.

It is showing the same as mine though.

http://www.absolutelygorgeous.com.au/test3.php (in case you look - yes, the site is very pink ... NOT my idea)

Just for the sake of being thorough, here is the complete script for the page.

Code: Select all

<?php
$a = 2.01;
$b = 2;
 
echo "$a - $b = ";
 
$a = ($a * 100);
$b = ($b * 100);
 
$c = ($a - $b)/100;
 
echo $c;

phpinfo();
?>
I did kind of prick my ears up to the fact that you dont seem to have bcmath enabled and I do, but I really dont know enough about it to make any real assessment.

If you get a chance, I'd be interested to know if you can see anything there.

Thanks alot for taking the time to help get to the bottom of this tricky little sucker. :)

Posted: Tue Jun 07, 2005 7:12 am
by Syranide
why in earth are you multiplying by 100 then dividing?
EDIT: that will most likely make the precision drop

Posted: Tue Jun 07, 2005 7:21 am
by Stryks
Syranide - I'm multiplying by 100 to move the decimal point two places to the right, turning 2.01 into 201 and 2 into 200. That way I can work with whole numbers.

In theory, this will leave me with 1, which when divided by 100 should give 0.01, the answer I am after. Even that doesnt work though, so .... yeah. :?

Posted: Tue Jun 07, 2005 7:30 am
by Syranide
in theory you would get 2.01 - 2 = 0.01 :P
so common human theory don't work with floats...
however, do note that doing operations on 100 is worse than doing it on 1, as 100 is less accurate than 1 for operations, roughly speaking, a float consists of 10 digits (approx).

note that for every operation you apply to a float it go farther from its "true" value as it is rounded each time, powers of 10 too. however as I said, remember that a float should generally not be compared or printed without rounding as it is very common they go of with infinite decimals or so. Meaning that you shouldn't keep more decimals than you require when printing it.

EDIT: if you where out for integer operations you have to typecast it first (int).