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
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)