Php şamandıralar karşılaştırın


157

Bu örnek kodda olduğu gibi PHP'de iki şamandıra karşılaştırmak istiyorum:

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

Bu kodda bunun sonucunu döndürür elsedurumun yerine ifrağmen, durumun $ave $baynıdır. PHP float işlemek / karşılaştırmak için herhangi bir özel yolu var mı?

Evet ise, lütfen bu sorunu çözmeme yardımcı olun.

Veya sunucu yapılandırmamla ilgili bir sorun mu var?


Anladım a and b are same. Bu tam kodunuz mu?
Pekka

hangi versiyon? benim için iyi çalışıyor.
gblazex

@Andrey muhtemelen budur çünkü gerçek dünya vakasının alıntılanan örnekten daha karmaşık olması muhtemeldir. Neden cevap olarak eklemiyorsun?
Pekka

2
floating-pointEtiket açıklamasını okudunuz mu? stackoverflow.com/tags/floating-point/info Bu, kayan noktalı sayıları kullanırken herhangi bir programlama dilinde karşılaşabileceğiniz bir davranıştır. Bkz. Örneğin stackoverflow.com/questions/588004/is-javascripts-math-broken
Piskvor binadan ayrıldı

Yanıtlar:


232

Eğer bunu böyle yaparsanız aynı olmalılar . Ama dikkat kayan noktalı değerlerin bir karakteristik olduğunu hesaplamalar olduğunu görünmek aslında aynı olması gerekmez aynı değere sonuçlanması. Yani $abir değişmez ise .17ve $boraya bir hesaplama yoluyla ulaşırsa , her ikisi de aynı değeri görüntülese de, farklı olmaları iyi olabilir.

Genellikle bu şekilde eşitlik için kayan nokta değerlerini asla karşılaştırmazsınız, kabul edilebilir en küçük farkı kullanmanız gerekir:

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

Bunun gibi bir şey.


21
DİKKAT! Sabit bir epsilon seçmek, sadece küçük göründüğü için kötü bir yoldur, bu karşılaştırma sayılar küçük olduğunda bir çok hassas hatada doğru dönecektir. Doğru bir yol göreceli hatanın epsilondan küçük olup olmadığını kontrol etmektir. abs($a-$b)>abs(($a-$b)/$b)
Piet Bijl

1
@Alexandru: Ne demek istediğini biliyorum, ama PHP bu konuda yalnız değil. Burada iki kullanım durumunu ayırt etmeniz gerekir: Bir kullanıcıya bir sayı gösterme. Bu durumda görüntüleme 0.10000000000000000555111512312578270211815834045410156genellikle anlamsızdır ve 0.1bunun yerine tercih ederler . Ve aynı şekilde tekrar okunabilmesi için bir sayı yazmak. Gördüğünüz gibi, ortaya çıkardığınız kadar net değil. Ve kayıt için, hala gösterdiğim gibi kayan nokta sayılarını karşılaştırmak istersiniz, çünkü onları farklılaştırabilecek farklı hesaplamalara $ave $bbunlara ulaşabilirsiniz.
Joey

2
Bu testin başarısız olduğu bazı uç durumlar vardır. Mümkün olan en küçük sıfır pozitif değer ve mümkün olan en küçük sıfır olmayan negatif değer gibi a=b=0ve varsa a, btest hatalı olarak başarısız olur. Burada bazı iyi bilgiler: floating-point-gui.de/errors/comparison
Dom

13
Neden bölünmeliyim $b? PHP kılavuzu yaptığının MySQL manuel de yaptığı aynıif(abs($a-$b) < $epsilon) HAVING ABS(a - b) <= 0.0001
Muhasebeci م

1
@CaslavSabani: Bu göreceli, mutlak bir hata değil. Hala kırık (özellikle ne zaman $a == $b == 0, ama zaten mutlak hatadan çok daha genel. Milyonlarda ise $ave varsa $b, o EPSILONzaman çok farklı olmalı $ave $byakın bir yerde olmalısınız 0. Daha iyi bir tartışma için yukarıdaki Dom'un bağlantısına bakın. Bu.
Joey

65

Önce kılavuzdaki kırmızı uyarıyı okuyun . Şamandıraları asla eşitlik için karşılaştırmamalısınız. Epsilon tekniğini kullanmalısınız.

Örneğin:

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

burada PHP_FLOAT_EPSILONçok küçük bir sayısını temsil eden sabit (Eğer 7.2 önce PHP geliştirilmiş olduğunu tanımlamak gerekir)


2
Açıklığa kavuşturmak gerekirse, bu durumda EPSILON yaklaşık 2.2204460492503E-16 olan makine epsilon mu? Ve bu karşılaştırma herhangi bir büyüklükteki iki şamandıra için işe yarıyor mu?
Michael Cordingley

1
@MichaelCordingley Hayır, EPSILONburada rastgele kullanıcı tanımlı bir sabit var. PHP, mimarinin belirli epsilon fikrini temsil eden yerleşik bir sabite sahip değildir. (Ayrıca bkz get_defined_constants.)
piskopos

5
PHP_FLOAT_EPSILONTemsil edilebilir en küçük pozitif sayı x, böylece x + 1.0! = 1.0. PHP 7.2.0 ve sonrasında mevcuttur.
Code4R7

2
Bu aslında bu durumda çalışmaz: $ a = 270.10 + 20.10; b $ = 290,20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'same'; }
NemoXP

@NemoXP çünkü bu ifadeler farklı sayılar üretir. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */Soru, uygulamanız için nasıl "eşit" tanımlamak istediğiniz, sayıların ne kadar yakın kabul edilmesi gerektiği.
Andrey

29

Veya bc math işlevlerini kullanmaya çalışın:

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

Sonuç:

0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func)  : bool(true)

2
bccomp kullanımınızda "ölçeği" kaçırdınız, bu nedenle aslında el kitabına göre 0 ile 0'ı karşılaştırıyorsunuz: php.net/manual/en/function.bccomp.php
stefancarlton

Bunu beğendim. Çoğu çözüm yuvarlama ve kaybetme hassasiyetine dayanıyor gibi görünüyor, ancak 12 hassasiyet noktası ile enlem ve boylam koordinatları ile uğraşıyorum ve bu, onları ince ayar gerektirmeden doğru bir şekilde karşılaştırıyor gibi görünüyor.
Rikaelus

Performans ne olacak? bccomp()dizeleri bağımsız değişken olarak alır. Her neyse PHP_FLOAT_DIGölçek argümanı için kullanabilirsiniz .
Code4R7

19

Daha önce de belirtildiği gibi, PHP'de kayan nokta karşılaştırmaları (eşit, daha büyük veya daha az) yaparken çok dikkatli olun. Bununla birlikte, sadece birkaç önemli basamakla ilgileniyorsanız, şöyle bir şey yapabilirsiniz:

$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 ondalık basamağa (veya 3 veya 4) yuvarlamanın kullanılması beklenen sonuca neden olacaktır.


1
Uyarı ekstra kelime, ben böyle ifadeler ile kod temeli çöp tavsiye etmem. Gevşek bir şamandıra karşılaştırması yapmak istiyorsanız, böyle bir yöntem yapın, loose_float_compareböylece neler olduğu açıktır.
Michael Butler

PHP'nin asıl bccomp($a, $b, 2)çözümü çözümünüzden üstündür. Bu örnekte, 2 doğruluktur. karşılaştırmak istediğiniz kayan nokta sayısına ayarlayabilirsiniz.
John Miller

@JohnMiller Size katılmıyorum, ancak varsayılan olarak bccomp kullanılamıyor. Bir derleme bayrağının etkinleştirilmesini veya bir uzantı yüklenmesini gerektirir. Çekirdeğin bir parçası değil.
Michael Butler

17

Yerel PHP karşılaştırmasını kullanmak daha iyi olur :

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. 

İki işlenen eşitse 0, sol_operand ve sağ_operand'dan büyükse 1, aksi takdirde -1 değerini döndürür.


10

Eğer eşitlik karşılaştırmak kayan nokta değeri varsa, basit bir şekilde riskini ortadan kaldırmak için iç yuvarlama vb işletim sistemi, dil, işlemcinin stratejisi veya karşılaştırılması verilebilir dize temsilini değerlerinin.

İstediğiniz sonucu üretmek için aşağıdakilerden herhangi birini kullanabilirsiniz: https://3v4l.org/rUrEq

Tel Tipi Döküm

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

Dize Birleştirme

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

strval işlevi

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

Eşitlik kontrolü söz konusu olduğunda, dize gösterimleri yüzenlerden çok daha az hassastır.


veya eğer orijinal değerleri dönüştürmek istemiyorsanız (strval ($ a) === strval ($ b)) {…}
Ekonoval

Benim asıl cevabım şuydu: if (''. $ A === ''. $ B) {…} ama birisi düzenledi. Yani ...
Ame Nomade

1
@Ekonoval Lütfen değişikliğinizi ayrıntılı olarak açıklar mısınız? Görünüşe göre, yayınlama (string)işleminin orijinal bildirimi değiştirerek referansla gerçekleştirildiğini iddia ediyor musunuz? Eğer durum böyle değilse 3v4l.org/Craas
fyrye

@fyrye Evet sanırım yanılmışım, her iki yaklaşım da aynı sonucu veriyor.
Ekonoval

Bir örnek kullanım ve orijinal ile birlikte diğer düzenlemelerin tüm örneklerini vermek için cevap güncellendi
fyrye

4

Kabul edilebilir küçük, sonlu sayıda ondalık noktanız varsa, aşağıdakiler iyi çalışır (epsilon çözümünden daha yavaş performans olsa da):

$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

Bu benim için PHP 5.3.27 üzerinde çalışıyor.

$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 için, PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ) ile çalışabilirsiniz :

if(abs($a-$b) < PHP_FLOAT_EPSILON){
   echo 'a and b are same';
}

Güzel çözüm. Ama: 1- herkes büyük / eski sistemler 2- Bu eserler sadece mevcut kolaylıkla yapabileceği hangi PHP 7.2 güncellenmesi gerektirir ==ve !=değil ama >, >=, <,<=
evilReiko

2

Bunu muhtemelen işe yarayacak şekilde yazarsanız, soru için basitleştirdiğinizi hayal ediyorum. (Ve soruyu basit ve özlü tutmak normalde çok iyi bir şeydir.)

Ama bu durumda bir sonucun bir hesaplama ve bir sonucun sabit olduğunu hayal ediyorum.

Bu, kayan noktalı programlamanın temel kuralını ihlal eder: Asla eşitlik karşılaştırmaları yapmayın.

Bunun nedenleri biraz ince 1'dir, ancak hatırlanması gereken önemli olan şey genellikle çalışmaz (ironik olarak, integral değerler hariç) ve alternatifin, aşağıdaki satırlar boyunca bulanık bir karşılaştırma olmasıdır:

if abs(a - y) < epsilon



1. En büyük sorunlardan biri programlara sayı yazma şeklimizdir. Onları ondalık dizeler olarak yazarız ve sonuç olarak yazdığımız kesirlerin çoğunun tam makine temsili yoktur. Kesin sonlu formları yoktur çünkü ikili olarak tekrar ederler. Her makine bölümü x / 2 n biçiminin rasyonel bir sayısıdır . Şimdi, sabitler ondalıktır ve her ondalık sabit x / (2 n * 5 m ) formunun rasyonel bir sayısıdır . 5 m rakamları tuhaftır, bu yüzden hiçbiri için 2 n faktör yoktur. Yalnızca m == 0, kesirin hem ikili hem de ondalık genişlemesinde sınırlı bir temsil olduğunda. Yani, 1.25 tam çünkü 5 / (2 2 * 5 0) ancak 0.1 1 / (2 0 * 5 1 ) olduğu için değildir. Aslında, 1.01 .. 1.99 serisinde sayıların sadece 3'ü tam olarak temsil edilebilir: 1.25, 1.50 ve 1.75.


DigitalRoss, yorumunuzdaki birkaç terimi anlamak oldukça zor, ancak evet, çok bilgilendirici. Ve ben bu terimleri google'a gideceğim. Teşekkürler :)
Santosh Sonarikar

Her seferinde sonucu yuvarlamanız ve birkaç önemli basamakta olmanız koşuluyla, şamandıralar üzerinde karşılaştırma yapmak yeterince güvenli değil mi? Başka bir deyişleround($float, 3) == round($other, 3)
Michael Butler

2

İşte kayan noktaları veya ondalık sayıları karşılaştırmanın çözümü

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

Bir decimaldeğişkeni yayınladığınızda stringiyi olacaksınız.


1

Eşitlikler için değişkenlerin karşılaştırılması saf bir O (n) algoritmasına sahiptir.

Her kayan noktalı sayı değerini bir dizeye dönüştürmeli, ardından tam sayı karşılaştırma işleçlerini kullanarak her kayan noktalı reklamın dize temsilinin sol tarafından başlayarak her basamağı karşılaştırmalısınız. PHP karşılaştırmadan önce her dizin konumundaki rakamı bir tamsayıya otomatik olarak yayınlar. İlk basamak diğerinden daha büyük olan döngüyü kıracak ve ait olduğu şamandırayı ikisinden daha büyük olarak ilan edecektir. Ortalama olarak, 1/2 * n karşılaştırmaları olacaktır. Birbirine eşit şamandıralar için n karşılaştırma yapılacaktır. Bu algoritma için en kötü senaryodur. En iyi durum senaryosu, her bir şamandıranın ilk basamağının farklı olması ve yalnızca bir karşılaştırmaya neden olmasıdır.

YARARLI KARŞILAŞTIRMA OPERATÖRLERİNİ ham sonuçlar üzerinde faydalı sonuçlar üretmek amacıyla kullanamazsınız. Bu tür işlemlerin sonuçlarının bir anlamı yoktur çünkü tamsayıları karşılaştırmazsınız. Anlamsız sonuçlar üreten her operatörün etki alanını ihlal ediyorsunuz. Bu delta karşılaştırması için de geçerlidir.

Tasarlandıkları şey için tamsayı karşılaştırma işleçlerini kullanın: tamsayıları karşılaştırma.

BASİTLEŞTİRİLMİŞ ÇÖZÜM:

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

Aşağıdaki işlevimi şu şekilde kullan: if(cmpFloats($a, '==', $b)) { ... }

  • Kolay okuma / yazma / değiştirme: cmpFloats($a, '<=', $b)vsbccomp($a, $b) <= -1
  • Bağımlılığa gerek yok.
  • Herhangi bir PHP sürümü ile çalışır.
  • Negatif sayılarla çalışır.
  • Hayal edebileceğiniz en uzun ondalık sayı ile çalışır.
  • Dezavantajı: bccomp () 'dan biraz daha yavaş

özet

Gizemi açığa çıkaracağım.

$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

Aşağıdakileri denerseniz, eşit olacaktır:

if($b == 0.17000000000000003) {
    echo 'same';
} else {
    echo 'different';
}
// Output "same"

Gerçek şamandıra değeri nasıl elde edilir?

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

Nasıl kıyaslayabilirsiniz?

  1. BC Math işlevlerini kullanın . (yine de birçok wtf-aha-gotcha anı elde edersiniz)
  2. PHP_FLOAT_EPSILON (PHP 7.2) kullanarak @ Gladhon'un cevabını deneyebilirsiniz.
  3. Şamandıraları ==ve ile karşılaştırıyorsanız !=, bunları dizelere yazabilirsiniz, mükemmel çalışmalıdır:

Dizeyle döküm yazın :

$b = 1 - 0.83;
if((string)$b === (string)0.17) {
    echo 'if';
} else {
    echo 'else';
}
// it will output "if"

Veya şununla yaz 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"

Uyarı:

Şamandıraları matematiksel olarak manipüle etmeyi (çarpma, bölme vb.) Ve ardından karşılaştırma yapmayı içeren çözümlerden kaçının, çoğunlukla bazı problemleri çözecek ve diğer problemleri getireceklerdir.


Önerilen çözüm

Ben saf PHP işlevi (depenedcies / kütüphaneler / uzantıları gerekli) oluşturduk. Her basamağı kontrol eder ve dize olarak karşılaştırır. Ayrıca negatif sayılarla çalışır.

/**
 * 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 fonksiyonunda bazı hatalar var:

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

Benim fonksiyonumda bu hataları düzelttim, ama yine de bazı durumlarda bu fonksiyon yanlış cevaplar veriyor:

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

Şamandıraları karşılaştırmak için sabit fonksiyon

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

Sorunuza cevap verin

$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

İşte kayan nokta sayılarıyla uğraşmak için kişisel kütüphanemden yararlı bir sınıf. Bunu beğeninize göre düzenleyebilir ve istediğiniz herhangi bir çözümü sınıf yöntemlerine ekleyebilirsiniz :-).

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

Basit cevap:

if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.