Chcę porównać dwie zmiennoprzecinkowe w PHP, jak w tym przykładowym kodzie:
$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';
}
W tym kodzie zwraca wynik else
warunku zamiast if
warunku, mimo że $a
i $b
są takie same. Czy jest jakiś specjalny sposób obsługi / porównywania wartości zmiennoprzecinkowych w PHP?
Jeśli tak, pomóż mi rozwiązać ten problem.
Czy jest problem z konfiguracją mojego serwera?
php
floating-point
Santosh Sonarikar
źródło
źródło
a and b are same
. Czy to twój pełny kod?floating-point
opis tagu? stackoverflow.com/tags/floating-point/info To zachowanie, które prawdopodobnie napotkasz w dowolnym języku programowania, używając liczb zmiennoprzecinkowych. Patrz np stackoverflow.com/questions/588004/is-javascripts-math-brokenOdpowiedzi:
Jeśli robisz to w ten sposób, powinny być takie same. Należy jednak zauważyć, że cechą charakterystyczną wartości zmiennoprzecinkowych jest to, że obliczenia, które wydają się dawać tę samą wartość, nie muszą być w rzeczywistości identyczne. Więc jeśli
$a
jest literałem.17
i$b
dociera do niego poprzez obliczenia, może być tak, że są różne, chociaż oba mają tę samą wartość.Zwykle nigdy nie porównujesz wartości zmiennoprzecinkowych dla równości w ten sposób, musisz użyć najmniejszej akceptowalnej różnicy:
Coś w tym stylu.
źródło
abs($a-$b)
>abs(($a-$b)/$b)
0.10000000000000000555111512312578270211815834045410156
jest zwykle bezcelowe i woleliby0.1
zamiast tego. I zapisanie liczby, aby można ją było odczytać ponownie w dokładnie ten sam sposób. Jak widzisz, nie jest to tak wyraźne, jak się wydaje. A dla przypomnienia, nadal chcesz porównać liczb zmiennoprzecinkowych jak wykazałem, ponieważ można dotrzeć$a
i$b
za pośrednictwem różnych obliczeń, które czynią je różni.a=b=0
i jeślia
jest najmniejszą możliwą wartością dodatnią zero ib
jest najmniejszą możliwą wartością ujemną niezerową, test niepoprawnie zakończy się niepowodzeniem. Kilka dobrych informacji tutaj: floating-point-gui.de/errors/comparison$b
? obsługi PHP po prostu nie obsługi MySQL zrobił również taka samaif(abs($a-$b) < $epsilon)
HAVING ABS(a - b) <= 0.0001
$a == $b == 0
, ale jest już o wiele bardziej ogólny niż błąd bezwzględny. Jeśli$a
i$b
są w milionach, toEPSILON
musielibyście być zupełnie inni niż gdyby$a
i$b
byli gdzieś blisko)0
. Zobacz link Dom powyżej, aby lepiej omówić to.Najpierw przeczytaj czerwone ostrzeżenie w instrukcji . Nigdy nie możesz porównywać zmiennych dla równości. Powinieneś użyć techniki epsilon.
Na przykład:
gdzie
PHP_FLOAT_EPSILON
jest stała reprezentująca bardzo małą liczbę (musisz ją zdefiniować w starszych wersjach PHP przed 7.2)źródło
EPSILON
tutaj jest dowolna stała zdefiniowana przez użytkownika. PHP nie ma wbudowanej stałej reprezentującej konkretną koncepcję architektury epsilon. (Zobacz takżeget_defined_constants
)PHP_FLOAT_EPSILON
Najmniejsza reprezentowalna liczba dodatnia x, tak że x + 1,0! = 1,0. Dostępne od PHP 7.2.0.echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */
Pytanie brzmi, jak chcesz zdefiniować „równe” dla swojej aplikacji, jak blisko liczby powinny być uważane za równe.Lub spróbuj użyć funkcji matematycznych bc:
Wynik:
źródło
bccomp()
przyjmuje ciągi jako argumenty. W każdym razie możesz użyćPHP_FLOAT_DIG
argumentu skali.Jak wspomniano wcześniej, należy być bardzo ostrożnym podczas wykonywania porównań zmiennoprzecinkowych (równych, większych lub mniejszych niż) w PHP. Jeśli jednak interesuje Cię tylko kilka znaczących cyfr, możesz zrobić coś takiego:
Użycie zaokrąglenia do 2 miejsc po przecinku (lub 3 lub 4) da oczekiwany wynik.
źródło
loose_float_compare
aby było oczywiste, co się dzieje.bccomp($a, $b, 2)
jest lepszy od Twojego rozwiązania. W tym przykładzie 2 to precyzja. możesz ustawić dowolną liczbę punktów zmiennoprzecinkowych, które chcesz porównać.Lepiej byłoby użyć natywnego porównania PHP :
źródło
Jeśli masz wartości zmiennoprzecinkowe do porównania z równością, prostym sposobem uniknięcia ryzyka wewnętrznej strategii zaokrąglania systemu operacyjnego, języka, procesora itp. Jest porównanie ciągowej reprezentacji wartości.
Aby uzyskać pożądany wynik, możesz użyć dowolnego z poniższych: https://3v4l.org/rUrEq
Rzutowanie typu ciągów
Konkatenacja ciągów
funkcja strval
Reprezentacje ciągów są znacznie mniej skomplikowane niż zmiennoprzecinkowe, jeśli chodzi o sprawdzanie równości.
źródło
(string)
operacja rzutowania jest wykonywana przez odniesienie, zmieniając pierwotną deklarację? Jeśli tak, to nie jest to przypadek 3v4l.org/CraasJeśli masz małą, skończoną liczbę miejsc po przecinku, które będą akceptowalne, poniższe działa dobrze (choć z wolniejszą wydajnością niż rozwiązanie epsilon):
źródło
To działa dla mnie na PHP 5.3.27.
źródło
W PHP 7.2 możesz pracować z PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ):
źródło
==
a!=
jednak nie>
,>=
,<
,<=
Jeśli napiszesz to tak po prostu, prawdopodobnie zadziała, więc wyobrażam sobie, że uprościłeś to pytanie. (A zachowanie prostego i zwięzłego pytania jest zazwyczaj bardzo dobrą rzeczą).
Ale w tym przypadku wyobrażam sobie, że jeden wynik jest obliczeniem, a jeden wynik jest stałą.
Narusza to podstawową zasadę programowania zmiennoprzecinkowego: nigdy nie porównuj równości.
Powody tego są nieco subtelne 1, ale ważne jest, aby pamiętać, że zwykle nie działają (z wyjątkiem, jak na ironię, wartości całkowitych) i że alternatywą jest niejasne porównanie wzdłuż linii:
1. Jeden z głównych problemów dotyczy sposobu, w jaki zapisujemy liczby w programach. Zapisujemy je jako ciągi dziesiętne, w wyniku czego większość zapisywanych ułamków nie ma dokładnej reprezentacji maszyny. Nie mają dokładnych form skończonych, ponieważ powtarzają się binarnie. Każdy ułamek maszynowy jest liczbą wymierną w postaci x / 2 n . Teraz stałe są dziesiętne, a każda stała dziesiętna jest liczbą wymierną w postaci x / (2 n * 5 m ). Liczby 5 m są nieparzyste, więc dla żadnej z nich nie ma współczynnika 2 n . Tylko wtedy, gdy m == 0 istnieje skończona reprezentacja zarówno w binarnym, jak i dziesiętnym rozszerzeniu ułamka. Czyli 1,25 jest dokładne, ponieważ wynosi 5 / (2 2 * 5 0), ale 0,1 nie jest, ponieważ wynosi 1 / (2 0 * 5 1 ). W rzeczywistości w serii 1.01 ... 1,99 tylko 3 z liczb są dokładnie reprezentowalne: 1,25, 1,50 i 1,75.
źródło
round($float, 3) == round($other, 3)
Oto rozwiązanie do porównywania liczb zmiennoprzecinkowych lub dziesiętnych
Prześlij
decimal
zmienną nastring
i wszystko będzie dobrze.źródło
Porównywanie liczb zmiennoprzecinkowych dla równości ma naiwny algorytm O (n).
Musisz przekonwertować każdą wartość zmiennoprzecinkową na łańcuch, a następnie porównać każdą cyfrę, zaczynając od lewej strony reprezentacji ciągu każdego zmiennoprzecinkowego, używając operatorów porównania liczb całkowitych. Przed porównaniem PHP automatycznie dokasuje cyfrę w każdej pozycji indeksu do liczby całkowitej. Pierwsza cyfra większa od drugiej przerwie pętlę i zadeklaruje liczbę zmiennoprzecinkową, do której należy, jako większą z nich. Średnio będzie 1/2 * n porównań. Dla równych sobie liczb zmiennoprzecinkowych będzie n porównań. To jest najgorszy scenariusz dla algorytmu. Najlepszym scenariuszem jest to, że pierwsza cyfra każdej liczby zmiennoprzecinkowej jest inna, co powoduje tylko jedno porównanie.
Nie można używać INTEGER COMPARISON OPERATORS na surowych wartościach zmiennoprzecinkowych w celu wygenerowania użytecznych wyników. Wyniki takich operacji nie mają znaczenia, ponieważ nie porównujesz liczb całkowitych. Naruszasz domenę każdego operatora, która generuje bezsensowne wyniki. Dotyczy to również porównania delta.
Używaj operatorów porównania liczb całkowitych do tego, do czego są przeznaczone: do porównywania liczb całkowitych.
UPROSZCZONE ROZWIĄZANIE:
źródło
2019
TL; DR
Użyj mojej funkcji poniżej, w ten sposób
if(cmpFloats($a, '==', $b)) { ... }
cmpFloats($a, '<=', $b)
vsbccomp($a, $b) <= -1
Podsumowanie
Odsłonię tajemnicę.
Więc jeśli spróbujesz poniżej, będzie to równe:
Jak uzyskać rzeczywistą wartość float?
Jak możesz porównać?
==
i!=
, możesz je typecastować na łańcuchy, powinno to działać idealnie:Typ cast ze sznurkiem :
Lub typoszereg z
number_format()
:Ostrzeżenie:
Unikaj rozwiązań, które wymagają matematycznego manipulowania liczbami zmiennoprzecinkowymi (mnożenie, dzielenie itp.), A następnie porównywanie, w większości rozwiązują one pewne problemy i wprowadzają inne problemy.
Sugerowane rozwiązanie
Stworzyłem czystą funkcję PHP (nie są potrzebne żadne zależności / biblioteki / rozszerzenia). Sprawdza i porównuje każdą cyfrę jako ciąg. Działa również z liczbami ujemnymi.
źródło
Funkcja z @evilReiko ma kilka błędów, takich jak te:
W mojej funkcji naprawiłem te błędy, ale i tak w niektórych przypadkach ta funkcja zwraca błędne odpowiedzi:
Naprawiono funkcję porównywania pływaków
Odpowiedz na Twoje pytanie
źródło
Oto przydatna klasa z mojej osobistej biblioteki do radzenia sobie z liczbami zmiennoprzecinkowymi. Możesz dostosować to do swoich upodobań i wstawić dowolne rozwiązanie do metod klasowych :-).
źródło
Prosta odpowiedź:
źródło