2019
TL; DR
Используйте мою функцию ниже, как это if(cmpFloats($a, '==', $b)) { ... }
- Легко читать / писать / менять:
cmpFloats($a, '<=', $b)
противbccomp($a, $b) <= -1
- Никаких зависимостей не требуется.
- Работает с любой версией PHP.
- Работает с отрицательными числами.
- Работает с самым длинным десятичным знаком, который вы можете себе представить.
- Недостаток: немного медленнее, чем bccomp ()
Резюме
Я раскрою тайну.
$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
Так что если вы попробуете ниже, это будет равно:
if($b == 0.17000000000000003) {
echo 'same';
} else {
echo 'different';
}
// Output "same"
Как узнать фактическую стоимость поплавка?
$b = 1 - 0.83;
echo $b;// 0.17
echo number_format($a, 100);// 0.1700000000000000399680288865056354552507400512695312500000000000000000000000000000000000000000000000
Как вы можете сравнить?
- Используйте функции BC Math . (вы все равно получите много моментов, связанных с WTF-ха-ха)
- Вы можете попробовать ответ @ Gladhon, используя PHP_FLOAT_EPSILON (PHP 7.2).
- Если сравнивать числа с плавающей запятой
==
и !=
, вы можете типизировать их со строками, это должно прекрасно работать:
Тип приведения со строкой :
$b = 1 - 0.83;
if((string)$b === (string)0.17) {
echo 'if';
} else {
echo 'else';
}
// it will output "if"
Или введите с 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"
Предупреждение:
Избегайте решений, которые включают математическое манипулирование числами с плавающей точкой (умножение, деление и т. Д.), А затем сравнение, в основном они решают некоторые проблемы и создают другие проблемы.
Предлагаемое решение
Я создал чистую функцию PHP (без зависимостей / библиотек / расширений не требуется). Проверяет и сравнивает каждую цифру как строку. Также работает с отрицательными числами.
/**
* 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)
a and b are same
. Это ваш полный код?