Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [biblioteka] Rounder
Forum PHP.pl > Inne > Oceny
greycoffey
Hej,

sfrustrowany dzisiaj zachowaniem funkcji round() której zaokrąglanie half up i half down nie jest tym, za co się podaje, spłodziłem klasę, która robi to poprawnie. Zawiera interfejs oraz domyślną implementację zaokrąglacza, druga implementacja (oparta na bcmath) właśnie się tworzy (co do tego, macie jakiś pomysł na implementację metod roundHalfEven() i roundHalfOdd() w Pamil\Rounder\BcmathRounder w gałęzi bcmath-support?).

Przykład użycia i kod na GitHubie.
gitbejbe
wygląda fajnie, klasa czytelna i zrozumiała. Chociaż zastanawiam się, na ile jest to przydatne ? Problem zaokrągleń jest na tyle banalny, że można samemu napisać szybko prosty kodzik, który będzie działał tak jak trzeba, nie zaprzęgając do tego klas. Po za tym zaokrągleń używa się zazwyczaj rzadko, tak czy siak szacun za inicjatywę : ). co do bcmath nigdy nie potrzebowałem takiej precyzji ale z implementacją powinno być podobnie jak teraz. Wymagasz podania liczby oraz wskazania precyzji. Możesz też dorobić dla bcmath operacje arytmetyczne na 2 liczbach, z pewnością wzbogaciłoby to wartość klasy
pyro
Wygląda na sensownie skonstruowany kawałek kodu. Niemniej nie rozumiem sensu użycia interfejsów i abstrakcji... można na różne sposoby np. zaokrąglać liczbę w górę itd.?

Tzn. umiałbyś np. inaczej napisać funkcję floor(), żeby dla 3.15 zwracała coś innego niż 3?

Chwała Ci za użycie przykładów w komentarzach, bo robi to stosunkowo niewiele osób, a różnica jest tak kolosalna jak różnica pomiędzy użyciem dużej szczotki, a szczoteczki do zębów w celu umycia sedesu smile.gif
destroyerr
Cytat
sfrustrowany dzisiaj zachowaniem funkcji round() której zaokrąglanie half up i half down nie jest tym, za co się podaje

Możesz podać przykład niepoprawnego działania? Sprawdziłem i u mnie zachowywała się normalnie. Przejrzałem Twoje testy i wskazują one na to, że błędnie rozumujesz zaokrąglanie w górę i dół. Chodzi o to, że zaokrągla się wartość bezwzględną, tzn. tak, żeby było od zera w kierunku nieskończoności (dodatniej lub ujemnej). Co zresztą objaśnione jest w dokumentacji:
Cytat
Round val up to precision decimal places away from zero, when it is half way there. Making 1.5 into 2 and -1.5 into -2.

Jeśli chodzi o ten przypadek to funkcja działa zgodnie z opisem.
greycoffey
@gitbejbe, trafiłeś w sedno, czas napisać coś, co usprawni obsługę dużych liczb, bo to funkcyjne API bcmath jest straszne, zwłaszcza gdy przychodzi do porównywania liczb.

@pyro, co do tej abstrakcji, w planach mam dodanie obsługi zaokrąglania dla dużych liczb (i to można wykonać za pomocą bcmath, lub własnego algorytmu, w każdym razie będzie to obsługa dużych liczb w postaci łańcuchów znaków, nie liczb).

Interfejsy - dla swobodnego dodawania i wymieniania implementacji.
Klasa abstrakcyjna - by nie pisać powtarzalnej metody round($number, $precision, $roundingMode) w implementacjach

@destroyerr, nazwa tej stałej to "PHP_ROUND_HALF_UP", jest to zaokrąglanie od połowy w stronę pozytywnej nieskończoności. Funkcja działa zgodnie z opisem stałej, nie z nazwą stałej.

Źródła:
Zaokrąglanie od połowy w stronę pozytywnej nieskończoności
Zaokrąglanie od połowy w stronę nieskończoności

W pierwszym linku możemy przeczytać:
Cytat
However, some programming languages (such as Java) define HALF_UP as round half away from zero.

To samo tyczy się PHP.
destroyerr
@greycoffey przekonałeś mnie. Za bardzo skupiłem się na manualu PHP. Teraz widzę, że gdybym przeczytał opis Twojego projektu to nie musiałbym zadawać tego pytania.
Git, composer, travis, testy, własne wyjątki a i projekt przydatny - można oceniać tylko pozytywnie.
Mała uwaga prośba, jeśli chciałbym tego użyć w zastępstwie funkcji \round() to przydałby się jej odpowiednik w Twojej przestrzeni nazw. Lub klasa statyczna z odpowiednimi funkcjami. Zwłaszcza, że Twoje obiekty nie posiadają żadnego stanu.
gitbejbe
@pyro

zastosowanie interfejsów i abstrakcji też mnie zastanowiło. Faktycznie kod jest bardziej pro i wymusza on schematy działania, ale czy nie jest to przyrost treści nad formą ? Tak naprawdę do każdego rodzaju zaokrąglenia wystarczy napisać samą metodę w której jest tylko to co trzeba. Każdy jednak ma swoją wizje, do Twojej tak czy siak trudno się przyczepić. Dobra robota !
greycoffey
@destroyerr, masz rację, przerobiłem Roundera na modłę statyczną, teraz jest prościej z niego korzystać, a tutorial także przerobiłem na prostszy (wymagana tylko jedna klasa, jako że Rounder implementuje RounderInterface, to dziedziczy po nim stałe).

@gitbejbe, czy ja wiem czy bardziej pro, na pewno łatwiejszy do ogarnięcia i ewentualnej zmiany implementacji, czy korzystania równolegle z dwóch Rounderów - mamy pewność, że mają taki sam interfejs.

Wersja 1.0 właśnie została wydana, w wersji 1.1 zostanie wprowadzona obsługa zaokrąglania dużych liczb jako BcmathRounder.
Crozin
Drobne uwagi:
- implements RounderInterface możesz spokojnie przenieść do AbstractRounder. Unikniesz dzięki temu potrzeby wstawiania kilku metod abstrakcyjnych.
- Zamiast tworzyć MethodNotImplementedException spokojnie mógłbyś skorzystać z wbudowanego BadMethodCallException ze stosowną informacją, zaś RoundingModeNotFoundException mógłby zostać zastąpiony przez InvalidArgumentException.

Poważne uwagi:
- Po co zmieniłeś poprawny, niestatyczny kod, na statyczny? Połowa zalet OOP i IDD właśnie została zaprzepaszczona.
pyro
Cytat(greycoffey @ 31.12.2013, 12:58:25 ) *
@pyro, co do tej abstrakcji, w planach mam dodanie obsługi zaokrąglania dla dużych liczb (i to można wykonać za pomocą bcmath, lub własnego algorytmu, w każdym razie będzie to obsługa dużych liczb w postaci łańcuchów znaków, nie liczb).

Interfejsy - dla swobodnego dodawania i wymieniania implementacji.
Klasa abstrakcyjna - by nie pisać powtarzalnej metody round($number, $precision, $roundingMode) w implementacjach



Cytat(gitbejbe @ 31.12.2013, 14:52:56 ) *
@pyro

zastosowanie interfejsów i abstrakcji też mnie zastanowiło. Faktycznie kod jest bardziej pro i wymusza on schematy działania, ale czy nie jest to przyrost treści nad formą ? Tak naprawdę do każdego rodzaju zaokrąglenia wystarczy napisać samą metodę w której jest tylko to co trzeba. Każdy jednak ma swoją wizje, do Twojej tak czy siak trudno się przyczepić. Dobra robota !


Chyba się trochę nie zrozumieliśmy. Interfejsy są po to, żeby móc skorzystać z danego typu implementacji na różne sposoby. Przykładowo na podstawie danego interfejsu mogę zwracać różne wyniki, np:

  1. <?php
  2.  
  3. class Calculator {
  4.  
  5. private $a;
  6. private $b;
  7. private $strategy;
  8.  
  9. public function __construct($a, $b) {
  10. $this->a = $a;
  11. $this->b = $b;
  12. }
  13.  
  14. public function setStrategy($strategyName) {
  15. $className = ucfirst(strtolower($strategyName));
  16.  
  17. if(class_exists($className) && is_subclass_of($className, 'CountingStrategy')) {
  18. $this->strategy = new $className();
  19. return true;
  20. } else {
  21. throw new StrategyNotFoundException('Calculation type not found');
  22. }
  23. }
  24.  
  25. public function calculate() {
  26. $this->strategy->setVariables($a, $b);
  27. return $this->strategy->getResult();
  28. }
  29.  
  30. }
  31.  
  32. abstract class CountingStrategy {
  33. private $a;
  34. private $b;
  35.  
  36. abstract public function getResult();
  37. public function setVariables($a, $b) {
  38. $this->a = $a;
  39. $this->b = $b;
  40. }
  41. }
  42.  
  43. class Sum extends CountingStrategy {
  44. public function getResult() {
  45. return $this->a + $this->b;
  46. }
  47. }
  48.  
  49. class Subtract extends CountingStrategy {
  50. public function getResult() {
  51. return $this->a - $this->b;
  52. }
  53. }
  54.  
  55. ?>


Albo mogę zwracać ten sam wynik, ale np. jedno kosztem drugiego. Przykład: jest sobie klasa, która działa szybko, ale liczy liczby do jakiegoś MAX_INT oraz druga, która jest wolniejsza, ale liczy też ogromne liczby.

Tutaj widzę trzeci przypadek, że jest sobie coś w stylu:

  1. interface RandomizerInterface {
  2. public function generate();
  3. }
  4.  
  5. class NormalRand implements RandomizerInterface {
  6.  
  7. public function generate() {
  8. return rand();
  9. }
  10.  
  11. }
  12.  
  13. class MtlRand implements RandomizerInterface {
  14.  
  15. public function generate() {
  16. return mt_rand();
  17. }
  18.  
  19. }


Obie klasy pozwalają na generowanie randomowych liczb, ale wyliczono, że mt_rand() działa ok. 4 razy szybciej. Inna implementacja po prostu nie ma sensu, bo zwracanie randoma to zwracanie randoma. Tu się nic nie wymyśli. A że mt_rand() działa szybciej, jest w tym przypadku jedyną słuszną implementacją, w związku z tym wystarczy:
  1. class MtlRand {
  2.  
  3. public function generate() {
  4. return mt_rand();
  5. }
  6.  
  7. }


bez zbędnych interfejsów. I widzę swojego rodzaju odbicie przypadku w przytoczonym kodzie. O to mi chodziło.

No, a teraz zmykam na sylwestra.

Do siego.
lukasz1985
Na tym polega cały nonsens takich przedsięwzięć, że więcej czasu zajmuje pisanie durnych testów jednostkowych i tworzenie repozytorium na GitHubie niż właściwe pisanie kodu. Ten interfejs to też sobie można gdzieś wsadzić bo wystarczy abstrakcyjna klasa, która sama w sobie stanowi interfejs. Na ten temat akurat wypowiedziałem się tutaj: Abuse of interfaces, in Java.

Czekam na bibliotekę, która będzie potrafiła poprawnie wykonywać operacje algebraiczne - będzie zwracać liczby zmiennoprzecinkowe przy dzieleniu dwóch liczb całkowitych, bo w końcu kto by pamiętał o rzutowaniu typu.

Dla niekumatych to była ironia.
com
@up to możesz dać choć jeden sensowny powód poco to napisałeś?
Crozin
Cytat
Interfejsy są po to, żeby móc skorzystać z danego typu implementacji na różne sposoby.
Interfejsy są po to by korzystać z różnych implementacji w ten sam sposób. To o czym piszesz w dalszej części postu w tym konkretnym przypadku jest błędne. Tutaj chcemy mieć obiekt, który będzie potrafił wykonywać różnego rodzaju zaokrąglenia (w sumie 11 sposobów) na różnych podmiotach (zwykła liczba czy BC Math) i interfejs służy właśnie do wyizolowania tych podmiotów, nie samych metod zaokrąglania. Interfejs kalkulatora, który podałeś również jest błędny. Każdy kalkulator powinien umieć dodać czy odejmować. Przedstawiony przez Ciebie interfejs powinien nazywać się Operation czy właściwie BinaryOperation (od dwóch argumentów, nie od danych binarnych).
Cytat
Albo mogę zwracać ten sam wynik, ale np. jedno kosztem drugiego. Przykład: jest sobie klasa, która działa szybko, ale liczy liczby do jakiegoś MAX_INT oraz druga, która jest wolniejsza, ale liczy też ogromne liczby.
Tutaj już podałeś poprawny przykład.
Cytat
Inna implementacja po prostu nie ma sensu, bo zwracanie randoma to zwracanie randoma. Tu się nic nie wymyśli. A że mt_rand() działa szybciej, jest w tym przypadku jedyną słuszną implementacją, w związku z tym wystarczy: [...]
Tutaj znowu błąd. mt_rand nie jest jedyną słuszną implementacją w tym wypadku. Przecież mogę potrzebować takiej implementacji, która zawsze zwróci 42 albo jakiś z góry określony cykl liczb na przykład na potrzeby testów. Albo taką, która będzie w przeciwieństwie do mt_rand generować kryptograficznie bezpieczną liczbę. Albo jeszcze inną, która zwróci inny rozkład losowych liczb dla danego przedziału.

Cytat
Na tym polega cały nonsens takich przedsięwzięć, że więcej czasu zajmuje pisanie durnych testów jednostkowych i tworzenie repozytorium na GitHubie niż właściwe pisanie kodu.
Ale to, że taki kod da się później normalnie wykorzystywać w innych projektach już nie napiszesz.
Cytat
Ten interfejs to też sobie można gdzieś wsadzić bo wystarczy abstrakcyjna klasa, która sama w sobie stanowi interfejs.
Klasa abstrakcyjna raczej nie powinna stanowić publicznego interfejsu w językach z pojedynczym dziedziczeniem. Na pewno nie w tym konkretnym przypadku.
pyro
Cytat(Crozin @ 6.01.2014, 16:42:57 ) *
Interfejsy są po to by korzystać z różnych implementacji w ten sam sposób. To o czym piszesz w dalszej części postu w tym konkretnym przypadku jest błędne. Tutaj chcemy mieć obiekt, który będzie potrafił wykonywać różnego rodzaju zaokrąglenia (w sumie 11 sposobów) na różnych podmiotach (zwykła liczba czy BC Math) i interfejs służy właśnie do wyizolowania tych podmiotów, nie samych metod zaokrąglania. Interfejs kalkulatora, który podałeś również jest błędny. Każdy kalkulator powinien umieć dodać czy odejmować. Przedstawiony przez Ciebie interfejs powinien nazywać się Operation czy właściwie BinaryOperation (od dwóch argumentów, nie od danych binarnych).


Kurcze, już w momencie pisania tamtego postu przeczuwałem, że ktoś przyczepi się do tych przykładów i zarzuci branie ich za autorytet lub jakiś w pełni funkcjonalny przykład. Czułem, że pownienem był napisać jakieś ostrzeżenie czy coś wink.gif . Pragnę więc oświadczyć Tobie, jak i potomnym, że z praktycznego punktu widzenia żadna z powyższych klas nie ma sensu i służyła tylko przedstawieniu toku myśleniowego "dlaczego tak a nie inaczej". Zostało to zresztą "pisane na kolanie", co widać chociażby po braku wcięć (Notepad++). Coś na styl kaczek, piesków, kotków z dajGlos(); (przykład często widziany w internecie).

Cytat(Crozin @ 6.01.2014, 16:42:57 ) *
Tutaj znowu błąd. mt_rand nie jest jedyną słuszną implementacją w tym wypadku. Przecież mogę potrzebować takiej implementacji, która zawsze zwróci 42 albo jakiś z góry określony cykl liczb na przykład na potrzeby testów. Albo taką, która będzie w przeciwieństwie do mt_rand generować kryptograficznie bezpieczną liczbę. Albo jeszcze inną, która zwróci inny rozkład losowych liczb dla danego przedziału.


W sumie wybrałem trochę kiepski przykład na przedstawienie problemu. Nie mniej specjalnie podałem tylko dwie klasy i cały czas pisałem dlaczego w tym przypadku jedna wyklucza drugą i dlaczego tylko odnosić się tylko do tego, co zostało napisane.
Cytat(pyro)
Obie klasy pozwalają na generowanie randomowych liczb, ale wyliczono, że mt_rand() działa ok. 4 razy szybciej. Inna implementacja po prostu nie ma sensu, bo zwracanie randoma to zwracanie randoma. Tu się nic nie wymyśli. A że mt_rand() działa szybciej, jest w tym przypadku jedyną słuszną implementacją


bo takie rzeczy jak losowanie z przedziału liczb w ogóle nie zostały tu po prostu przedstawione. Ale tak jak wspomniałem - przykład rzeczywiście nie najlepszy. Załóżmy, że dla tego przykładu nie istnieją dodatkowe założenia jak możliwość generowanie kryptograficznie bezpiecznej liczby albo testów. Zachęcam w międzyczasie do napisania lepszego przykładu z powodów przedstawionych.
lukasz1985
"Klasa abstrakcyjna raczej nie powinna stanowić publicznego interfejsu w językach z pojedynczym dziedziczeniem. Na pewno nie w tym konkretnym przypadku."

Głupszego sformułowanie nie słyszałem. Klasa sama w sobie stanowi interfejs i definiuje kontrakt, więc nie ma tutaj "nie powinna" skoro fakt zupełnie zaprzecza nawet samemu "nie może". Służy do tego zazwyczaj słowo kluczowe "public". Definicja słowa interfejs nie obejmuje sobą tylko tego co w języku programowania ma słowo kluczowe "interface". Klasy abstrakcyjne są podstawą programowania obiektowego, a interfejsy głównie wszystkim poprzewracały w głowach. Rezultatem jest to, że zamiast właściwej kompozycji i dziedziczenia wszędzie są pociskane interfejsy i powielany kod.

Nikt nie potrzebuje klasy do zaokrąglania liczb, a jeśli już to wystarczy, że klasa "Rounder" będzie zaopatrzona w metody abstrakcyjne implementowane w klasach pochodnych (MathRounder czy InnyRounder).

A tworzenie takiej biblioteki mija się z celem bo jest ona za mała i każdy komu zaokrąglanie nie pasuje prędzej napisze własne funkcje niż znajdzie to w sieci. Nie ma też sensu pisanie testów do zadań trywialnych.
emp
Cytat(Crozin)
Klasa abstrakcyjna raczej nie powinna stanowić publicznego interfejsu w językach z pojedynczym dziedziczeniem. Na pewno nie w tym konkretnym przypadku.

Przeczytaj pare klasycznych książek i naucz się programować zanim zaczniesz pociskać takie farmazony i pozowac.

Klasa jest zupełnie nieprzydatna nikomu poza autorem i wszystko tak jak pisze lukasz1985
Crozin
Cytat
Kurcze, już w momencie pisania tamtego postu przeczuwałem, że ktoś przyczepi się do tych przykładów i zarzuci branie ich za autorytet lub jakiś w pełni funkcjonalny przykład.
Wiesz, przykłady powinny w miarę możliwości być trafne. wink.gif
Cytat
Klasa sama w sobie stanowi interfejs i definiuje kontrakt, więc nie ma tutaj "nie powinna" skoro fakt zupełnie zaprzecza nawet samemu "nie może". Służy do tego zazwyczaj słowo kluczowe "public". Definicja słowa interfejs nie obejmuje sobą tylko tego co w języku programowania ma słowo kluczowe "interface".
Dlatego właśnie pisałem o publicznym interfejsie, przez który rozumiałem interfejs jaki dana biblioteka udostępnia kodowi korzystającemu z niej. Tutaj w przypadku PHP będzie się to ograniczać do TypeHintingu czy informacji o zwracanych typach danych (phpDoc).
Cytat
Rezultatem jest to, że zamiast właściwej kompozycji i dziedziczenia wszędzie są pociskane interfejsy i powielany kod.
Nie rozumiem w czym niby interfejs miałby przeszkadzać w dziedziczeniu czy kompozycji, pomijając fakt, że w przypadku tego ostatniego, wymusza tworzenie lepszego kodu (nieograniczony polimorfizm/nieskrępowane dziedziczenie).
Cytat
Nikt nie potrzebuje klasy do zaokrąglania liczb, a jeśli już to wystarczy, że klasa "Rounder" będzie zaopatrzona w metody abstrakcyjne implementowane w klasach pochodnych (MathRounder czy InnyRounder).
Jak widać @greycoffey potrzebował i zastosowanie tutaj interfejsu w niczym nie przeszkadza, a w przypadku gdy InnyRounder będzie już dziedziczył po czymś innym nie będzie miał problemu z podpięciem swojej klasy.
Cytat
A tworzenie takiej biblioteki mija się z celem bo jest ona za mała i każdy komu zaokrąglanie nie pasuje prędzej napisze własne funkcje niż znajdzie to w sieci.
Od kiedy to przydatność mierzy się liczbą linii kodu? Ja, gdybym potrzebował poprawnego zaokrąglania czy potrzebowałbym innych metod zaokrąglania wolałbym skorzystać z pow. biblioteki, niż tracić czas na pisanie jej samemu.
Cytat
Nie ma też sensu pisanie testów do zadań trywialnych.
Kto powiedział, że zaokrąglanie jest trywialne. Ja tam widzę co najmniej kilka miejsc, gdzie coś może źle zadziałać.
lukasz1985
Naprawdę wątpię, żeby komuś chciało się do zaokrąglania liczb szukać biblioteki. Z całym szacunkiem do włożonego trudu (w pisanie klasy i implementacji / testów jednostkowych nie zdzierżę bo nie cierpię nadgorliwości).

@crozin Przeczytaj sobie mój artykuł, o którym pisałem w jednym z moich poprzednich postów i przemyśl sobie to szybko dojdziesz do wniosku, że interfejsy mogą stać się problemem jeśli się ich używa "zamiast" po prostu dziedziczenia.
Crozin
Cytat
Naprawdę wątpię, żeby komuś chciało się do zaokrąglania liczb szukać biblioteki.
Skąd wiesz czy komuś będzie się chciało wpisać w Googlea php number rounding library?
Cytat
Z całym szacunkiem do włożonego trudu (w pisanie klasy i implementacji / testów jednostkowych nie zdzierżę bo nie cierpię nadgorliwości).
Obecność testów jest dla wielu osób/projektów wręcz wymogiem przy decydowaniu się na skorzystanie z kodu osób trzecich.

Pamiętaj, że nie każdy ma takie potrzeby jak Ty.
Cytat
@crozin Przeczytaj sobie mój artykuł, o którym pisałem w jednym z moich poprzednich postów i przemyśl sobie to szybko dojdziesz do wniosku, że interfejsy mogą stać się problemem jeśli się ich używa "zamiast" po prostu dziedziczenia.
Chodzi Ci o artykuł Abuse of interfaces in Java? Przeczytałem go od razu i nie zgadzam się z częścią tez w nim zawartych. Nie wiem też w czym interfejsy same w sobie miałby utrudniać dziedziczenie (pomijaj też fakt, że ich wykorzystanie umożliwia w ogóle skorzystanie z dziedziczenia w wielu przypadkach w przeciwieństwie do klas abstrakcyjnych). Mam wrażenie jakbyś podchodził do tematu na zasadzie interfejsy kontra dziedziczenie/klasy abstrakcyjnie, zamiast normalnie, tj. interfejsy plus dziedziczenie/klasy abstrakcyjne.
greycoffey
Hej, ogarnąłem się po sylwestrze także mogę odpowiedzieć smile.gif

Co do Roundera, to uważam jednak, że ustatycznienie go było złą decyzją. Trochę się wzorowałem na istniejącej funkcji round, ale niestety biblioteka standardowa w PHP nie jest dobrym wzorcem smile.gif Myślę, że @Crozin ma rację co do tej kwestii. Anyway, podobne opinie co do kobyłowatości i przekombinowania słyszałem wobec Symfony2, które właściwie zapoczątkowało prawdziwy przełom w świecie PHP.

Wersja "odstatyczniona" 2.0 (zgodnie z semantic versioning) prawdopodobnie na dniach wyląduje na Githubie.

Cytat
testów jednostkowych nie zdzierżę bo nie cierpię nadgorliwości

Życzę miłego debugowania, podczas którego zrozumiesz, że nie jest to żadna nadgorliwość, a właściwie niezbędny element każdego kodu. Używając mojej klasy czy pisząc nowe implementacje można się w chwilę upewnić, czy wszystko działa tak jak powinno.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.