Сравните поплавки в php


157

Я хочу сравнить два числа с плавающей точкой в ​​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такой же. Есть ли какой-нибудь особый способ обработки / сравнения чисел в PHP?

Если да, то, пожалуйста, помогите мне решить эту проблему.

Или есть проблема с моим сервером конфигурации?


Я получаю a and b are same. Это ваш полный код?
Пекка

какая версия? Он отлично работает для меня.
gblazex

@ Андрей, возможно, это так, потому что реальный случай, вероятно, будет более сложным, чем приведенный пример. Почему бы не добавить его в качестве ответа?
Пекка

2
Вы читали floating-pointописание тега? stackoverflow.com/tags/floating-point/info Такое поведение вы, скорее всего, встретите на любом языке программирования при использовании чисел с плавающей запятой. См., Например, stackoverflow.com/questions/588004/is-javascripts-math-broken
Писквор покинул здание

Ответы:


232

Если вы делаете это так, они должны быть одинаковыми. Но обратите внимание, что характеристикой значений с плавающей точкой является то, что вычисления, которые, по- видимому, приводят к одному и тому же значению, не обязательно должны быть идентичными. Так что если $aлитерал .17и $bприбывает туда через вычисление, вполне может быть, что они разные, хотя оба отображают одно и то же значение.

Обычно вы никогда не сравниваете значения с плавающей точкой для равенства, как это, вам нужно использовать наименьшее допустимое различие:

if (abs(($a-$b)/$b) < 0.00001) {
  echo "same";
}

Что-то такое.


21
BEWARE! Выбор фиксированного эпсилона - плохой способ только потому, что он выглядит маленьким, это сравнение вернет true во множестве ошибок точности, когда числа малы. Правильным способом было бы проверить, меньше ли относительная ошибка, чем эпсилон. abs($a-$b)>abs(($a-$b)/$b)
Пит Биджл

1
@Alexandru: Я знаю, что вы имеете в виду, но PHP не одинок в этом отношении. Здесь нужно различать два варианта использования: показ числа пользователю. В этом случае показ 0.10000000000000000555111512312578270211815834045410156обычно не имеет смысла, и 0.1вместо этого они предпочли бы . И записать число так, чтобы его можно было прочитать снова точно таким же образом. Как видите, это не так ясно, как вы это представляете. И для записи, вы все еще хотите сравнить числа с плавающей запятой, как я показал, потому что вы можете прийти $aи $bчерез различные вычисления, которые могут сделать их разными.
Джои

2
Есть все еще некоторые крайние случаи, которые этот тест не проходит. Например, a=b=0если и aявляется наименьшим из возможных ненулевых положительных значений и bявляется наименьшим ненулевым отрицательным значением, проверка будет ошибочно провалена. Немного полезной информации здесь: плавающая
точка-

13
Зачем делить на $b? руководство по PHP только что сделало if(abs($a-$b) < $epsilon) руководство по MySQL, также сделало то же самоеHAVING ABS(a - b) <= 0.0001
Бухгалтер م

1
@CaslavSabani: это относительная, а не абсолютная ошибка. Он все еще не работает (особенно когда $a == $b == 0, но он уже гораздо более общий, чем абсолютная ошибка. Если $aи $bв миллионах, то вам EPSILONпридется сильно отличаться от того, если $aи $bгде-то близко 0. См. Ссылку Dom выше для лучшего обсуждения это
Джои

65

Прочитайте красное предупреждение в руководствеСначала . Вы никогда не должны сравнивать поплавки на равенство. Вы должны использовать технику эпсилон.

Например:

if (abs($a-$b) < PHP_FLOAT_EPSILON) {  }

где PHP_FLOAT_EPSILONконстанта, представляющая очень небольшое число (вы должны определить его в старых версиях PHP до 7.2)


2
Чтобы уточнить, является ли EPSILON в данном случае машиной epsilon, что примерно равно 2,2204460492503E-16? И работает ли это сравнение для двух поплавков любой величины?
Майкл Кордингли

1
@MichaelCordingley Нет, EPSILONздесь произвольная пользовательская константа. В PHP нет встроенной константы, представляющей специфическую идею архитектуры epsilon. (См. Также get_defined_constants.)
епископ

5
PHP_FLOAT_EPSILONНаименьшее представимое положительное число x, так что x + 1.0! = 1.0. Доступно с PHP 7.2.0.
Code4R7

2
Это на самом деле не работает в этом случае: $ a = 270,10 + 20,10; $ b = 290,20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'same'; }
NemoXP

@NemoXP, потому что эти выражения производят разные числа. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */Вопрос в том, как вы хотите определить «равный» для вашего приложения, насколько близко должны быть числа, чтобы считаться равными.
Андрей

29

Или попробуйте использовать математические функции:

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

2
при использовании bccomp вы пропустили «шкалу», таким образом, вы фактически сравниваете 0 к 0 согласно руководству: php.net/manual/en/function.bccomp.php
stefancarlton

Мне это нравится. Кажется, что большинство решений полагаются на округление и потерю точности, но я имею дело с координатами широты и долготы с 12 точками точности, и это, кажется, сравнивает их точно без необходимости подстройки.
Рикаэль

Как насчет производительности? bccomp()принимает строки в качестве аргументов. В любом случае вы можете использовать PHP_FLOAT_DIGдля аргумента масштаба.
Code4R7

19

Как уже было сказано, будьте очень осторожны при выполнении сравнений с плавающей запятой (равно, больше или меньше) в 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) приведет к ожидаемому результату.


1
Дополнительное предупреждение, я бы не советовал засорять вашу кодовую базу такими утверждениями. Если вы хотите сделать сравнение с плавающей запятой, создайте такой метод, loose_float_compareчтобы было понятно, что происходит.
Майкл Батлер

Родной язык PHP bccomp($a, $b, 2)превосходит ваше решение. В этом примере 2 - это точность. Вы можете установить любое число с плавающей запятой, которое хотите сравнить.
Джон Миллер

@JohnMiller Я не согласен с вами, но bccomp по умолчанию недоступен. Для этого требуется либо включить флаг компиляции, либо установить расширение. Не является частью ядра.
Майкл Батлер

17

Было бы лучше использовать нативное сравнение 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 в противном случае.


10

Если у вас есть значения с плавающей запятой для сравнения с равенством, простой способ избежать риска внутренней стратегии округления ОС, языка, процессора и т. Д. Состоит в сравнении строкового представления значений.

Вы можете использовать любой из следующих способов для получения желаемого результата: https://3v4l.org/rUrEq

Струнное литье

if ( (string) $a === (string) $b) {  }

Конкатенация строк

if ('' . $a === '' . $b) {  }

функция strval

if (strval($a) === strval($b)) {  }

Строковые представления гораздо менее требовательны, чем плавающие, когда дело доходит до проверки равенства.


или если (strval ($ a) === strval ($ b)) {…}, если вы не хотите преобразовывать исходные значения
Ekonoval,

Ну, мой первоначальный ответ был: если (''. $ A === ''. $ B) {…}, но кто-то отредактировал его. Итак ...
Ame Nomade

1
@Ekonoval Не могли бы вы уточнить вашу модификацию. Похоже, вы утверждаете, что (string)операция приведения выполняется по ссылке, изменяя исходное объявление? Если так, то это не так 3v4l.org/Craas
фырье

@fyrye Да, наверное, я ошибался, оба подхода дают одинаковый результат.
Эконовал

Обновлен ответ, чтобы привести пример использования и все примеры других правок вместе с оригиналом
fyrye

4

Если у вас есть небольшое конечное число десятичных знаков, которое будет приемлемым, следующее прекрасно работает (хотя и с более низкой производительностью, чем решение 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';
}

4

Это работает для меня на 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
}

3

Для 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';
}

Хорошее решение Но: 1 Требуется обновление PHP 7.2 , который не каждый может легко сделать для существующих больших / старых систем 2- Это работает только для ==и , !=но не >, >=, <,<=
evilReiko

2

Если вы напишите это так, это, вероятно, сработает, так что я полагаю, вы упростили это для вопроса. (И держать вопрос простым и кратким, как правило, очень хорошая вещь.)

Но в этом случае я предполагаю, что один результат - это вычисление, а один - постоянная величина.

Это нарушает основное правило программирования с плавающей запятой: никогда не сравнивайте равенство.

Причины этого несколько тонкие 1, но важно помнить, что они обычно не работают (за исключением, по иронии судьбы, целочисленных значений), и что альтернативой является нечеткое сравнение по направлениям:

if abs(a - y) < epsilon



1. Одна из основных проблем связана с тем, как мы пишем числа в программах. Мы записываем их как десятичные строки, и в результате большинство записываемых дробей не имеют точных машинных представлений. У них нет точных конечных форм, потому что они повторяются в двоичном виде. Каждая машинная дробь является рациональным числом вида x / 2 n . Теперь константы являются десятичными, и каждая десятичная константа является рациональным числом вида x / (2 n * 5 m ). Числа 5 м являются нечетными, поэтому ни для одного из них нет коэффициента 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.


DigitalRoss довольно сложно понять несколько терминов в вашем комментарии, но да, это очень информативно. И я собираюсь погуглить эти условия. Спасибо :)
Сантош Сонарикар

Разве не достаточно безопасно проводить сравнения по числам с плавающей запятой, если вы округляете результат каждый раз и находитесь в пределах нескольких значащих цифр? Другими словамиround($float, 3) == round($other, 3)
Майкл Батлер

2

Вот решение для сравнения чисел с плавающей запятой или десятичных чисел

//$fd['someVal'] = 2.9;
//$i for loop variable steps 0.1
if((string)$fd['someVal']== (string)$i)
{
    //Equal
}

Приведите decimalпеременную к stringи все будет в порядке.


1

Сравнение поплавков на равенство имеет наивный алгоритм O (n).

Вы должны преобразовать каждое значение с плавающей точкой в ​​строку, а затем сравнить каждую цифру, начиная с левой стороны строкового представления каждого с плавающей запятой, используя операторы сравнения целых чисел. PHP автоматически передаст цифру в каждой позиции индекса в целое число перед сравнением. Первая цифра, большая чем другая, разорвет цикл и объявит число с плавающей точкой, к которому она принадлежит, как большее из двух. В среднем будет 1/2 * n сравнений. Для поплавков, равных друг другу, будет n сравнений. Это худший сценарий для алгоритма. В лучшем случае, первая цифра каждого числа с плавающей запятой отличается, вызывая только одно сравнение.

Вы не можете использовать INTEGER COMPARISON OPERATORS для необработанных значений с плавающей точкой с целью получения полезных результатов. Результаты таких операций не имеют значения, потому что вы не сравниваете целые числа. Вы нарушаете домен каждого оператора, который приводит к бессмысленным результатам. Это относится и к дельта-сравнению.

Используйте целочисленные операторы сравнения для того, для чего они предназначены: сравнения целых чисел.

Упрощенное решение:

<?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;
  }
 }

?>

1

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

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

  1. Используйте функции BC Math . (вы все равно получите много моментов, связанных с WTF-ха-ха)
  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 'same';
} else {
    echo 'different';
}
// Output: different (wrong)

if(cmpFloats($a, '==', $b)) {
    echo 'same';
} else {
    echo 'different';
}
// Output: same (correct)

1

У функции из @evilReiko есть некоторые ошибки, подобные этим:

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

В моей функции я исправил эти ошибки, но в любом случае в некоторых случаях эта функция возвращает неправильные ответы:

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)

0

Вот полезный класс из моей личной библиотеки для работы с числами с плавающей запятой. Вы можете настроить его по своему вкусу и вставить любое понравившееся вам решение в методы класса :-).

/**
 * A class for dealing with PHP floating point values.
 * 
 * @author Anthony E. Rutledge
 * @version 12-06-2018
 */
final class Float extends Number
{
    // PHP 7.4 allows for property type hints!

    private const LESS_THAN = -1;
    private const EQUAL = 0;
    private const GREATER_THAN = 1;

    public function __construct()
    {

    }

    /**
     * Determines if a value is an float.
     * 
     * @param mixed $value
     * @return bool
     */
    public function isFloat($value): bool
    {
        return is_float($value);
    }

    /**
     * A method that tests to see if two float values are equal.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function equals(float $y1, float $y2): bool
    {
        return (string) $y1 === (string) $y2;
    }

    /**
     * A method that tests to see if two float values are not equal.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isNotEqual(float $y1, float $y2): bool
    {
        return !$this->equals($y1, $y2);
    }

    /**
     * Gets the bccomp result.
     * 
     * @param float $y1
     * @param float $y2
     * @return int
     */
    private function getBccompResult(float $y1, float $y2): int
    {
        $leftOperand = (string) $y1;
        $rightOperand = (string) $y2;

        // You should check the format of the float before using it.

        return bccomp($leftOperand, $rightOperand);
    }

    /**
     * A method that tests to see if y1 is less than y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isLess(float $y1, float $y2): bool
    {
        return ($this->getBccompResult($y1, $y2) === self::LESS_THAN);
    }

    /**
     * A method that tests to see if y1 is less than or equal to y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isLessOrEqual(float $y1, float $y2): bool
    {
        $bccompResult = $this->getBccompResult($y1, $y2);
        return ($bccompResult === self::LESS_THAN || $bccompResult === self::EQUALS);
    }

    /**
     * A method that tests to see if y1 is greater than y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isGreater(float $y1, float $y2): bool
    {
        return ($this->getBccompResult($y1, $y2) === self::GREATER_THAN);
    }

    /**
     * A method that tests to see if y1 is greater than or equal to y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isGreaterOrEqual(float $y1, float $y2): bool
    {
        $bccompResult = $this->getBccompResult($y1, $y2);
        return ($bccompResult === self::GREATER_THAN || $bccompResult === self::EQUALS);
    }

    /**
     * Returns a valid PHP float value, casting if necessary.
     * 
     * @param mixed $value
     * @return float
     *
     * @throws InvalidArgumentException
     * @throws UnexpectedValueException
     */
    public function getFloat($value): float
    {
        if (! (is_string($value) || is_int($value) || is_bool($value))) {
            throw new InvalidArgumentException("$value should not be converted to float!");
        }

        if ($this->isFloat($value)) {
            return $value;
        }

        $newValue = (float) $value;

        if ($this->isNan($newValue)) {
            throw new UnexpectedValueException("The value $value was converted to NaN!");
        }

        if (!$this->isNumber($newValue)) {
            throw new UnexpectedValueException("The value $value was converted to something non-numeric!");
        }

        if (!$this->isFLoat($newValue)) {
            throw new UnexpectedValueException("The value $value was not converted to a floating point value!");
        }

        return $newValue;
    }
}
?>

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.