Grzegorz Lech

Grzegorz Lech GoldenLine.pl

Temat: PHP Bugs

Znalazłem ciekawy case w php.
Załóżmy, że mamy sklep internetowy i wszystkie ceny chcemy trzymać jako liczby całkowite bez przecinków.
Możemy to w prosty sposób osiągnąć mnożąc kwotę razy 100, po czym zrzutować na int'a, aby mieć pewność że będzie to liczba całkowita.

Przykładowy kod może wyglądać tak: $kwota = (int) ($kwota * 100);
Sposobów na to jest wiele, ale nie w tym rzecz. Wynik zawsze będzie ten sam.

Problem pojawia się wtedy, gdy dostajemy kwotę '19.90'.
Po zrzutowaniu jej na int'a otrzymujemy kwotę 1989.

Czy ktoś jest mi w stanie to w logiczny sposób wytłumaczyć dlaczego tak się dzieje?
Pytanie, ile ile jeszcze takich liczb jest...

Można ten bug ominąć na kilka sposobów, pytanie tylko dlaczego tak się dzieje i dlaczego dla tej kwoty...
Marek Skopowski

Marek Skopowski Development Team
Leader

Marek Skopowski

Marek Skopowski Development Team
Leader

Temat: PHP Bugs

ciekawe, ciekawe.
warto pamiętać, że taki sam wynik dają nam wartośći 16.90, 17, 18, 19.
rozwiązaniem jest faktycznie rzutowanie do floata.
ciekawy case, dobrze wiedzieć.

problemy mogą się pojawić, jeżeli dostarczasz klientowi usługę, on zapłacił 19.90, sprawdzasz w bazie, tam jest mniej niż powinno być i musisz mu podziękować, nie wiedząc nawet, że to Twój błąd.
Grzegorz Lech

Grzegorz Lech GoldenLine.pl

Temat: PHP Bugs

Jednym z rozwiązań jest pomnożenie tej liczby przez 1000, a następnie podzielenie przez 10.
Pytanie tylko, czy to się dla wszystkich przypadków sprawdza...

konto usunięte

Temat: PHP Bugs

Ja znam idealne rozwiązanie.

1. Nie rzutować floata na inta
2. Nie wykonywać żadnych operacji na floatach
3. Nie porównywać floatów
4. Ogólnie nie robić z nimi nic. Co najwyżej wyświetlać te które ktoś wprowadził.

Przestałem im ufać już dawno. Korzystajcie z takich narzędzi jak:
http://www.php.net/manual/en/ref.bc.php
http://www.php.net/manual/en/ref.gmp.php

Do tego są biblioteki które operują na ciągach znaków (nie sypnę teraz nazwą ale kto chce to w Google znajdzie). Są wolniejsze niż natywne funkcje PHP ale za to pewne.
Widziałem już różne wpadki z floatami i nauczyłem się że najlepiej nie wykonywać na nich ŻADNYCH operacji bezpośrednio.

konto usunięte

Temat: PHP Bugs

Sklep to pikuś, zrobi się tylko bałagan w fakturach lub kilka złotych w plecy
Ale jeśli popełni się taki błąd w jakichś funkcjach/metodach obliczeniowych to zrobi się niezły burdel
Grzegorz Lech

Grzegorz Lech GoldenLine.pl

Temat: PHP Bugs

bcmul funkcja całkiem dobrze się sprawdza http://php.net/manual/pl/function.bcmul.php
Rafał Korszuń

Rafał Korszuń co-owner @ Kleder

Temat: PHP Bugs

Podobny problem miałem z odejmowaniem

np: 12.90-10.91 dawało 1.9888888888889 (pewnie wynikało z konwersji liczb)

no i potem w niektórych przypadkach update bazy danych przez orm'a się nie udawał.

konto usunięte

Wojciech Sznapka

Wojciech Sznapka CTO @ STS Zakłady
Bukmacherskie

Temat: PHP Bugs

Problem z floatami jest powszechnie znany i nie dotyczy tylko PHP.

konto usunięte

Temat: PHP Bugs


>>> a
19.899999999999999
>>> b
1989.9999999999998
>>> str(b)
'1990.0'

<?php

$a = 19.90;
$b = $a * 100;
$c = (string)$b;

echo $a, "\n", $b, "\n", $c, "\n";

output:

19.9
1990
1990


hmmmm :)

Python - 2.6.1
PHP - 5.3.5

...a tu rozwiązanie:


>>> from decimal import Decimal as d
>>> a = d('19.90')
>>> b = a * 100
>>> a
Decimal('19.90')
>>> b
Decimal('1990.00')
>>> str(b)
'1990.00'
Łukasz K. edytował(a) ten post dnia 01.12.11 o godzinie 18:29
Stanisław P.

Stanisław P. Software designer

Temat: PHP Bugs

Łukasz K.:
>>> a
19.899999999999999
>>> b
1989.9999999999998
>>> str(b)
'1990.0'

Zależy od wersji. Co jest w zmiennej nie zmienia się w zależności od języka, co jest wypisywane: tak.

Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53) 
>>> 19.899999999999999
19.9
Stanisław P. edytował(a) ten post dnia 01.12.11 o godzinie 18:28

konto usunięte

Temat: PHP Bugs


2.7

>>> a
19.9
>>> b
1989.9999999999998
>>> str(b)
'1990.0'

3.2

>>> a
19.9
>>> b
1989.9999999999998
>>> str(b)
'1989.9999999999998'
>>> 19.89999999999999
19.89999999999999
>>> 19.899999999999999
19.9



I tak śmieszne :P
Stanisław P.

Stanisław P. Software designer

Temat: PHP Bugs

Łukasz K.:
I tak śmieszne :P
Dlaczego? Nie rozumiem... Żadna z tych odpowiedzi nie jest dokładna tak czy tak. Bo nie chcesz zobaczyć "dokładnej" odpowiedzi. Dostajesz mniej lub bardziej zaokrągloną i +/- bliską temu czego oczekujesz.

Chyba że naprawdę chciałeś dostać:

In [1]: a=19.899999999999999
In [2]: from decimal import Decimal
In [3]: Decimal(a)
Out[3]: Decimal('19.89999999999999857891452847979962825775146484375')

konto usunięte

Temat: PHP Bugs

Stanisław P.:
Łukasz K.:
I tak śmieszne :P
Dlaczego? Nie rozumiem... Żadna z tych odpowiedzi nie jest dokładna tak czy tak. Bo nie chcesz zobaczyć "dokładnej" odpowiedzi. Dostajesz mniej lub bardziej zaokrągloną i +/- bliską temu czego oczekujesz.

Chyba że naprawdę chciałeś dostać:

In [1]: a=19.899999999999999
In [2]: from decimal import Decimal
In [3]: Decimal(a)
Out[3]: Decimal('19.89999999999999857891452847979962825775146484375')

Oj wiem o tym. Tak po prostu sobie przetestowałem zachowanie w różnych wersjach ;) Sądziłem, że wyniki będą powtarzalne, ale wielkiego znaczenia to nie ma.

(O niedokładności mowa oczywiście w wersji zmiennoprzecinkowej)

konto usunięte

Temat: PHP Bugs

"This rounding error is the characteristic feature of floating-point computation."

źródło: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldb...

konto usunięte

Temat: PHP Bugs

Grzegorz Lech:
Jednym z rozwiązań jest pomnożenie tej liczby przez 1000, a następnie podzielenie przez 10.
Pytanie tylko, czy to się dla wszystkich przypadków sprawdza...

$a = 19.90;
$b = (int) (round( $a * 100));
echo $b;

//

to bedzie zawsze dzialac
Stanisław P.

Stanisław P. Software designer

Temat: PHP Bugs

Tomasz Grzechowski:
$a = 19.90;
$b = (int) (round( $a * 100));
echo $b;

to bedzie zawsze dzialac
O ile nigdy nie będziesz robił na nich operacji matematycznych.

1.0 - 0.9 - 0.1 < 0

Chyba że zawsze na każdym pojednyczym kroku będziesz robił konwersję w jedną i drugą stronę... Pytanie tylko - po co się wysilać? ;)

konto usunięte

Temat: PHP Bugs

wszelkich operacji matematycznych w php unikam jak ognia
przejechałem sie na tym już kilka razy
w php brakuje typów, rzutowanie ręczne nie zawsze się sprawdza,
dlatego wszelkie obliczenia na danych wykonuje po stronie bazy danych (pracuje na prostgres)

skoro mam do bazy zapisać 19,89*100 to po co php ma to obrabiać

przykład
insert into (...) values (..., (19.89*100), ...);

ewentualnie tam mogę rzutować

insert into (...) values (..., (19.89*100)::int, ...);

jeżeli chcecie robić operacje matematyczne w php to ożywajcie np
http://www.php.net/manual/pl/ref.bc.phpSebastian Krajewski edytował(a) ten post dnia 02.12.11 o godzinie 08:41
Stanisław P.

Stanisław P. Software designer

Temat: PHP Bugs

Sebastian Krajewski:
skoro mam do bazy zapisać 19,89*100 to po co php ma to obrabiać

przykład
insert into (...) values (..., (19.89*100), ...);

ewentualnie tam mogę rzutować

insert into (...) values (..., (19.89*100)::int, ...);
Wiesz, że dostaniesz dokładnie ten sam wynik czy zrobisz to w php, czy sql, prawda? Te same błędy precyzji, automatyczne konwersje, etc.

Następna dyskusja:

Narzędzia do PHP




Wyślij zaproszenie do