Odd behavior comparing doubles, two PHP double values aren't equivalent - php

I have two seemingly equal double values in PHP (at least when echoing them).
But when comparing them with double equals, for some reason, it evaluates to false. Are there any special considerations when performing this kind of comparison?

You shouldn't compare floating point numbers using the == operator.
See the big warning and explanation in the php manual
What will work is asserting that the two numbers are within a certain small distance of each other like this:
if(abs($a - $b) < 0.0001) {
print("a is mostly equal to b");
}
The reason is because of rounding errors due to floating point arithmetic performed after the decimals are converted to binary, then converted back to decimal. These back and forth conversions cause the phenomenon where 0.1 + 0.2 does not equal 0.3.

float and double should never be compared for equality: there are precision errors that will make two numbers different even if they seem equal (when they are printed out, they are usually rounded).
Proper way to compare is using some DELTA constant:
define(DELTA, 0.00001); // Or whatever precision you require
if (abs($a-$b) < DELTA) {
// ...
}
Also note that this is not PHP specific but also important in other languages (Java, C, ...)

Representation of floating point numbers in PHP (as well as in C and many other languages) is inexact. Due to this fact, seemingly equal numbers can in fact be different and comparison will fail. Instead, choose some small number and check that the difference is less than that, like:
if(abs($a-$b)<0.00001) {
echo "Equal!";
}
See also explanations in the PHP manual.

A small function i made, hope helps someone:
function are_doubles_equal($double_1, $double_2, $decimal_count) {
if (!$decimal_count || $decimal_count < 0) {
return intval($double_1) == intval($double_2);
}
else {
$num_1 = (string) number_format($double_1, $decimal_count);
$num_2 = (string) number_format($double_2, $decimal_count);
return $num_1 == $num_2;
}
}
Usage:
$a = 2.2;
$b = 0.3 + 1.9002;
are_doubles_equal($a, $b, 1); // true : 2.2 == 2.2
are_doubles_equal($a, $b, 1); // false : 2.2000 == 2.2002

Not the fastest way but convert to string before comparing:
if( strval($a) === strval($b) ){
// double values are exactly equal
}

Related

Php comparison integer with double

I got a problem with this script
$total = 0;
$expected_total = 1111;
$i = [85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.48];
foreach ($i as $item) { $total += $item; }
if($total != $expected_total) {
echo json_encode([$total,$expected_total]);
}
The problem is that at the end of the sum the $total should be equal to the $expected_total.
I thought about different number types, I printed the type of the two vars and I was right, one was double and the other was integer, so I converted the integer to double
$expected_total = 1111.00;
but the result is still the same.
The only solution that I could find was comparing the rappresentation of the two numbers, casting them to a string.
if((string)$total != (string)$expected_total) {
echo json_encode([$total,$expected_total]);
}
But obviously this is kind of a hack.
Have you ever had a similar problem? How have you solved it?
PHP version : 5.5.9
Many Thanks
This is not only PHP problem. It is about representation of floating point numbers in memory. Floating numbers has limited precision. PHP uses IEEE 754. Read carefully the manual page and you will understand.
You can find in manual code snippet of how to do it.
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001; //very small number
if(abs($a-$b) < $epsilon) {
echo "true";
}
If you want to check them as integers you could round both values.
You should change the if-statement to the following:
if(round($total) != round($expected_total)) {
echo json_encode([$total,$expected_total]);
}
If you do it like this you will compare the rounded values, which will be the same.
There is a big red label in the PHP Manual, that says:
Floating point numbers have limited precision…
So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.
In this specific case, you could add the floating numbers using bcadd() and compare the totals using bccomp(). Both functions are provided by the BC Math extension:
foreach ($i as $item) {
$total = bcadd((string) $total, (string) $item, 2);
}
if (bccomp((string) $total, (string) $expected_total, 2) == 0) {
echo json_encode([$total,$expected_total]);
}

Floating point rounding error php ...how can I make sure it works correctly?

I have the following function that determines if I sale is fully paid for. I don't remember why I did it this way, but it has been working so far and I don't remember why I had to do it this way.
function _payments_cover_total()
{
//get_payments is a list of payment amounts such as:
//10.20, 10.21, or even 10.1010101101 (10 decimals max)
$total_payments = 0;
foreach($this->sale_lib->get_payments() as $payment)
{
$total_payments += $payment['payment_amount'];
}
//to_currency_no_money rounds total to 2 decimal places
if (to_currency_no_money($this->sale_lib->get_total()) - $total_payments ) > 1e-6 ) )
{
return false;
}
return true;
}
I am wondering if there is ever a case where due to a rounding error that this function would return false when it shouldn't.
The main part I have a question about is:
> 1e-6
I think before I had, but it was causing problems in some cases.
> 0
I think you are doing what is mentioned on php floating help page. To quote it directly :
To test floating point values for equality, an upper bound on the
relative error due to rounding is used. This value is known as the
machine epsilon, or unit roundoff, and is the smallest acceptable
difference in calculations.
$a and $b are equal to 5 digits of precision.
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
So in your case:
(to_currency_no_money($this->sale_lib->get_total()) - $total_payments) > 1e-6
relative error due to rounding should not be great than 1e-6 or 0.000001
if you are not sure about left operand being greater than right 100% time,then you should add abs() e.g for correctness.
$relative_error=to_currency_no_money($this->sale_lib->get_total()) - $total_payments;
if(abs($relative_error) > 1e-6){
return false
}
return true;
$x = (1.333-1.233)-(1.334-1.234);
echo $x;
//result = $x = -2.2204460492503E-16 - close to zero
//but (1.333-1.233)-(1.334-1.234) = 0.1 - 0.1 = 0 (in calculator)
if($x === 0){
echo "|zero";
}
else {
echo "|non zero"; //<== this is result
}
//screen = -2.2204460492503E-16|non zero
//how to get to zero?
if($x > 1e-6){//1e-6 mathematical constant
echo "|non zero";
}
else {
echo "|zero";//this is result
}
//screen -2.2204460492503E-16|non zero|zero
if ($x > 1e-6 )
{
echo " false";
//echo "|non zero";
//return false;
}
else{
echo " true";//<== this resut
//echo "|zero";
//return true;
}
//screen -2.2204460492503E-16|non zero|zero true
printf("%.1f<br />", 1e-1);
printf("%.2f<br />", 1e-2);
printf("%.3f<br />", 1e-3);
printf("%.4f<br />", 1e-4);
printf("%.5f<br />", 1e-5);
printf("%.6f<br />", 1e-6);
printf("%.7f<br />", 1e-7);
printf("%.8f<br />", 1e-8);
printf("%.9f<br />", 1e-9);
printf("%.10f<br />", 1e-10);
printf("%.11f<br />", 1e-11);
printf("%.12f<br />", 1e-12);
printf("%.29f<br />", -2.2204460492503E-16);
//0.1
//0.01
//0.001
//0.0001
//0.00001
//0.000001
//0.0000001
//0.00000001
//0.000000001
//0.0000000001
//0.00000000001
//0.000000000001
//-0.00000000000000022204460492503
I am sorry, but when dealing with currency, you shouldn't really be using PHP floats, as IMSoP stated. The reason is also from PHP float help pages:
Additionally, rational numbers that are exactly representable as
floating point numbers in base 10, like 0.1 or 0.7, do not have an
exact representation as floating point numbers in base 2, which is
used internally, no matter the size of the mantissa. Hence, they
cannot be converted into their internal binary counterparts without a
small 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, since the internal representation will be something like
7.9999999999999991118....
So never trust floating number results to the last digit, and do not
compare floating point numbers directly for equality. If higher
precision is necessary, the arbitrary precision math functions and gmp
functions are available.
Note that the help page specifically says you can't trust float results to the last digit, no matter how short (after comma) it is.
So while you do have very short floats (just 2 digits after comma), the float precision (1e-6) doesn't enter into it really, and you can't trust them 100%.
Since it is a question of money, in order to avoid angry customers and lawsuits accusing of penny shaving (https://en.wikipedia.org/wiki/Salami_slicing), the real solutions are:
1) either use PHP BC math, which works with string representation of numbers
http://php.net/manual/en/book.bc.php
2) as IMSoP suggested, use integers and store the amounts in smallest denomination (cents, eurocents or whatever you have) internally.
First solution might be a bit more resource intense: I haven't used BC math myself much, but storing strings and doing arbitrary precision math (which might be a bit of an overkill in this case) are by definition more RAM and CPU intense than working with integers.
It might, however, need less changes in other parts of the code.
The second solution requires changes to represenation, so that wherever user sees the amounts, they are normalized to dollars,cents (or whatever have you).
Note, however, that in this case also you run problems with rounding risks at some point, as when you do:
float shown_amount; // what we show to customer: in dollars,cents
int real_amount; // what we use internally: in cents
shown_amount = cent_amount / 100;
you may reintroduce the rounding problems as you have floats again and possibilities for rounding errors, so tread carefully and be sure to make calculations and roundings only on real_amount, never on shown_amount

PHP if statement fails to validate on "!== 0"

How can the below be possible:
$varnum = 4;
if( $varnum/4 - floor($varnum/4) !== 0){
echo 'foo';
}
This echoes 'foo' on my server running PHP 5.1.6. If i change the operator to == I get the same results.
I have no idea why, but could it possibly be because "==" is "equals" and "!==" is "Not identical"? How then would I make them identical? I guess in javaScript I would "parseInt", but there is no such thing in PHP, right?
The reason this fails is because in PHP, the floor function returns a float, despite the fact that the value is always a whole number. You can see this in the documentation here: http://php.net/manual/en/function.floor.php
You're doing a fixed type comparison of that float to an integer zero, so the result is false, regardless of whether the value is actually zero.
To fix this, either:
cast the output of floor to an integer - either intval(float(...)) or (int)float(..)
use != instead of !==.
use 0.0 instead of just 0 to compare against.
In case you're wondering why floor() would return a float rather than an integer, it's because the input is a float. The float data type has a larger possible range than integer, and thus it is possible to call floor() on a value that would be too big to hold in an integer. Therefore it would not be safe for the function to return an integer; it returns a float instead so that it can guarantee the result will be correct.
It may seem odd at first glance, but hopefully that explains the logic behind it for you.
What is it you are trying to accomplish? If you are trying to see if $varnum is divisible by four then use modulus, so...
$varnum = 4;
if ($varnum % 4 != 0) {
echo "foo - $varnum is divisible by 4";
}
You original post should use '!=' versus '!==', like this:
$varnum = 4;
if( $varnum/4 - floor($varnum/4) != 0){
echo 'foo';
}

What is the most efficient way to make and odds system?

I'm trying to design an odds system that goes from 1-100, however it also uses 0-1 for rarer odds.
I was told I should use a floating point format, but I don't know that.
Basically I have..
if (mt_rand(1,1000)/100 == $odds) {} else if (mt_rand(1,100) == $odds) {}
however that only yields the same probability.
I looked up floating point format in the PHP manual, but the answers there couldn't help me.
See Odds to understand how to convert your odds to a probability. If you have odds of 4:1 then there is a 1/5 == 0.2 probability of the event. If your odds are .2:1 then there is a 5/6 (about .833) probability of the event happening. In general, if the odds are m:n against then the probability is n/(m+n).
Now, if you want to simulate whether an event occurs or not, you need to get a random floating point number between 0 and 1 then check if this is less than the probability of the event. You can use something like mt_rand(0,1000)/1000 to get a random number between 0 and 1.
Examples:
$odds1 = 4; // 4:1
$prob1 = 1/($odds1+1); // 1/5
if( mt_rand(0,1000)/1000 <= $prob1 ) {
// event happened
}
$odds2 = .2; // .2:1
$prob2 = 1/($odds2+1); // 5/6
if( mt_rand(0,1000)/1000 <= $prob2 ) {
// event happened
}
Floating point values are inexact. (See Why does `intval(19.9 * 100)` equal `1989`? and search: php floating point inexact.)
You cannot use == for floating point values. A simple 5/10 == 0.5 might already be wrong due to inherent precision loss.
You can either round numbers before comparison, your what I'd advise in your case, pre-convert floats into integers:
# 52 == 100*0.52
if (mt_rand(1,100) == round(100*$odds)) {
Instead of comparing 0.99 with another float, you convert your odds into an integer 99 and compare it with an integer random in the range 1 to 100. If odds already was an integer, not a float, then the *100 multiplication will already cut it out of that first (faux float) comparison.
if($odds < 1){
// floating point math here
if((float)mt_rand(0,100) / 100.0 < $odds){
echo "you're a float winner, harry";
}
}else{
if(mt_rand(0,100) < $odds){
echo "you're an int winner, harry!";
}
}

PHP does not find two equal numbers to be equal? bug? [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
compare floats in php
i have a condition:
if($x <= .3)
echo 1;
it will echo 1 only if x is less than .3
if $x is EQUAL to .3, i do not get a 1.
i have tried wrapping the x in floatval(), but no luck
i tried to echo the $x and i get "0.3"
i have tried if ($x == .3) - nothing
if i have tried if (.3 == .3) which obviously works
any ideas? is this a PHP bug?
It's all about binary representation of floating point numbers :
var_dump(sprintf("%.40f", 0.3));
// string(42) "0.2999999999999999888977697537484345957637"
Basically, 0.3 can't be represented exactly in base 2, so it gets truncated after a few digits. It's basically like 1/3 in base 10 : you can type 0.3, but 0.33 is more precise, so is 0.333, 0.3333, etc. You can't represent it exactly.
Floating point values are not exact. You can check to see if it's roughly <= 0.3 like this:
if ($x <= 0.3000001) {
echo 'yay';
}
Here you go, big, red and fat: Floating Point Numbers:
It is typical that simple decimal
fractions like 0.1 or 0.7 cannot be
converted into their internal binary
counterparts without a small 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,
since the internal representation will
be something like 7.9.
This is due to the fact that it is
impossible to express some fractions
in decimal notation with a finite
number of digits. For instance, 1/3 in
decimal form becomes 0.3.
So never trust floating number results
to the last digit, and never compare
floating point numbers for equality.
If higher precision is necessary, the
arbitrary precision math functions and
gmp functions are available.
PS: There is even more fun:
INF == INF => false
INF < INF => true
INF > INF => true
So infinity is not infinity and infinity is smaller than infinity and greater than infinity at the same time. If you think about it, it does actually make some sense...
hmmm, i have to assume that your var $x is really not equal to .3.
i just tested this:
<?
$var = 0.3;
if( $var <= .3 )
{
echo 'yay';
}
else
{
echo 'boo';
}
?>
and it outputs yay
$x = .4;
if ($x <= .3) { echo 1; }
Works fine here...tried different values at $x
however... where are you getting $x ?? you might need to "round" the value before comparing.
Floating points are not 100% accurate. In short, the fractional component is generally stored by adding 1/(2^n) together. e.g., 1/2 + 1/4 is how 3/4 would be stored. So this isn't a bug, nor is it a specific PHP question.
However, this should always be true:
$x = 0.3;
if ($x == 0.3) echo "true";
because the same inaccuracy would be present in both.
But this is not guaranteed to be true:
$x = 0.1;
$y = 0.2;
if ($x + $y == 0.3) echo "true";
A simple way to work around this is to use a delta:
if (abs($a - $b) < $delta) echo "true"
where $delta is a very small number.
If you need accuracy, then check out something like the BCMath extension.
If this is for money, then it's usually easier to just do calculations in whole cents (integers), where $1.23 = 123.

Categories