Czy w prostej grze symulacyjnej dla biznesu (wbudowanej w Javę + Slick2D) aktualna kwota pieniędzy gracza powinna być przechowywana w postaci innej float
karty int
?
W moim przypadku większość transakcji będzie korzystała z centów (0,50 USD, 1,20 USD itp.) I będą obejmowały proste obliczenia stopy procentowej.
Widziałem ludzi mówiących, że nigdy nie powinieneś używać float
waluty, a także ludzi, którzy twierdzą, że nigdy nie powinieneś używać int
waluty. Czuję, że powinienem zastosować int
i zaokrąglić wszelkie niezbędne obliczenia procentowe. Czego powinienem użyć?
Currency
typu jak Delphi, który używa skalowanej matematyki stałoprzecinkowej, aby dać ci matematy dziesiętne bez problemów z precyzją nieodłącznych od zmiennoprzecinkowych?BigDecimal
tego rodzaju problemy.Odpowiedzi:
Możesz użyć
int
i rozważyć wszystko w centach. 1,20 USD to zaledwie 120 centów. Na wyświetlaczu wstawiasz przecinek tam, gdzie należy.Obliczenia odsetek byłyby po prostu obcięte lub zaokrąglone w górę. Więc
W ten sposób nie będziesz mieć bałaganu po przecinku. Możesz wzbogacić się, dodając do swojego konta bankowego niezliczone pieniądze (z powodu zaokrągleń)
źródło
round
nie ma prawdziwego powodu, aby rzucić na toint
, czy jest?Okej, wskoczę.
Moja rada: to gra. Uspokój się i użyj
double
.Oto moje uzasadnienie:
float
ma problem z precyzją, który pojawia się przy dodawaniu jednostek do milionów, więc chociaż może być łagodny, unikałbym tego typu.double
zaczyna się problemy z kwintillonami (miliard miliardów).int
i jestlong
bezużyteczne, ponieważ nie wiesz, gdzie się zatrzymać miejsca dziesiętne.BigDecimal
da ci dowolną precyzję, ale stanie się boleśnie powolny, chyba że kiedyś dokonasz precyzji. Jakie są zasady zaokrąglania? Jak wybrać, gdzie się zatrzymać? Czy warto mieć więcej precyzyjnych bitów niżdouble
? Nie wierzę.Powodem stosowania arytmetyki punktu stałego w aplikacjach finansowych jest to, że są deterministyczne. Reguły zaokrąglania są doskonale zdefiniowane, czasem przez prawo, i muszą być ściśle stosowane, ale w pewnym momencie nadal występuje zaokrąglanie . Każdy argument przemawiający za danym typem opartym na precyzji jest prawdopodobnie fałszywy. Wszystkie typy mają problemy z precyzją w rodzaju obliczeń, które zamierzasz wykonać.
Praktyczne przykłady
Widzę sporo komentarzy twierdzących, że nie zgadzam się z zaokrągleniem lub precyzją. Oto kilka dodatkowych przykładów ilustrujących to, co mam na myśli.
Przechowywanie : jeśli jednostką podstawową jest cent, prawdopodobnie będziesz chciał zaokrąglić do najbliższego centa podczas przechowywania wartości:
Przy zastosowaniu tej metody nie będziesz mieć absolutnie żadnych problemów z zaokrąglaniem, których nie miałbyś również w przypadku liczb całkowitych.
Pobieranie : wszystkie obliczenia można wykonać bezpośrednio na podwójnych wartościach, bez konwersji:
Zauważ, że powyższy kod nie działa, jeśli zastąpi
double
sięint64_t
: nie będzie niejawna konwersjadouble
, a następnie obcięcie doint64_t
, z możliwością utraty information.data.GetValue ()Porównywanie : porównania są jedną rzeczą, którą należy uzyskać w przypadku typów zmiennoprzecinkowych. Sugeruję użycie metody porównania, takiej jak ta:
Zaokrąglanie uczciwości
Załóżmy, że masz na koncie 9,99 USD z 4% odsetkami. Ile powinien zarobić gracz? Zaokrąglając liczbę całkowitą, otrzymujesz 0,03 $; zaokrąglanie zmiennoprzecinkowe daje 0,04 USD. Uważam, że to drugie jest bardziej sprawiedliwe.
źródło
int_64t
s.10/3 = 3+3+4
lub10/3 = 3+3+3 (+1)
. Dla wszystkich innych operacji liczby całkowite działają idealnie, w przeciwieństwie do liczb zmiennoprzecinkowych, które mogą powodować problemy z zaokrąglaniem w dowolnej operacji.Typy zmiennoprzecinkowe w Javie (
float
,double
) nie są dobrą reprezentacją walut z jednego głównego powodu - występuje błąd maszynowy w zaokrąglaniu. Nawet jeśli to prosta kalkulacja zwraca liczbę całkowitą - jak12.0/2
(6,0), przy czym zmiennoprzecinkowe może błędnie runda to (z powodu tho konkretnej reprezentacji tych typów pamięci) jako6.0000000000001
lub5.999999999999998
lub podobny. Jest to wynik specyficznego zaokrąglenia maszyny występującego w procesorze i jest unikalny dla komputera, który go obliczył. Zwykle rzadko jest problem z obsługą tych wartości, ponieważ błąd jest dość zaniedbany, ale wyświetlenie go użytkownikowi nie jest łatwe.Możliwym rozwiązaniem tego byłoby użycie niestandardowych implementacji typu danych zmiennoprzecinkowych, takich jak
BigDecimal
. Obsługuje lepsze mechanizmy obliczeniowe, które przynajmniej izolują błędy zaokrąglania, które nie są specyficzne dla maszyny, ale są wolniejsze pod względem wydajności.Jeśli potrzebujesz wysokiej wydajności, lepiej trzymaj się prostych typów. Jeśli operujesz ważnymi danymi finansowymi , a każdy cent jest ważny (np. Aplikacja Forex lub gra kasynowa), polecam użyć
Long
lublong
.Long
pozwoli ci obsługiwać duże ilości i dobrą precyzję. Załóżmy, że potrzebujesz, powiedzmy, 4 cyfr po przecinku, wystarczy pomnożyć tę kwotę przez 10000. Mając doświadczenie w opracowywaniu gier kasynowych online, widziałem,Long
że często jestem używany do przedstawiania pieniędzy w centach . W aplikacjach Forex dokładność jest ważniejsza, więc potrzebujesz większego mnożnika - wciąż liczby całkowite są wolne od problemów z zaokrąglaniem maszyny (oczywiście zaokrąglania ręcznego, jak w 3/2, powinieneś sobie poradzić).Dopuszczalną opcją byłoby użycie standardowych typów zmiennoprzecinkowych -
Float
aDouble
jeśli wydajność jest ważniejsza niż dokładność do setnych centa. Następnie, zgodnie z logiką wyświetlania, wystarczy użyć predefiniowanego formatowania , aby brzydota potencjalnego zaokrąglenia maszyny nie dotarła do użytkownika.źródło
W przypadku gier na małą skalę i tam, gdzie szybkość procesu ważna jest pamięć (ze względu na precyzję lub praca z koprocesorem matematycznym może boleśnie spowolnić), wystarczy podwójna .
Ale w przypadku gier na dużą skalę (na przykład gier społecznościowych) i tam, gdzie szybkość procesu, pamięć nie jest ograniczona, BigDecimal jest lepszy. Ponieważ tutaj
Zasoby:
Od https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency
Z Bloch, J., Effective Java, wyd. 2, pozycja 48:
Zobacz także
źródło
Aby zapisać swoją walutę
long
i obliczyć swoją walutędouble
, przynajmniej jako zapasową. Chcesz, aby wszystkie transakcje odbywały się jakolong
.Powodem, dla którego chcesz przechowywać swoją walutę,
long
jest to, że nie chcesz stracić żadnej waluty.Załóżmy, że używasz
double
i nie masz pieniędzy. Ktoś daje ci trzy dziesięciocentówki, a następnie bierze je z powrotem.Cóż, to nie jest takie fajne. Może ktoś z 10 $ chce przekazać swoją fortunę, najpierw dając ci trzy dziesięciocentówki, a następnie 9,70 $ komuś innemu.
A potem oddajesz im dziesięciocentówki:
To jest po prostu zepsute.
Teraz użyjmy długiego, a my będziemy śledzić dziesiąte centy (czyli 1 = 0,001 $). Dajmy każdemu na świecie miliard sto dwanaście milionów siedemdziesiąt pięć tysięcy sto czterdzieści trzy dolary:
Um, czekaj, możemy dać każdemu ponad miliard dolarów i wydać tylko nieco ponad dwa? Przepełnienie jest tutaj katastrofą.
Tak więc, ilekroć obliczasz kwotę, którą chcesz przelać, użyj jej
double
iMath.round
zdobądźlong
. Następnie napraw salda (dodaj i odejmij oba konta) za pomocąlong
.Twoja gospodarka nie wycieknie i zwiększy się do miliardów dolarów.
Są trudniejsze problemy - na przykład, co robisz, jeśli dokonujesz dwudziestu płatności? * - ale to powinno zacząć.
* Obliczasz, co to jest jedna płatność, w zaokrągleniu do
long
; następnie pomnóż przez20.0
i sprawdź, czy jest w zasięgu; jeśli tak, pomnóż płatność przez,20L
aby uzyskać kwotę potrąconą z salda. Zasadniczo wszystkie transakcje muszą być obsługiwane jakolong
, więc naprawdę trzeba podsumować wszystkie poszczególne transakcje; możesz pomnożyć jako skrót, ale musisz upewnić się, że nie dodajesz błędów zaokrąglania i nie przepełniasz, co oznacza, że musisz sprawdzićdouble
przed wykonaniem prawdziwych obliczeńlong
.źródło
Chciałbym powiedzieć, że każda wartość, która może być wyświetlana użytkownikowi, prawie zawsze powinna być liczbą całkowitą. Pieniądze są tylko najbardziej znanym tego przykładem. Zadanie potworowi 225 obrażeń czterokrotnie 225 i stwierdzenie, że pozostało mu jeszcze 1 PW, odejmie od doświadczenia tak samo, jak stwierdzenie, że jesteś niewidzialnym ułamkiem grosza, który czegoś nie daje.
Z technicznego punktu widzenia warto zauważyć, że nie trzeba wracać do pływaków, aby robić zaawansowane rzeczy, takie jak zainteresowanie. Tak długo, jak masz wystarczającą ilość miejsca w wybranym typie liczb całkowitych, mnożenie i dzielenie będą oznaczać mnożenie przez liczbę dziesiętną, na przykład, aby dodać 4%, w zaokrągleniu w dół:
Aby dodać 4%, w zaokrągleniu do standardowych konwencji:
Nie ma tu niedokładności punktu zmiennoprzecinkowego, zaokrąglenie zawsze dzieli się dokładnie na
.5
znaku.Edytuj prawdziwe pytanie:
Widząc, jak debata upadł zacznę myśleć, że przedstawiając to, co jest pytanie o może być bardziej przydatne niż zwykły
int
/float
odpowiedź. U podstaw pytania nie chodzi o typy danych, lecz o przejęcie kontroli nad szczegółami programu.Użycie liczby całkowitej do przedstawienia wartości niecałkowitej zmusza programistę do zajęcia się szczegółami implementacji. „Jakiej precyzji użyć?” i „W jaki sposób zaokrąglić?” są pytania, na które należy udzielić wyraźnej odpowiedzi.
Z drugiej strony liczba zmiennoprzecinkowa nie zmusza programisty do zmartwień, robi już prawie wszystko, czego można by oczekiwać. Ponieważ jednak liczba zmiennoprzecinkowa nie jest nieskończoną precyzją, nastąpi pewne zaokrąglenie, a to zaokrąglenie jest dość nieprzewidywalne.
Co dzieje się przy jednym użyciu zmiennoprzecinkowych i chcesz przejąć kontrolę nad zaokrąglaniem? Okazuje się, że jest prawie niemożliwe. Jedynym sposobem, aby zmiennoprzecinkowe było naprawdę przewidywalne, jest użycie tylko wartości, które mogą być reprezentowane w całych
2^n
literach. Ale ta konstrukcja sprawia, że spławiki są dość trudne w użyciu.Tak więc odpowiedź na proste pytanie brzmi: jeśli chcesz przejąć kontrolę, użyj liczb całkowitych, jeśli nie, użyj liczb zmiennoprzecinkowych.
Ale omawiane pytanie jest tylko inną formą pytania: czy chcesz przejąć kontrolę?
źródło
Nawet jeśli to „tylko gra”, użyłbym Money Pattern od Martina Fowlera, popartego długim.
Dlaczego?
Lokalizacja (L10n): Za pomocą tego wzoru możesz łatwo zlokalizować swoją walutę gry. Pomyśl o starych grach potentata, takich jak „Transport Tycoon”. Pozwalają graczowi łatwo zmienić walutę w grze (np. Z funta brytyjskiego na dolara amerykańskiego), aby spełnić warunki rzeczywistej waluty świata.
I
Oznacza to, że możesz przechowywać 9 000 razy bieżącą podaż pieniądza M2 w USA (~ 10 000 miliardów dolarów). Dając ci wystarczająco dużo miejsca na użycie dowolnej innej światowej waluty, prawdopodobnie nawet tych, którzy mieli / mają hiperinflację (jeśli jesteś ciekawy, zobacz niemiecką inflację po I wojnie światowej , gdzie 1 funt chleba wynosił 3 000 000 000 marek)
Długie jest łatwe do utrzymania, bardzo szybkie i powinno dać ci wystarczająco dużo miejsca na wykonanie wszystkich obliczeń odsetek przy użyciu tylko arytmetyki liczb całkowitych, odpowiedź e-biznesu wyjaśnia, jak to zrobić.
źródło
Ile pracy chcesz w to włożyć? Jak ważna jest dokładność? Czy zależy Ci na śledzeniu błędów ułamkowych, które występują podczas zaokrąglania i niedokładności przedstawiania liczb dziesiętnych w systemie dwójkowym?
Ostatecznie spędzam trochę więcej czasu na kodowaniu i wdrażaniu testów jednostkowych dla „przypadków narożnych” i znanych spraw problematycznych - więc pomyślałbym o zmodyfikowanym BigInteger, który obejmuje dowolnie duże kwoty i zachowuje bity ułamkowe za pomocą BigDecimal lub część BigRational (dwie BigInteger, jedna dla mianownika, a druga dla licznika) - i zawiera kod, aby część ułamkowa była ułamkiem rzeczywistym poprzez (być może tylko okresowe) dodawanie dowolnej części niefrakcyjnej do głównej BigInteger. Następnie wewnętrznie śledziłbym wszystko w centach, tylko po to, aby części ułamkowe nie były uwzględniane w obliczeniach GUI.
Prawdopodobnie sposób na skomplikowanie dla (prostej) gry - ale dobry dla biblioteki opublikowanej jako open source! Musiałbym tylko wymyślić sposoby utrzymania dobrej wydajności w przypadku bitów ułamkowych ...
źródło
Na pewno nie unosi się. Mając tylko 7 cyfr, masz tylko dokładność:
12 345,67 123 456,7x
Widzisz, już mając 10 ^ 5 dolarów, tracisz poczucie grosza. double jest użyteczny do celów gry, a nie w prawdziwym życiu z powodu obaw o dokładność.
Ale długi (a przez to mam na myśli 64-bitowy lub długi długi w C ++) nie wystarczy, jeśli zamierzasz śledzić transakcje i sumować je. Wystarczy utrzymać udziały netto dowolnej firmy, ale wszystkie transakcje na rok mogą się przepełnić. To zależy od tego, jak duży jest Twój finansowy świat w grze.
źródło
Kolejnym rozwiązaniem, które tu dodam, jest zarabianie pieniędzy. Klasa byłaby czymś podobnym (mogłaby nawet po prostu być strukturą).
Pozwoliłoby to przedstawić centy w tym przypadku jako liczbę całkowitą, a całe dolary jako liczbę całkowitą. Ponieważ masz oddzielną flagę za ujemną, możesz użyć liczb całkowitych bez znaku dla swoich wartości, co podwaja możliwą ilość (możesz także napisać klasę liczb, która stosuje ten pomysł do liczb, aby uzyskać NAPRAWDĘ duże liczby). Wszystko, co musisz zrobić, to przeciążenie operatorów matematycznych i możesz użyć tego jak każdego starego typu danych. Zajmuje więcej pamięci, ale tak naprawdę rozszerza granice wartości.
Można to dodatkowo rozszerzyć o takie rzeczy, jak liczba częściowych jednostek na całe jednostki, dzięki czemu możesz mieć waluty, które dzielą się na coś innego niż 100 jednostek podrzędnych, w tym przypadku centów, za dolara. Na przykład możesz mieć 125 floopie, które tworzą jeden flooper.
Edytować:
Rozwinę pomysł, że możesz mieć również tabelę przeglądową, do której klasa pieniędzy może uzyskać dostęp, która zapewniłaby kurs dla jej waluty względem wszystkich innych walut. Możesz to wbudować w funkcje przeciążania operatora. Dlatego jeśli spróbujesz automatycznie dodać 4 USD do 4 funtów brytyjskich, automatycznie otrzymasz 6,6 funta (w momencie pisania).
źródło
Jak wspomniano w jednym z komentarzy do twojego pierwotnego pytania, problem ten został omówiony na StackExchange: https://stackoverflow.com/questions/8148684/what-is-the-best-data-type-to-use-for- Aplikacja Money-in-Java
Zasadniczo typy zmiennoprzecinkowe nigdy nie powinny być używane do reprezentowania walut i podobnie jak większość języków, Java ma bardziej odpowiedni typ. Własna dokumentacja Oracle dotycząca prymitywów sugeruje użycie BigDecimal .
źródło
Użyj klasy, to najlepszy sposób. Możesz ukryć realizację swoich pieniędzy, możesz zmieniać podwójne / długie, co tylko chcesz w dowolnym momencie.
Przykład
W swoim kodzie używaj po prostu gettera, to jest lepszy sposób i myślę, że możesz przechowywać bardziej przydatne metody.
źródło