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
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';
}
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.
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.
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)
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)
Here is a useful class from my personal library for dealing with floating point numbers. You can tweek it to your liking and insert any solution you like into the class methods :-).
/**
* A class for dealing with PHP floating point values.
*
* @author Anthony E. Rutledge
* @version 12-06-2018
*/
final class Float extends Number
{
// PHP 7.4 allows for property type hints!
private const LESS_THAN = -1;
private const EQUAL = 0;
private const GREATER_THAN = 1;
public function __construct()
{
}
/**
* Determines if a value is an float.
*
* @param mixed $value
* @return bool
*/
public function isFloat($value): bool
{
return is_float($value);
}
/**
* A method that tests to see if two float values are equal.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function equals(float $y1, float $y2): bool
{
return (string) $y1 === (string) $y2;
}
/**
* A method that tests to see if two float values are not equal.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isNotEqual(float $y1, float $y2): bool
{
return !$this->equals($y1, $y2);
}
/**
* Gets the bccomp result.
*
* @param float $y1
* @param float $y2
* @return int
*/
private function getBccompResult(float $y1, float $y2): int
{
$leftOperand = (string) $y1;
$rightOperand = (string) $y2;
// You should check the format of the float before using it.
return bccomp($leftOperand, $rightOperand);
}
/**
* A method that tests to see if y1 is less than y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isLess(float $y1, float $y2): bool
{
return ($this->getBccompResult($y1, $y2) === self::LESS_THAN);
}
/**
* A method that tests to see if y1 is less than or equal to y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isLessOrEqual(float $y1, float $y2): bool
{
$bccompResult = $this->getBccompResult($y1, $y2);
return ($bccompResult === self::LESS_THAN || $bccompResult === self::EQUALS);
}
/**
* A method that tests to see if y1 is greater than y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isGreater(float $y1, float $y2): bool
{
return ($this->getBccompResult($y1, $y2) === self::GREATER_THAN);
}
/**
* A method that tests to see if y1 is greater than or equal to y2.
*
* @param float $y1
* @param float $y2
* @return bool
*/
public function isGreaterOrEqual(float $y1, float $y2): bool
{
$bccompResult = $this->getBccompResult($y1, $y2);
return ($bccompResult === self::GREATER_THAN || $bccompResult === self::EQUALS);
}
/**
* Returns a valid PHP float value, casting if necessary.
*
* @param mixed $value
* @return float
*
* @throws InvalidArgumentException
* @throws UnexpectedValueException
*/
public function getFloat($value): float
{
if (! (is_string($value) || is_int($value) || is_bool($value))) {
throw new InvalidArgumentException("$value should not be converted to float!");
}
if ($this->isFloat($value)) {
return $value;
}
$newValue = (float) $value;
if ($this->isNan($newValue)) {
throw new UnexpectedValueException("The value $value was converted to NaN!");
}
if (!$this->isNumber($newValue)) {
throw new UnexpectedValueException("The value $value was converted to something non-numeric!");
}
if (!$this->isFLoat($newValue)) {
throw new UnexpectedValueException("The value $value was not converted to a floating point value!");
}
return $newValue;
}
}
?>