Tutorial :Compare floats in php



Question:

I want to compare two floats in PHP, like in this sample code:

$a = 0.17;  $b = 1 - 0.83; //0.17  if($a == $b ){   echo 'a and b are same';  }  else {   echo 'a and b are not same';  }  

In this code it returns the result of the else condition instead of the if condition, even though $a and $b are same. Is there any special way to handle/compare floats in PHP?

If yes then please help me to solve this issue.

Or is there a problem with my server config?


Solution:1

If you do it like this they should be the same. But note that a characteristic of floating-point values is that calculations which seem to result in the same value do not need to actually be identical. So if $a is a literal .17 and $b arrives there through a calculation it can well be that they are different, albeit both display the same value.

Usually you never compare floating-point values for equality like this, you need to use a smallest acceptable difference:

if (abs(($a-$b)/$b) < 0.00001) {    echo "same";  }  

Something like that.


Solution:2

Read the red warning in the manual first. You must never compare floats for equality. You should use the epsilon technique.

For example:

if (abs($a-$b) < PHP_FLOAT_EPSILON) { … }  

where PHP_FLOAT_EPSILON is constant representing a very small number (you have to define it in old versions of PHP before 7.2)


Solution:3

Or try to use bc math functions:

<?php  $a = 0.17;  $b = 1 - 0.83; //0.17    echo "$a == $b (core comp oper): ", var_dump($a==$b);  echo "$a == $b (with bc func)  : ", var_dump( bccomp($a, $b, 3)==0 );  

Result:

0.17 == 0.17 (core comp oper): bool(false)  0.17 == 0.17 (with bc func)  : bool(true)  


Solution:4

As said before, be very careful when doing floating point comparisons (whether equal-to, greater-than, or less-than) in PHP. However if you're only ever interested in a few significant digits, you can do something like:

$a = round(0.17, 2);  $b = round(1 - 0.83, 2); //0.17  if($a == $b ){      echo 'a and b are same';  }  else {      echo 'a and b are not same';  }  

The use of rounding to 2 decimal places (or 3, or 4) will cause the expected result.


Solution:5

It would be better to use native PHP comparison:

bccomp($a, $b, 3)  // Third parameter - the optional scale parameter  // is used to set the number of digits after the decimal place  // which will be used in the comparison.   

Returns 0 if the two operands are equal, 1 if the left_operand is larger than the right_operand, -1 otherwise.


Solution:6

If you have floating point values to compare to equality, a simple way to avoid the risk of internal rounding strategy of the OS, language, processor or so on, is to compare the string representation of the values, like:

 if ( strval($a) === strval($b)) { … }  

String representations are much less finicky than floats when it comes to checking equality.


Solution:7

If you have a small, finite number of decimal points that will be acceptable, the following works nicely (albeit with slower performance than the epsilon solution):

$a = 0.17;  $b = 1 - 0.83; //0.17    if (number_format($a, 3) == number_format($b, 3)) {      echo 'a and b are same';  } else {      echo 'a and b are not same';  }  


Solution:8

This works for me on PHP 5.3.27.

$payments_total = 123.45;  $order_total = 123.45;    if (round($payments_total, 2) != round($order_total, 2)) {     // they don't match  }  


Solution:9

Here is the solution for comparing floating points or decimal numbers

//$fd['someVal'] = 2.9;  //$i for loop variable steps 0.1  if((string)$fd['someVal']== (string)$i)  {      //Equal  }  

Cast a decimal variable to string and you will be fine.


Solution:10

For PHP 7.2, you can work with PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ):

if(abs($a-$b) < PHP_FLOAT_EPSILON){     echo 'a and b are same';  }  


Solution:11

If you write it just like that it will probably work, so I imagine you've simplified it for the question. (And keeping the question simple and concise is normally a very good thing.)

But in this case I imagine one result is a calculation and one result is a constant.

This violates a cardinal rule of floating point programming: Never do equality comparisons.

The reasons for this are a bit subtle1 but what's important to remember is that they usually don't work (except, ironically, for integral values) and that the alternative is a fuzzy comparison along the lines of:

if abs(a - y) < epsilon  



1. One of the major problems involves the way we write numbers in programs. We write them as decimal strings, and as a result most of the fractions we write do not have exact machine representations. They don't have exact finite forms because they repeat in binary. Every machine fraction is a rational number of the form x/2n. Now, the constants are decimal and every decimal constant is a rational number of the form x/(2n * 5m). The 5m numbers are odd, so there isn't a 2n factor for any of them. Only when m == 0 is there a finite representation in both the binary and decimal expansion of the fraction. So, 1.25 is exact because it's 5/(22*50) but 0.1 is not because it's 1/(20*51). In fact, in the series 1.01 .. 1.99 only 3 of the numbers are exactly representable: 1.25, 1.50, and 1.75.


Solution:12

Comparing floats for equality has a naive O(n) algorithm.

You must convert each float value to a string, then compare each digit starting from the left side of each float's string representation using integer comparison operators. PHP will autocast the digit in each index position to an integer before the comparison. The first digit larger than the other will break the loop and declare the float that it belongs to as the greater of the two. On average, there will be 1/2 * n comparisons. For floats equal to each other, there will be n comparisons. This is the worst case scenario for the algorithm. The best case scenario is that the first digit of each float is different, causing only one comparison.

You cannot use INTEGER COMPARISON OPERATORS on raw float values with the intention of generating useful results. The results of such operations have no meaning because you are not comparing integers. You are violating the domain of each operator which generates meaningless results. This holds for delta comparison as well.

Use integer comparison operators for what they are designed for : comparing integers.

SIMPLIFIED SOLUTION:

<?php    function getRand(){    return ( ((float)mt_rand()) / ((float) mt_getrandmax()) );   }     $a = 10.0 * getRand();   $b = 10.0 * getRand();     settype($a,'string');   settype($b,'string');     for($idx = 0;$idx<strlen($a);$idx++){    if($a[$idx] > $b[$idx]){     echo "{$a} is greater than {$b}.<br>";     break;    }    else{     echo "{$b} is greater than {$a}.<br>";     break;    }   }    ?>  


Solution:13

2019

TL;DR

Use my function below, like this if(cmpFloats($a, '==', $b)) { ... }

  • Easy to read/write/change: cmpFloats($a, '<=', $b) vs bccomp($a, $b) <= -1
  • No dependencies needed.
  • Works with any PHP version.
  • Works with negative numbers.
  • Works with the longest decimal you can imagine.
  • Downside: Slightly slower than bccomp()

Summary

I'll unveil the mystery.

$a = 0.17;  $b = 1 - 0.83;// 0.17 (output)                // but actual value internally is: 0.17000000000000003996802888650563545525074005126953125  if($a == $b) {      echo 'same';  } else {      echo 'different';  }  // Output: different  

So if you try the below, it will be equal:

if($b == 0.17000000000000003) {      echo 'same';  } else {      echo 'different';  }  // Output "same"  

How to get the actual value of float?

$b = 1 - 0.83;  echo $b;// 0.17  echo number_format($a, 100);// 0.1700000000000000399680288865056354552507400512695312500000000000000000000000000000000000000000000000  

How can you compare?

  1. Use BC Math functions. (you'll still get a lot of wtf-aha-gotcha moments)
  2. You may try @Gladhon's answer, using PHP_FLOAT_EPSILON (PHP 7.2).
  3. If comparing floats with == and !=, you can typecast them to strings, it should work perfectly:

Type cast with string:

$b = 1 - 0.83;  if((string)$b === (string)0.17) {      echo 'if';  } else {      echo 'else';  }  // it will output "if"  

Or typecast with number_format():

$b = 1 - 0.83;  if(number_format($b, 3) === number_format(0.17, 3)) {      echo 'if';  } else {      echo 'else';  }  // it will output "if"  

Warning:

Avoid solutions that involves manipulating floats mathematically (multiplying, dividing, etc) then comparing, mostly they'll solve some problems and introduce other problems.


Suggested Solution

I've create pure PHP function (no depenedcies/libraries/extensions needed). Checks and compares each digit as string. Also works with negative numbers.

/**   * Compare numbers (floats, int, string), this function will compare them safely   * @param Float|Int|String  $a         (required) Left operand   * @param String            $operation (required) Operator, which can be: "==", "!=", ">", ">=", "<" or "<="   * @param Float|Int|String  $b         (required) Right operand   * @param Int               $decimals  (optional) Number of decimals to compare   * @return boolean                     Return true if operation against operands is matching, otherwise return false   * @throws Exception                   Throws exception error if passed invalid operator or decimal   */  function cmpFloats($a, $operation, $b, $decimals = 15) {      if($decimals < 0) {          throw new Exception('Invalid $decimals ' . $decimals . '.');      }      if(!in_array($operation, ['==', '!=', '>', '>=', '<', '<='])) {          throw new Exception('Invalid $operation ' . $operation . '.');      }        $aInt = (int)$a;      $bInt = (int)$b;        $aIntLen = strlen((string)$aInt);      $bIntLen = strlen((string)$bInt);        // We'll not used number_format because it inaccurate with very long numbers, instead will use str_pad and manipulate it as string      $aStr = (string)$a;//number_format($a, $decimals, '.', '');      $bStr = (string)$b;//number_format($b, $decimals, '.', '');        // If passed null, empty or false, then it will be empty string. So change it to 0      if($aStr === '') {          $aStr = '0';      }      if($bStr === '') {          $bStr = '0';      }        if(strpos($aStr, '.') === false) {          $aStr .= '.';      }      if(strpos($bStr, '.') === false) {          $bStr .= '.';      }        $aIsNegative = strpos($aStr, '-') !== false;      $bIsNegative = strpos($bStr, '-') !== false;        // Append 0s to the right      $aStr = str_pad($aStr, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);      $bStr = str_pad($bStr, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);        // If $decimals are less than the existing float, truncate      $aStr = substr($aStr, 0, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals);      $bStr = substr($bStr, 0, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals);        $aDotPos = strpos($aStr, '.');      $bDotPos = strpos($bStr, '.');        // Get just the decimal without the int      $aDecStr = substr($aStr, $aDotPos + 1, $decimals);      $bDecStr = substr($bStr, $bDotPos + 1, $decimals);        $aDecLen = strlen($aDecStr);      //$bDecLen = strlen($bDecStr);        // To match 0.* against -0.*      $isBothZeroInts = $aInt == 0 && $bInt == 0;        if($operation === '==') {          return $aStr === $bStr ||                 $isBothZeroInts && $aDecStr === $bDecStr;      } else if($operation === '!=') {          return $aStr !== $bStr ||                 $isBothZeroInts && $aDecStr !== $bDecStr;      } else if($operation === '>') {          if($aInt > $bInt) {              return true;          } else if($aInt < $bInt) {              return false;          } else {// Ints equal, check decimals              if($aDecStr === $bDecStr) {                  return false;              } else {                  for($i = 0; $i < $aDecLen; ++$i) {                      $aD = (int)$aDecStr[$i];                      $bD = (int)$bDecStr[$i];                      if($aD > $bD) {                          return true;                      } else if($aD < $bD) {                          return false;                      }                  }              }          }      } else if($operation === '>=') {          if($aInt > $bInt ||             $aStr === $bStr ||             $isBothZeroInts && $aDecStr === $bDecStr) {              return true;          } else if($aInt < $bInt) {              return false;          } else {// Ints equal, check decimals              if($aDecStr === $bDecStr) {// Decimals also equal                  return true;              } else {                  for($i = 0; $i < $aDecLen; ++$i) {                      $aD = (int)$aDecStr[$i];                      $bD = (int)$bDecStr[$i];                      if($aD > $bD) {                          return true;                      } else if($aD < $bD) {                          return false;                      }                  }              }          }      } else if($operation === '<') {          if($aInt < $bInt) {              return true;          } else if($aInt > $bInt) {              return false;          } else {// Ints equal, check decimals              if($aDecStr === $bDecStr) {                  return false;              } else {                  for($i = 0; $i < $aDecLen; ++$i) {                      $aD = (int)$aDecStr[$i];                      $bD = (int)$bDecStr[$i];                      if($aD < $bD) {                          return true;                      } else if($aD > $bD) {                          return false;                      }                  }              }          }      } else if($operation === '<=') {          if($aInt < $bInt ||              $aStr === $bStr ||             $isBothZeroInts && $aDecStr === $bDecStr) {              return true;          } else if($aInt > $bInt) {              return false;          } else {// Ints equal, check decimals              if($aDecStr === $bDecStr) {// Decimals also equal                  return true;              } else {                  for($i = 0; $i < $aDecLen; ++$i) {                      $aD = (int)$aDecStr[$i];                      $bD = (int)$bDecStr[$i];                      if($aD < $bD) {                          return true;                      } else if($aD > $bD) {                          return false;                      }                  }              }          }      }  }  

$a = 1 - 0.83;// 0.17  $b = 0.17;  if($a == $b) {      echo 'same';  } else {      echo 'different';  }  // Output: different (wrong)    if(cmpFloats($a, '==', $b)) {      echo 'same';  } else {      echo 'different';  }  // Output: same (correct)  


Solution:14

One neglected pitfall here ...

If you're working with signed floats you'll want to do two comparisons to check for proximity:

$a - $b < EPSILON && $b - $a < EPSILON  


Solution:15

//You can compare if less or more.    $parcela='250.23'; //total value  $tax = (double) '15.23'; //tax value  $taxaPercent=round((100*$tax)/$parcela,2); //tax percent     $min=(double) '2.50';// minimum tax percent                                 if($taxaPercent < $min ){      // tax error tax is less than 2.5  }  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »