web-dev-qa-db-ja.com

PHPでフロートを比較する

このサンプルコードのように、PHPの2つのフロートを比較します。

$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';
}

このコードでは、$a$bが同じであっても、else条件の代わりにif条件の結果を返します。 PHPでフロートを処理/比較する特別な方法はありますか?

はいの場合、この問題を解決するのを手伝ってください。

または、サーバー構成に問題がありますか?

133

このようにすると、shouldは同じになります。ただし、浮動小数点値の特性は、同じ値になるseemの計算が実際に同一である必要はないことに注意してください。そのため、$aがリテラル.17であり、$bが計算によってそこに到着した場合、両方が同じ値を表示していても、それらは異なる可能性があります。

通常、このような等価性について浮動小数点値を比較することはありません。許容できる最小の差を使用する必要があります。

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

そんな感じ。

214
Joey

赤い警告を読む マニュアル内 最初。浮動小数点数が等しいかどうかを比較してはいけません。イプシロン技術を使用する必要があります。

例えば:

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

PHP_FLOAT_EPSILONは非常に小さな数を表す定数です(7.2より前の古いバージョンのPHPで定義する必要があります)

53
Andrey

または、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)
24
Mario

前に述べたように、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)への丸めを使用すると、予期した結果が生じます。

14
Michael Butler

ネイティブ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. 

2つのオペランドが等しい場合は0、left_operandがright_operandより大きい場合は1、それ以外の場合は-1を返します。

13
FieryCat

同等と比較する浮動小数点値がある場合、OSの戦略internal roundingのリスクを回避する簡単な方法、言語、プロセッサなどは、値の文字列表現を比較することです:

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

文字列表現は、等価性のチェックに関してはフロートよりもはるかに扱いにくいです。

6
Ame Nomade

これは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
crmpicco

許容できる少数の有限数の小数点がある場合、以下はうまく機能します(ただし、イプシロンソリューションよりもパフォーマンスが遅くなります)。

$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';
}
3
dtbarne

浮動小数点または10進数を比較するためのソリューションは次のとおりです。

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

decimal変数をstringにキャストすれば大丈夫です。

2
Natalie Rey

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';
}
2
Gladhon

おそらくそのように書くと、おそらく動作するので、質問のために単純化したと思います。 (そして、質問をシンプルで簡潔に保つことは、通常非常に良いことです。)

しかし、この場合、1つの結果が計算であり、1つの結果が定数であると思います。

これは、浮動小数点プログラミングの基本的な規則に違反します。等値比較を実行しないでください。

この理由は少し微妙です1 しかし、覚えておくべき重要なことは、それらは通常は機能せず(皮肉なことに、整数値を除く)、代替案は次のようなファジーな比較であるということです。

if abs(a - y) < epsilon



1。大きな問題の1つは、プログラムで数値を書く方法に関係しています。それらを10進数の文字列として記述します。その結果、記述する小数のほとんどは正確なマシン表現を持ちません。それらはバイナリで繰り返されるため、正確な有限形式を持ちません。すべての機械分数は、x/2の形式の有理数です。n。現在、定数は10進数であり、すべての10進数定数はx /(2n * 5m)。 5m 数字は奇数なので、2はありませんn それらのいずれかの要因。 m == 0の場合のみ、小数の2進展開と10進展開の両方に有限表現があります。したがって、1.25は5 /(22* 5)しかし、0.1は1 /(2* 51)。実際、シリーズ1.01 .. 1.99では、1.25、1.50、1.75の3つの数字のみが正確に表現できます。

1
DigitalRoss

浮動小数点数の同等性の比較には、単純なO(n)アルゴリズムがあります。

各フロート値を文字列に変換し、整数比較演算子を使用して各フロートの文字列表現の左側から始まる各桁を比較する必要があります。 PHPは、比較の前に各インデックス位置の数字を整数に自動キャストします。最初の数字が他の数字よりも大きい場合、ループが中断され、2つの大きい方として属するフロートが宣言されます。平均して、1/2 * n回の比較が行われます。互いに等しいフロートの場合、n回の比較があります。これは、アルゴリズムの最悪のシナリオです。最良のシナリオは、各フロートの最初の桁が異なり、比較が1回だけになることです。

有用な結果を生成する目的で、生の浮動小数点値に対して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
Kyle

ここで一つの無視された落とし穴...

符号付きフロートを使用している場合は、2つの比較を行って近接性を確認する必要があります。

$a - $b < EPSILON && $b - $a < EPSILON
0
nathan kozyra

2019年

TL; DR

このif(cmpFloats($a, '==', $b)) { ... }のように、以下の関数を使用します

  • 読み取り/書き込み/変更が簡単:cmpFloats($a, '<=', $b) vs 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 functions を使用します。 (まだ多くのwtf-aha-gotchaの瞬間が得られます)
  2. PHP_FLOAT_EPSILON(PHP 7.2)を使用して、@ Gladhonの回答を試すことができます。
  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)
0
evilReiko
//You can compare if less or more.

$parcela='250.23'; //total value
$tax = (double) '15.23'; //tax value
$taxaPercent=round((100*$tax)/$parcela,2); //tax percent 

$min=(double) '2.50';// minimum tax percent                             

if($taxaPercent < $min ){
    // tax error tax is less than 2.5
}
0
GustavoVargas

私はそれを言うのが嫌いですが、「私のために働く」:

Beech:~ adamw$ php -v
PHP 5.3.1 (cli) (built: Feb 11 2010 02:32:22) 
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2009 Zend Technologies
Beech:~ adamw$ php -f test.php
a and b are same

現在、浮動小数点の比較は一般に注意が必要です-同じと思われるものはそうではありません(丸め誤差や表現のニュアンスのため)。読みたくなるかもしれません http://floating-point-gui.de/

0
Adam Wright
if( 0.1 + 0.2 == 0.3 ){
 echo 'a and b are same';
}
else {
 echo 'a and b are not same';
}

IEEE標準の浮動小数点演算(この問題がある)により、これにより 問題 が発生します。

0
gblazex

私は単にで終わった:

  uasort($_units[$k], function($a, $b)
                     {
                        $r = 0;
                        if ($a->getFloatVal() > $b->getFloatVal()) $r = 1;
                        if ($a->getFloatVal() < $b->getFloatVal()) $r = -1;
                        //print_r(["comparing {$a->getFloatVal()} vs {$b->getFloatVal()} res {$r}"]);
                        return $r * -1;
                      }
  );    
0
luky