PHP에서 float 비교


156

이 샘플 코드와 같이 PHP에서 두 개의 float를 비교하고 싶습니다.

$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에서 float를 처리 / 비교하는 특별한 방법이 있습니까?

그렇다면이 문제를 해결하도록 도와주세요.

또는 서버 구성에 문제가 있습니까?


나는 얻는다 a and b are same. 이것이 전체 코드입니까?
Pekka 2016 년

어떤 버전? 그것은 나를 위해 잘 작동합니다.
gblazex 2016 년

@Andrey 이것은 아마도 실제 사례가 인용 된 예보다 더 복잡 할 가능성이 있기 때문에 아마도 그렇습니다. 왜 답변으로 추가하지 않습니까?
Pekka 2016 년

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
조심해! 고정 엡실론을 선택하는 것은 그것이 작게 보이기 때문에 나쁜 방법입니다.이 비교는 숫자가 작을 때 많은 정밀도 오류에서 true를 반환합니다. 올바른 방법은 상대 오류가 엡실론보다 작은 지 확인하는 것입니다. abs($a-$b)>abs(($a-$b)/$b)
Piet Bijl

1
@Alexandru : 무슨 말인지 알지만 PHP는 그 자체만으로는 아닙니다. 여기서 두 가지 사용 사례를 구별해야합니다. 사용자에게 숫자 표시. 이 경우 표시 0.10000000000000000555111512312578270211815834045410156는 일반적으로 무의미하며 0.1대신 선호 합니다. 그리고 정확히 같은 방식으로 다시 읽을 수 있도록 숫자를 쓰십시오. 보시다시피, 그것은 당신이 그것을 만드는만큼 분명하지 않습니다. 그리고 기록을 위해, 당신은 아직도 당신이 도달 할 수 있기 때문에이 보여처럼 부동 소수점 숫자를 비교할 $a$b그들이 다른 만들 수있는 다른 계산을 통해.
Joey

2
이 테스트가 실패하는 경우가 여전히 있습니다. 같은 a=b=0경우와 a작은 수 없음 제로 양의 값이고, b가장 작은 영이 아닌 음의 값이고, 테스트 실패를 잘못. 여기에 좋은 정보가 있습니다 : floating-point-gui.de/errors/comparison
Dom

13
왜 나누어야 $b합니까? PHP 매뉴얼은 단지 한 MySQL의 설명서 도했던 것과 같은if(abs($a-$b) < $epsilon) HAVING ABS(a - b) <= 0.0001
회계사 م

1
@CaslavSabani : 이것은 절대적인 오류가 아니라 상대적인 오류입니다. 그것은 여전히 때 특히 (생겼습니다 $a == $b == 0,하지만 절대 오류보다 이미 훨씬 더 일반적이다. 만약 $a$b수백만에, 다음 EPSILON의 경우에 비해 매우 다를 할 것 $a$b가까운 곳에 있습니다 0. 참조 돔의 링크를 위의 더 나은 토론 이.
조이

64

먼저 설명서 의 빨간색 경고 읽으십시오 . 플로트가 평등인지 비교해서는 안됩니다. 엡실론 기술을 사용해야합니다.

예를 들면 다음과 같습니다.

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

PHP_FLOAT_EPSILON매우 작은 수를 나타내는 상수는 어디 입니까 (7.2 이전의 PHP 버전에서는 정의해야합니다)


2
명확히하기 위해,이 경우 EPSILON은 대략 2.2204460492503E-16 인 기계 엡실론입니까? 그리고이 비교는 어떤 규모의 두 수레에 대해서도 효과가 있습니까?
Michael Cordingley

1
@MichaelCordingley 아니요, EPSILON여기에 임의의 사용자 정의 상수가 있습니다. PHP에는 엡실론에 대한 아키텍처의 특정 아이디어를 나타내는 내장 상수가 없습니다. (참조 get_defined_constants)
주교

5
PHP_FLOAT_EPSILONx + 1.0! = 1.0이되도록 표현 가능한 가장 작은 양수 x 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 사용에서 "scale"을 놓쳤으므로 매뉴얼에 따라 실제로 0과 0을 비교합니다. php.net/manual/en/function.bccomp.php
stefancarlton

나는 이것을 좋아한다. 대부분의 솔루션은 반올림과 정밀도 손실에 의존하는 것처럼 보이지만 12 포인트의 정밀도로 위도와 경도 좌표를 처리하고 있으며 조정하지 않아도 정확하게 비교하는 것 같습니다.
Rikaelus 2016 년

성능은 어떻습니까? 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어떤 일이 일어나고 있는지 명백한 방법을 만드십시오 .
Michael Butler

PHP의 네이티브 bccomp($a, $b, 2)는 솔루션보다 우수합니다. 이 예에서 2는 정밀도입니다. 비교하려는 부동 소수점 수에 관계없이 설정할 수 있습니다.
John Miller

@JohnMiller 동의하지 않지만 bccomp는 기본적으로 사용할 수 없습니다. 컴파일 플래그가 사용 가능하거나 확장이 설치되어 있어야합니다. 핵심의 일부가 아닙니다.
Michael Butler

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을, left_operand가 right_operand보다 큰 경우 1을, 그렇지 않으면 -1을 리턴합니다.


10

동등성과 비교할 부동 소수점 값이있는 경우 OS, 언어, 프로세서 등 의 내부 반올림 전략의 위험을 피하는 간단한 방법 은 값 의 문자열 표현 을 비교하는 것 입니다.

다음 중 하나를 사용하여 원하는 결과를 얻을 수 있습니다. https://3v4l.org/rUrEq

끈 유형 주물

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

문자열 연결

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

strval 함수

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

문자열 표현은 동등성을 검사 할 때 수레보다 훨씬 덜 까다 롭습니다.


또는 if (strval ($ a) === strval ($ b)) {…} 원래 값을 변환하지 않으려면
Ekonoval

글쎄, 원래의 대답은 다음과 같습니다. if ( ''. $ a === ''. $ b) {…} 그러나 누군가가 편집했습니다.
Ame Nomade

1
@Ekonoval 수정에 대해 자세히 설명해 주시겠습니까? (string)캐스트 작업이 참조에 의해 수행되어 원래 선언을 변경 한다고 주장하는 것 같습니다 . 그렇다면 3v4l.org/Craas
fyrye

@fyrye 네, 내가 틀렸다고 생각합니다. 두 방법 모두 같은 결과를 낳습니다.
Ekonoval

사용 예와 다른 편집의 모든 예를 원본과 함께 제공하도록 답변을 업데이트했습니다.
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 - 모두가 큰 / 오래된 시스템 2이 작품만을위한 기존의 쉽게 할 수있는 PHP 7.2 업데이트가 필요 ==하고 !=있지 만 >, >=, <,<=
evilReiko

2

당신이 그것을 좋아하는 것처럼 작성한다면 아마도 효과가있을 것이기 때문에 질문을 위해 그것을 단순화했다고 상상합니다. (질문을 간단하고 간결하게 유지하는 것은 일반적으로 매우 좋은 일입니다.)

그러나이 경우 하나의 결과가 계산이고 하나의 결과가 상수라고 생각합니다.

이는 부동 소수점 프로그래밍의 기본 규칙을 위반합니다 . 평등 비교를 수행하지 마십시오.

이것에 대한 이유는 약간 미묘 하지만 1 기억해야 할 것은 일반적으로 작동하지 않으며 (아이러니하게, 정수 값을 제외하고) 대안은 다음 줄에 대한 퍼지 비교입니다.

if abs(a - y) < epsilon



1. 주요 문제 중 하나는 프로그램에서 숫자를 쓰는 방식과 관련이 있습니다. 우리는 그것들을 십진 문자열로 쓰고, 결과적으로 우리가 쓰는 대부분의 분수는 정확한 기계 표현을 가지고 있지 않습니다. 이진수로 반복되므로 정확한 유한 형태를 갖지 않습니다. 모든 기계 분수는 x / 2 n 형식의 합리적인 수입니다 . 이제 상수는 10 진수이며 모든 10 진수 상수는 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는 귀하의 의견에서 몇 가지 용어를 이해하기가 어렵지만 정보가 풍부합니다. 그리고 나는이 용어를 구글로 할 것입니다. 감사합니다 :)
Santosh Sonarikar

매번 결과를 반올림하고 소수 자릿수 이내 인 경우 플로트를 비교하는 것이 안전하지 않습니까? 다른 말로round($float, 3) == round($other, 3)
Michael Butler

2

부동 소수점 또는 소수를 비교하는 솔루션은 다음과 같습니다.

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

decimal변수를 캐스트하면 string괜찮을 것입니다.


1

동등성을 위해 float를 비교하는 것은 순진한 O (n) 알고리즘이 있습니다.

각 부동 소수점 값을 문자열로 변환 한 다음 정수 비교 연산자를 사용하여 각 부동 소수점 문자열 표시의 왼쪽부터 시작하여 각 숫자를 비교해야합니다. PHP는 비교하기 전에 각 인덱스 위치의 숫자를 정수로 자동 전송합니다. 다른 숫자보다 큰 첫 번째 숫자는 루프를 끊고 두 숫자 중 더 큰 것으로 속한 float을 선언합니다. 평균적으로 1/2 * n 비교가 있습니다. 서로 같은 수레의 경우 n 개의 비교가 있습니다. 이것은 알고리즘에 대한 최악의 시나리오입니다. 가장 좋은 시나리오는 각 부동 소수점의 첫 번째 숫자가 다르므로 한 번만 비교하는 것입니다.

유용한 결과를 생성하려는 의도로 원시 부동 소수점 값에는 정수 비교 연산자를 사용할 수 없습니다. 정수를 비교하지 않기 때문에 이러한 연산의 결과는 의미가 없습니다. 의미없는 결과를 생성하는 각 연산자의 도메인을 위반하고 있습니다. 이것은 델타 비교에도 적용됩니다.

정수 비교 연산자를 위해 설계된 것에 대해 정수 비교 연산자를 사용하십시오.

단순화 된 솔루션 :

<?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 버전에서 작동합니다.
  • 음수로 작동합니다.
  • 상상할 수있는 가장 긴 10 진수로 작동합니다.
  • 단점 : 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)을 사용하여 @Gladhon의 답변을 시도 할 수 있습니다.
  3. float와 ==and를 비교 !=하면 문자열로 타입 캐스트 할 수 있으면 완벽하게 작동합니다.

문자열로 타입 캐스트 :

$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 기능을 만들었습니다 (depenedcies / 라이브러리 / 확장 필요 없음). 각 숫자를 확인하고 문자열로 비교합니다. 음수로도 작동합니다.

/**
 * 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

다음은 내 개인 라이브러리에서 부동 소수점 숫자를 처리하는 데 유용한 클래스입니다. 원하는대로 tweek하고 클래스 메소드에 원하는 솔루션을 삽입 할 수 있습니다 :-).

/**
 * 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 }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.