Tak jakby pewne wartości zmiennoprzecinkowe wymnożone przez 100 nie zgadzały się z wartością całkowitą. Z tym że ja tu nie widzę logiki. Na błąd trafiłem totalnie przypadkowo. Czy ktoś może się wypowiedzieć w tym temacie?
Komputer nie jest w stanie przechowywać dokładnych wartości liczb zmiennoprzecinkowych - mają one ograniczoną dokładność. Szerzej:
Moim zdaniem wynika to z natury liczb zmiennoprzecinkowych, 2,3 * 100 może dać np, 230,32.
Ale nie daje. Rozumiem że gdyby to były liczby 2,3000000001*100 albo inne dziwne wartości jak w linkach w drugim poście to mogło by się coś dziać. Tutaj są zwykłe wartości.
Jest wyraźnie napisane var_dump($string_amount*100); to float(27296) - czyli 27296 zapisane jako wartość zmiennoprzecinkowa, która jest równa wartości 27296 wartości stałoprzecinkowej. Co ciekawe podobna wartość o tej samej wartości po przecinku daje poprawne wskazania.
Zobaczcie na odświeżony link - dodałem wartość zmiennoprzecinkową o większej ilości zdjęć.
Przyznam że totalnie mnie to zdziwiło, ponieważ funkcja w której się spotkałem z tym problemem działa od długiego czasu, a dopiero po natrafieniu na tą wartość się wywaliła.
W skrócie - liczby wymierne, które są skończone w systemie dziesiętnym, niekoniecznie będą skończone w systemie binarnym, co powoduje nieznaczne utraty części danych.
To by było bez sensu, bo co by się działo w przypadku maszyn, które muszą robić poważniejsze obliczenia niż mnożenie cen przez 100.
Pod odświeżonym linkiem umieściłem działanie string_amount*1000000000 które zwraca wartość float(272960000000). Większej dokładności nie mogę użyć, bo wychodzi poza zakres, czyli w tym konkretnym przypadku teoretycznie wartość musi być poprawna, nie ma żadnych ułamków.
A gdyby nawet były te ułamki zapisanie w postaci binarnej (w sumie jak, skoro to maszyna 32b) to mam w jednym konkretnym elemencie ewidentny brak logiki.
(int)(string_amount*100) int(27296) - wyraźnie zwraca wartość integer, nie ważne czy string_amount*100 to 27295.99999 czy 27296.000001 , to zawsze musi zwrócić liczbę całkowitą i taka wartość musi gdzieś być zapisana w pamięci. Wg mnie w tym przypadku (int)(string_amount*100) musi być równoznaczne z jakas_zmienna = 27296.
Mógłby ktoś sprawdzić ten przypadek z tą wartością (lub innymi) w innych językach programowania niż PHP?
Tutaj jeszcze jedna stronka opisująca problem: http://floating-point-gui.de/basic/ Te drobne niedokładności powodują takie własnie problemy. 272.97*100 = 27297.000000000004
272.96*100 = 27295.999999999996
A rzutowanie do inta po prostu obcina część po przecinku, stąd 27295!=27296.
A co gdy mimo wszystko potrzebujemy tej dokładności? Można użyć specjalnego typu danych, np. w Javie BigDecimal, w PHP się nie orientuję za bardzo, oprócz tego, że istnieją specjalne funkcje do operacji na liczbach
btw. w każdym języku będzie tak samo. Tj. zakładając ten sam typ co w PHP - double (tak, sprawdzałem w Javie :P).
Raczej 27296!=27296 jest true - wg tych wyliczeń co napisałem. Wygląda na to, że var_dump() zwraca to co oczy chcą widzieć, ale maszynowo wyniki działań to są bzdury;-) Niestety teorię nabytą w szkole i moją logikę szlag trafił;-)
Ale raczej faktycznie coś musi w tym być.
MySQL
SELECT '272.96' *100 AS `string` , 272.96 *100 AS `int` , ROUND( 272.96 ) AS `round`
Wyniki
string=27295.999999999996
int=27296.00
round=27296
jak wyrzucasz liczbę zmiennoprzecinkową na ekran to odpalasz magicznego __toString();
zawsze to co jest na ekranie jest rzutowane na stringa
btw. przypadkowo odkryłem kiedyś ten sam problem w JavaScript
– Dodane 15.07.2013 (Pn) 21:17 –
Właśnie badałem ten temat na XAMPIE w WIndows 7 i za każdym razem robi mi się blue screen :twisted: (o dziwo pierwszy raz w życiu widzę blue screen w windows 7)
Jeszcze raz powtórzę:
(double) 272.96*100 != 27296
(double) 272.96*100 = 27295.999999999996
PHP domyślnie zaokrągla liczbę przy echo (à propos tego, że PHP ogłupia). number_format(272.96*100,12) pokaże Ci prawdziwą wartość tego wyrażenia. A skoro rzutowanie do inta obcina część po przecinku porównujesz liczby 27295 i 27296
Przykład z wypisaniem surowej zawartości zmiennych.
Wyraźnie widać, że 272.96 * 100 != 27296.
Liczby zmiennoprzecinkowe są przechowywane z ograniczoną dokładnością, podaczas kolejnych operacji kumulują się kolejne błędy zaokrągleń (czasem większe, czasem mniejsze).
Nigdy nie należy porównywać liczb zmiennoprzecinkowych uzyskanych z obliczeń, jeśli jest taka potrzeba należy sprawdzić czy ich bezwzględna różnica jest mniejsza od zadanego marginesu.
Jak to zrobić?
W PHP używasz funkcji round( LICZBA_ZMIENNOPRZECINKOWA , OPCJONALNIE_ILOŚĆ_MIEJSC_PO_PRZECINKU );
W MySQL ROUND( LICZBA_ZMIENNOPRZECINKOWA * 10^ILOSC_MIEJSC_PO_PRZECINKU ) / ILOSC_MIEJSC_PO_PRZECINKU - choć tutaj robiąć działanie np. ROUND( 12345.6789 * 100 ) / 100 otrzyma się wynik 12345.6700. Wynik w MySQL to też w sumie osobny temat;-)
W JS i chyba C++ podobnie jak w MySQL, a w Pascal, C# i VB chyba podobnie jak w PHP - niestety nie pamiętam.