Сравнение поплавков в 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 и все будет в порядке.

Ответ 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/

Ответ 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

Как вы можете сравнить?

  1. Используйте функции BC Math. (вы все равно получите много моментов, связанных с неуверенностью в себе)
  2. Вы можете попробовать ответ @Gladhon, используя PHP_FLOAT_EPSILON (PHP 7.2).
  3. Если сравнивать числа с плавающей запятой с == и !=, Вы можете типизировать их со строками, это должно прекрасно работать:

Тип приведения со строкой:

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