Сравнение поплавков в php
Я хочу сравнить два поплавка в PHP, как в этом примере кода:
$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';
}
В этом коде он возвращает результат условия else
вместо условия if
, хотя $a
и $b
одинаковы. Есть ли специальный способ обработки/сравнения float в PHP?
Если да, то помогите мне решить эту проблему.
Или есть проблема с моей конфигурацией сервера?
Ответы
Ответ 1
Если вы сделаете это так, они должны быть одинаковыми. Но обратите внимание, что характеристикой значений с плавающей запятой является то, что вычисления, которые, как представляется, приводят к одному и тому же значению, не обязательно должны быть идентичными. Поэтому, если $a
является литеральным .17
и $b
прибывает туда через вычисление, вполне возможно, что они разные, хотя оба показывают одно и то же значение.
Обычно вы никогда не сравниваете значения с плавающей запятой для равенства, как это, вам нужно использовать наименьшую допустимую разницу:
if (abs(($a-$b)/$b) < 0.00001) {
echo "same";
}
Что-то вроде этого.
Ответ 2
Сначала прочтите красное предупреждение в руководстве. Вы никогда не должны сравнивать поплавки на равенство. Вы должны использовать технику эпсилон.
Например:
if (abs($a-$b) < PHP_FLOAT_EPSILON) { … }
где PHP_FLOAT_EPSILON
- это константа, представляющая очень небольшое число (вы должны определить его в старых версиях PHP до 7.2)
Ответ 3
Или попробуйте использовать математические функции:
<?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 );
Результат:
0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func) : bool(true)
Ответ 4
Как уже говорилось, будьте очень осторожны при выполнении сравнений с плавающей запятой (будь то равно, больше или меньше) в PHP. Однако, если вас интересует только несколько значащих цифр, вы можете сделать что-то вроде:
$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';
}
Использование округления до 2 знаков после запятой (или 3 или 4) приведет к ожидаемому результату.
Ответ 5
Лучше использовать родное сравнение PHP:
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.
Возвращает 0, если оба операнда равны, 1, если left_operand больше, чем right_operand, -1 в противном случае.
Ответ 6
Если у вас есть значения с плавающей запятой для сравнения с равенством, простой способ избежать риска внутренней стратегии округления ОС, языка, процессора и т.д. Состоит в сравнении строкового представления значений, например:
if ( strval($a) === strval($b)) { … }
Строковые представления гораздо менее требовательны, чем плавающие, когда дело доходит до проверки равенства.
Ответ 7
Это работает для меня на 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
}
Ответ 8
Если у вас есть небольшое, конечное число десятичных точек, которое будет приемлемым, следующее будет хорошо работать (хотя и с более низкой производительностью, чем решение epsilon):
$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';
}
Ответ 9
Вот решение для сравнения чисел с плавающей запятой или десятичных чисел
//$fd['someVal'] = 2.9;
//$i for loop variable steps 0.1
if((string)$fd['someVal']== (string)$i)
{
//Equal
}
Приведите decimal
переменную к string
и все будет в порядке.
Ответ 10
Для PHP 7.2 вы можете работать с PHP_FLOAT_EPSILON (http://php.net/manual/en/reserved.constants.php):
if(abs($a-$b) < PHP_FLOAT_EPSILON){
echo 'a and b are same';
}
Ответ 11
Если вы напишете это так, то это, вероятно, сработает, поэтому, я думаю, вы упростили это для вопроса. (И держать вопрос простым и лаконичным, как правило, очень хорошо.)
Но в этом случае я считаю, что один результат - это вычисление, а один результат - константа.
Это нарушает кардинальное правило программирования с плавающей запятой: Никогда не выполняйте сравнения равенства.
Причины этого немного тонкие 1 но важно помнить, что они обычно не работают (за исключением, как это ни парадоксально, для интегральных значений), и что альтернатива - это нечеткое сравнение вдоль строки:
if abs(a - y) < epsilon
1. Одна из основных проблем связана с тем, как мы пишем числа в программах. Мы пишем их как десятичные строки, и в результате большинство записываемых нами дробей не имеют точных представлений машин. У них нет точных конечных форм, потому что они повторяются в двоичном виде. Каждая машинная дробь является рациональным числом вида x/2 n. Теперь константы десятичные, а каждая десятичная константа - рациональное число вида x/(2 n * 5 m). Число 5 m нечетно, поэтому для любого из них не существует 2 n. Только тогда, когда m == 0 есть конечное представление как в двоичном, так и в десятичном разложении дроби. Итак, 1.25 точна, потому что 5/(2 2 * 5 0), но 0,1 не потому, что 1/(2 0 * 5 1). Фактически, в серии 1.01.. 1.99 только 3 числа точно представлены: 1,25, 1,50 и 1,75.
Ответ 12
Сравнение поплавков для равенства имеет наивный алгоритм O (n).
Вы должны преобразовать каждое значение float в строку, а затем сравнить каждую цифру, начиная с левой стороны каждого представления строки float, используя операции сравнения целого числа. PHP будет автоматически обновлять цифру в каждой позиции индекса до целого числа перед сравнением. Первая цифра больше, чем другая, приведет к разрыву цикла и объявит поплавок, к которому он принадлежит, как к большему из двух. В среднем будет 1/2 * n сравнений. Для поплавков, равных друг другу, будет проведено n сравнений. Это наихудший сценарий для алгоритма. Наилучшим случаем является то, что первая цифра каждого поплавка отличается, вызывая только одно сравнение.
Вы не можете использовать INTEGER COMPARISON OPERATORS для сырых значений float с целью получения полезных результатов. Результаты таких операций не имеют смысла, потому что вы не сравниваете целые числа. Вы нарушаете домен каждого оператора, который генерирует бессмысленные результаты. Это также относится к дельта-сопоставлению.
Используйте операторы целочисленного сравнения для того, для чего они предназначены: сравнение целых чисел.
УПРОЩЕННОЕ РЕШЕНИЕ:
<?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;
}
}
?>
Ответ 13
Я ненавижу это говорить, но "работает для меня":
Beech:~ adamw$ php -v
PHP 5.3.1 (cli) (built: Feb 11 2010 02:32:22)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2009 Zend Technologies
Beech:~ adamw$ php -f test.php
a and b are same
Теперь сравнения с плавающей запятой в общем сложны - вещи, которые вы, возможно, ожидаете быть одинаковыми, не являются (из-за ошибок округления и/или нюансов представления). Возможно, вам захочется прочитать http://floating-point-gui.de/
Ответ 14
if( 0.1 + 0.2 == 0.3 ){
echo 'a and b are same';
}
else {
echo 'a and b are not same';
}
Это вызовет проблемы из-за стандартной арифметики IEEE с плавающей запятой (которая имеет эту проблему).
Ответ 15
Одна забытая ловушка здесь...
Если вы работаете с подписанными поплавками, вам нужно сделать два сравнения, чтобы проверить близость:
$a - $b < EPSILON && $b - $a < EPSILON
Ответ 16
//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
}
Ответ 17
Я закончил просто:
uasort($_units[$k], function($a, $b)
{
$r = 0;
if ($a->getFloatVal() > $b->getFloatVal()) $r = 1;
if ($a->getFloatVal() < $b->getFloatVal()) $r = -1;
//print_r(["comparing {$a->getFloatVal()} vs {$b->getFloatVal()} res {$r}"]);
return $r * -1;
}
);
Ответ 18
function floatcmp($f1,$f2,$precision = 10)
{
$e = pow(10,$precision);
return (intval($f1 * $e) == intval($f2 * $e));
}
Контрольный пример
$a = 0.17;
$b = 0.17;
echo floatcmp($a,$b) ? 'yes' : 'no'; // yes
echo floatcmp($a,$b + 0.01) ? 'yes' : 'no'; // no
Ответ 19
function compareFloats($a, $b){
list($a_int, $a_dec) = explode('.', strval($a));
list($b_int, $b_dec) = explode('.', strval($b));
if(intval($a_int) == intval($b_int) && intval($a_dec) == intval($b_dec)){
return 'same';
}else{
if((intval($a_int) < intval($b_int)) || (intval($a_int) === intval($b_int) && intval($a_dec) < intval($b_dec))){
return 'smaller';
}
if((intval($a_int) > intval($b_int)) || (intval($a_int) === intval($b_int) && intval($a_dec) > intval($b_dec))){
return 'bigger';
}
return 'error';
}
}
Ответ 20
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 'a and b are same';
} else {
echo 'a and b are not same';
}
Так что если вы попробуете ниже, это будет равно:
if($b == 0.17000000000000003) {
echo 'if';
} else {
echo 'else';
}
// it will output "if"
Как узнать фактическую стоимость поплавка?
$b = 1 - 0.83;
echo $b;// 0.17
echo number_format($a, 100);// 0.1700000000000000399680288865056354552507400512695312500000000000000000000000000000000000000000000000
Как вы можете сравнить?
- Используйте функции BC Math. (вы все равно получите много моментов, связанных с неуверенностью в себе)
- Вы можете попробовать ответ @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 'equal';
} else {
echo 'not equal';
}
// Output: not equal (wrong)
if(cmpFloats($a, '==', $b)) {
echo 'equal';
} else {
echo 'not equal';
}
// Output: equal (correct)