Порівняйте поплавці в php


156

Я хочу порівняти два поплавці в 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. Це ваш повний код?
Pekka

яка версія? це прекрасно працює для мене.
gblazex

@Andrey це, мабуть, тому, що справжній випадок, ймовірно, буде складнішим, ніж наведений приклад. Чому б не додати це як відповідь?
Pekka

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

Відповіді:


232

Якщо ви робите це так, вони повинні бути однаковими. Але зауважте, що характеристикою значень з плаваючою комою є те, що обчислення, які, здається, призводять до одного і того ж значення, не повинні насправді бути однаковими. Отже, якщо $aце буквальний .17і $bнадходить туди за допомогою обчислення, цілком може бути, що вони різні, хоча обидва відображають однакове значення.

Зазвичай ви ніколи не порівнюєте значення з плаваючою комою для рівності, як це, вам потрібно використовувати найменшу прийнятну різницю:

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

Щось схоже.


21
ПОДЕРЖАЙТЕ! Вибір фіксованого епсілона - це поганий спосіб лише тому, що він виглядає малим, це порівняння поверне справжню велику кількість помилок точності, коли цифри невеликі. Правильним способом було б перевірити, чи відносна похибка менша, ніж епсилон. abs($a-$b)>abs(($a-$b)/$b)
Піт Біджл

1
@ Александру: Я знаю, що ти маєш на увазі, але PHP не один у цьому плані. Тут потрібно розрізнити два випадки використання: Показ номера користувачеві. У цьому випадку відображення 0.10000000000000000555111512312578270211815834045410156зазвичай безглуздо, і вони віддають перевагу 0.1замість цього. І написання числа, щоб його можна було прочитати ще раз точно таким же чином. Як бачите, це не так чітко, як ви це робите. А для запису ви все ще хочете порівняти числа з плаваючою комою, як я показав, тому що ви можете дістатись $aта зробити $bрізні розрахунки, які можуть зробити їх різними.
Joey

2
Є ще деякі випадкові випадки, коли цей тест не вдається. Наприклад, як a=b=0і якщо aє найменше можливе нульове позитивне значення і bє найменшим можливим ненульовим негативним значенням, тест буде неправильно відмовити. Ось хороша інформація тут: floating-point-gui.de/errors/comparison
Dom

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. Див. Посилання Дому вище для кращого обговорення це.
Joey

64

Спочатку прочитайте червоне попередження в посібнику . Ви ніколи не повинні порівнювати поплавці для рівності. Вам слід скористатися технікою епсілон.

Наприклад:

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Найменше репрезентативне додатне число х, так що х + 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 'той же'; }
NemoXP

@NemoXP, оскільки ці вирази дають різні числа. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */Питання полягає в тому, як ви хочете визначити "рівним" для вашої програми, наскільки близькими повинні бути цифри, щоб вважати рівними.
Андрій

28

Або спробуйте використовувати математичні функції bc:

<?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 пунктами точності, і це, здається, порівнює їх без необхідності налаштування.
Rikaelus

А як щодо продуктивності? 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';
}

Використання округлення до двох знаків після коми (або 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 якщо лівий_операнд більший, ніж правий_операнд, -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

Ну, моя первісна відповідь була: if (''. $ A === ''. $ B) {…}, але хтось її редагував. Отже ...
Аме

1
@Ekonoval Чи можете ви, будь ласка, докладно розробити свою модифікацію? Здається, ви стверджуєте, що (string)операція передачі виконується шляхом посилання, змінюючи початкову декларацію? Якщо так, це не так 3v4l.org/Craas
fyrye

@fyrye Так, я думаю, я помилявся, обидва підходи дають однаковий результат.
Еконоваль

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

4

Якщо у вас є невелика, кінцева кількість десяткових знаків, яка буде прийнятною, наступне працює чудово (хоча і з меншою продуктивністю, ніж рішення епсілону):

$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 досить важко зрозуміти кілька термінів у вашому коментарі, але так, це дуже інформативно. І я збираюся google ці терміни. Дякую :)
Сантош Сонарікар

Хіба це не досить безпечно для порівнянь на поплавках, за умови, що ви кожного разу округляєте результат і маєте кілька значних цифр? Іншими словами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).

Ви повинні перетворити кожне значення float у рядок, а потім порівняти кожну цифру, починаючи з лівої частини представлення рядка кожного поплавця, використовуючи цілі оператори порівняння. PHP автоматично передасть цифру в кожній позиції індексу на ціле число перед порівнянням. Перша цифра, більша за другу, розірве цикл і оголосить поплавок, якому він належить, як більший з двох. У середньому буде порівняння 1/2 * n. Для поплавків, рівних один одному, буде n порівнянь. Це найгірший сценарій для алгоритму. Найкращий сценарій полягає в тому, що перша цифра кожного поплавця є різною, викликаючи лише одне порівняння.

Ви не можете використовувати ОПЕРАТОРИ INTEGER COMPARISON OPER для значень "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;
  }
 }

?>

1

2019 рік

TL; DR

Використовуйте мою функцію нижче, як це if(cmpFloats($a, '==', $b)) { ... }

  • Легко читати / писати / змінювати: cmpFloats($a, '<=', $b)vsbccomp($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"

Як отримати фактичне значення float?

$b = 1 - 0.83;
echo $b;// 0.17
echo number_format($a, 100);// 0.1700000000000000399680288865056354552507400512695312500000000000000000000000000000000000000000000000

Як можна порівняти?

  1. Використовуйте функції BC Math . (ви все одно отримаєте багато моментів wtf-aha-gotcha)
  2. Ви можете спробувати відповідь @ Гладхона, використовуючи 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;
    }
}
?>

0

Проста відповідь:

if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.