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
Simple answer:
if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
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';
}
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;
}
}
?>
Function from @evilReiko have some bugs like these:
cmpFloats(-0.1, '==', 0.1); // Expected: false, actual: true
cmpFloats(-0.1, '<', 0.1); // Expected: true, actual: false
cmpFloats(-4, '<', -3); // Expected: true, actual: true
cmpFloats(-5.004, '<', -5.003); // Expected: true, actual: false
In my function I've fixed these bugs, but anyway in some cases this function return wrong answers:
cmpFloats(0.0000001, '==', -0.0000001); // Expected: false, actual: true
cmpFloats(843994202.303411, '<', 843994202.303413); // Expected: true, actual: false
cmpFloats(843994202.303413, '>', 843994202.303411); // Expected: true, actual: false
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 && $aIsNegative === $bIsNegative);
} elseif ($operation === '!=') {
return $aStr !== $bStr ||
$isBothZeroInts && $aDecStr !== $bDecStr;
} elseif ($operation === '>') {
if ($aInt > $bInt) {
return true;
} elseif ($aInt < $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return (!$aIsNegative && $bIsNegative);
}
if ($aDecStr === $bDecStr) {
return false;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD < $bD) {
return true;
} elseif ($aD > $bD) {
return false;
}
} else {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
}
}
}
}
} elseif ($operation === '>=') {
if ($aInt > $bInt ||
$aStr === $bStr ||
$isBothZeroInts && $aDecStr === $bDecStr) {
return true;
} elseif ($aInt < $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return (!$aIsNegative && $bIsNegative);
}
if ($aDecStr === $bDecStr) {// Decimals also equal
return true;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD < $bD) {
return true;
} elseif ($aD > $bD) {
return false;
}
} else {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
}
}
}
}
} elseif ($operation === '<') {
if ($aInt < $bInt) {
return true;
} elseif ($aInt > $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return ($aIsNegative && !$bIsNegative);
}
if ($aDecStr === $bDecStr) {
return false;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
} else {
if ($aD < $bD) {
return true;
} elseif ($aD > $bD) {
return false;
}
}
}
}
}
} elseif ($operation === '<=') {
if ($aInt < $bInt ||
$aStr === $bStr ||
$isBothZeroInts && $aDecStr === $bDecStr) {
return true;
} elseif ($aInt > $bInt) {
return false;
} else {// Ints equal, check decimals
if ($aIsNegative !== $bIsNegative) {
return ($aIsNegative && !$bIsNegative);
}
if ($aDecStr === $bDecStr) {// Decimals also equal
return true;
} else {
for ($i = 0; $i < $aDecLen; ++$i) {
$aD = (int)$aDecStr[$i];
$bD = (int)$bDecStr[$i];
if ($aIsNegative && $bIsNegative) {
if ($aD > $bD) {
return true;
} elseif ($aD < $bD) {
return false;
}
} else {
if ($aD < $bD) {
return true;
} elseif ($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)