Czy powinienem używać NSDecimalNumber do czynienia z pieniędzmi?

Gdy zacząłem kodować moją pierwszą aplikację, użyłem NSNumber dla wartości pieniędzy, nie zastanawiając się dwa razy. Potem pomyślałem, że może typy c wystarczą, by poradzić sobie z moimi wartościami. Jednak doradzono mi na forum iPhone SDK, aby używać NSDecimalNumber, ze względu na doskonałe możliwości zaokrąglania.

Nie będąc matematykiem z temperamentu, myślałem, że paradygmat mantissa/exponent może być przesadą; mimo to, googlując się wokół, zdałem sobie sprawę, że większość rozmów o pieniądzu / walucie w kakao odnosi się do NSDecimalNumber.

Zauważ, że aplikacja, nad którą pracuję, będzie umiędzynarodowiona, więc opcja liczenia kwoty w centach nie jest naprawdę opłacalna, ponieważ struktura monetarna w dużym stopniu zależy od używanej lokalizacji.

Jestem w 90% pewien, że muszę iść z NSDecimalNumber, ale ponieważ nie znalazłem jednoznacznej odpowiedzi w Internecie (coś w stylu: "jeśli masz do czynienia z pieniędzmi, użyj NSDecimalNumber!") Pomyślałem, że zapytam tutaj. Może odpowiedź jest dla większości oczywista, ale chcę mieć pewność przed rozpoczęciem masowego ponownego faktoringu mojej aplikacji.

Przekonaj mnie:)

Author: Elijah, 2009-01-07

6 answers

[[0]}Marcus Zarra ma dość jasne stanowisko w tej sprawie: "Jeśli w ogóle masz do czynienia z walutą, powinieneś używać NSDecimalNumber." jego artykuł zainspirował mnie do przyjrzenia się NSDecimalNumber i byłem pod wielkim wrażeniem. Błędy zmiennoprzecinkowe IEEE podczas pracy z matematyką base-10 od jakiegoś czasu mnie irytują (1 * (0.5 - 0.4 - 0.1) = -0.00000000000000002776) i NSDecimalNumber usuwa z nich.

NSDecimalNumber nie dodaje kolejnych kilku cyfr binarna precyzja zmiennoprzecinkowa, w rzeczywistości robi matematykę bazową-10. To usuwa błędy, takie jak ten pokazany w powyższym przykładzie.

Teraz piszę symboliczną aplikację matematyczną, więc moje pragnienie dokładności cyfr dziesiętnych 30+ i żadnych dziwnych błędów zmiennoprzecinkowych może być wyjątkiem, ale myślę, że warto na to spojrzeć. Operacje są trochę bardziej niewygodne niż proste obliczenia w stylu var = 1 + 2, ale nadal można je kontrolować. Jeśli obawiasz się przydzielania różnego rodzaju instancji podczas operacji matematycznych, NSDecimal jest odpowiednikiem struktury C nsdecimalnumber i istnieją funkcje C do wykonywania dokładnie tych samych operacji matematycznych z nim. Z mojego doświadczenia wynika, że są one bardzo szybkie dla wszystkich, ale najbardziej wymagających aplikacji (3,344,593 dodatków/s, 254,017 dywizji/s na MacBook Air, 281,555 dodatków/s, 12,027 dywizji/s na iPhone).

Jako dodatkowy bonus, nsdecimalnumber ' s descriptionWithLocale: metoda dostarcza ciąg znaków ze zlokalizowaną wersją liczby, wraz z prawidłowym separatorem dziesiętnym. To samo dzieje się odwrotnie dla metody initWithString: locale:.

 61
Author: Brad Larson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-28 18:46:38

Tak. Musisz użyć

NSDecimalNumber oraz

Nie podwójne lub float Gdy masz do czynienia z walutą na iOS.

Dlaczego??

Ponieważ nie chcemy mieć takich rzeczy jak $9.9999999998 zamiast $10

Jak to się dzieje??

Pływaki i dublerzy to przybliżenia. Zawsze pojawia się błąd zaokrąglenia. Format używany przez komputery do przechowywania dziesiętnych powoduje ten błąd routingu. Jeśli Potrzebujesz więcej szczegółów przeczytaj

Http://floating-point-gui.de/

Według apple docs,

NSDecimalNumber jest niezmienną podklasą NSNumber, zapewnia obiektowe opakowanie do arytmetyki bazy-10. Instancja może reprezentować dowolną liczbę, która może być wyrażona jako wykładnik mantissa x 10^, gdzie mantissa jest liczbą dziesiętną o długości do 38 cyfr, a wykładnik jest liczbą całkowitą od -128 do 127.wrapper do wykonywania arytmetyki bazy-10.

Więc NSDecimalNumber jest zalecany do umowy z walutą.

 8
Author: MadNik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-14 11:02:09

(z mojego komentarza do drugiej odpowiedzi.)

Tak, powinieneś. Integralna liczba groszy działa tylko tak długo, jak nie trzeba reprezentować, powiedzmy, pół centa. Jeśli tak się stanie, możesz zmienić go na pół centa, ale co, jeśli będziesz musiał reprezentować ćwierć centa lub ósmą część centa?

Jedynym właściwym rozwiązaniem jest NSDecimalNumber (lub coś podobnego), co odkłada problem na 10^-128 ¤ (tj.
0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000001¢).

(innym sposobem byłaby arytmetyka arbitralnie precyzyjna, ale wymaga to oddzielnej biblioteki, takiej jak GNU MP Bignum library. GMP jest pod LGPL. Nigdy nie korzystałem z tej biblioteki i nie wiem dokładnie, jak ona działa, więc nie mogłem powiedzieć, jak dobrze by to działało dla Ciebie.)

[Edit: widocznie przynajmniej jedna osoba-Brad Larson-myślę, że mówię o binarnym zmiennoprzecinkowym gdzieś w tej odpowiedzi. Nie jestem.]

 5
Author: Peter Hosey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-22 07:37:18

Lepszym pytaniem jest, kiedy powinieneś nie używać NSDecimalNumber do radzenia sobie z pieniędzmi. Krótka odpowiedź na to pytanie brzmi: kiedy nie możesz tolerować narzutu wydajności NSDecimalNumber i nie dbasz o małe błędy zaokrąglania, ponieważ nigdy nie masz do czynienia z więcej niż kilkoma cyframi precyzji. Jeszcze krótsza odpowiedź brzmi: powinieneś Zawsze używać NSDecimalNumber, gdy masz do czynienia z pieniędzmi.

 4
Author: Tony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-09 19:50:11

Uznałem, że wygodne jest użycie liczby całkowitej do reprezentowania liczby centów, a następnie podzielenie przez 100 do prezentacji. Unika całego problemu.

 2
Author: wisequark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-07 18:37:02

VISA, MasterCard i inne używają wartości całkowitych podczas przekazywania kwot. Do nadawcy i odbiorcy należy poprawne parsowanie wartości według wykładnika waluty (podzielić lub pomnożyć przez 10^num, gdzie num - jest wykładnikiem waluty). Należy pamiętać, że różne waluty mają różne wykładniki. Zazwyczaj jest to 2 (stąd dzielimy i mnożymy przez 100), ale niektóre waluty mają wykładnik = 0 (VND,etc), lub = 3.

 2
Author: Eugene Martinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-01 05:35:38