Prawdopodobnie powinieneś przeskalować swoje wartości dziesiętne o 100 i przedstawić wszystkie wartości pieniężne w pełnych centach. Ma to na celu uniknięcie problemów z logiką zmiennoprzecinkową i arytmetyką . W JavaScript nie ma danych dziesiętnych - jedynym typem liczbowym jest zmiennoprzecinkowy. Dlatego ogólnie zaleca się traktowanie pieniędzy w 2550
centach zamiast25.50
dolarach.
Weź pod uwagę, że w JavaScript:
var result = 1.0 + 2.0; // (result === 3.0) returns true
Ale:
var result = 0.1 + 0.2; // (result === 0.3) returns false
Wyrażenie 0.1 + 0.2 === 0.3
zwraca false
, ale na szczęście arytmetyka liczb całkowitych w liczbach zmiennoprzecinkowych jest dokładna, więc można uniknąć błędów w reprezentacji dziesiętnej przez skalowanie 1 .
Zauważ, że chociaż zbiór liczb rzeczywistych jest nieskończony, tylko skończona ich liczba (dokładnie 18 437 736 874 454 810 627) może być dokładnie reprezentowana przez format zmiennoprzecinkowy JavaScript. Dlatego przedstawienie pozostałych liczb będzie przybliżeniem rzeczywistej liczby 2 .
1 Douglas Crockford: JavaScript: Dobre części : Dodatek A - Okropne części (strona 105) .
2 David Flanagan: JavaScript: The Definitive Guide, wydanie czwarte : 3.1.3 Literały zmiennoprzecinkowe (strona 31) .
3000.57 + 0.11 === 3000.68
wracafalse
.Rozwiązaniem jest skalowanie każdej wartości o 100. Robienie tego ręcznie jest prawdopodobnie bezużyteczne, ponieważ możesz znaleźć biblioteki, które robią to za Ciebie. Polecam moneysafe, który oferuje funkcjonalne API dobrze dopasowane do aplikacji ES6:
https://github.com/ericelliott/moneysafe
Działa zarówno w Node.js, jak iw przeglądarce.
źródło
in$, $
nazwy wartości są niejednoznaczne dla kogoś, kto nie korzystał wcześniej z pakietu. Wiem, że to Eric zdecydował się nazwać rzeczy w ten sposób, ale nadal uważam, że to wystarczający błąd, że prawdopodobnie zmieniłbym ich nazwy w instrukcji import / destructured require.Money$afe has not yet been tested in production at scale.
. Po prostu wskazuję na to, aby każdy mógł rozważyć, czy jest to odpowiednie dla ich przypadku użyciaNie ma czegoś takiego jak „precyzyjne” obliczenia finansowe z powodu zaledwie dwóch cyfr po przecinku, ale to jest bardziej ogólny problem.
W JavaScript możesz skalować każdą wartość o 100 i używać jej
Math.round()
za każdym razem, gdy może wystąpić ułamek.Możesz użyć obiektu do przechowywania liczb i uwzględnienia zaokrąglenia w jego prototypowej
valueOf()
metodzie. Lubię to:W ten sposób za każdym razem, gdy użyjesz obiektu Money, będzie on reprezentowany jako zaokrąglony do dwóch miejsc po przecinku. Wartość niezaokrąglona jest nadal dostępna za pośrednictwem
m.amount
.Jeśli chcesz, możesz wbudować własny algorytm zaokrąglania w
Money.prototype.valueOf()
.źródło
Money(0.1)
Oznacza, że lekser JavaScript odczytuje łańcuch „0.1” ze źródła, a następnie konwertuje go na binarną liczbę zmiennoprzecinkową, a następnie wykonałeś już niezamierzone zaokrąglenie. Problem dotyczy reprezentacji (binarna vs dziesiętna), a nie precyzji .użyj liczb dziesiętnych ... To bardzo dobra biblioteka, która rozwiązuje trudną część problemu ...
po prostu używaj go we wszystkich swoich operacjach.
https://github.com/MikeMcl/decimal.js/
źródło
Twój problem wynika z niedokładności w obliczeniach zmiennoprzecinkowych. Jeśli używasz tylko zaokrąglania, aby rozwiązać ten problem, będziesz mieć większy błąd podczas mnożenia i dzielenia.
Rozwiązanie jest poniżej, a wyjaśnienie:
Aby to zrozumieć, musisz pomyśleć o matematyce stojącej za tym. Liczb rzeczywistych, takich jak 1/3, nie można przedstawić w matematyce za pomocą wartości dziesiętnych, ponieważ nie mają one końca (np. - .333333333333333 ...). Niektórych liczb dziesiętnych nie można poprawnie przedstawić binarnie. Na przykład 0,1 nie może być poprawnie reprezentowane binarnie za pomocą ograniczonej liczby cyfr.
Bardziej szczegółowy opis znajdziesz tutaj: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Przyjrzyj się wdrożeniu rozwiązania: http://floating-point-gui.de/languages/javascript/
źródło
Ze względu na binarny charakter ich kodowania, niektórych liczb dziesiętnych nie można przedstawić z doskonałą dokładnością. Na przykład
Jeśli potrzebujesz używać czystego javascript, musisz pomyśleć o rozwiązaniu dla każdego obliczenia. Dla powyższego kodu możemy zamienić ułamki dziesiętne na całkowite liczby całkowite.
Unikanie problemów z matematyką dziesiętną w JavaScript
Istnieje dedykowana biblioteka do obliczeń finansowych z doskonałą dokumentacją. Finance.js
źródło
Niestety, wszystkie dotychczasowe odpowiedzi pomijają fakt, że nie wszystkie waluty mają 100 jednostek podrzędnych (np. Cent jest podjednostką dolara amerykańskiego (USD)). Waluty takie jak dinar iracki (IQD) mają 1000 jednostek podrzędnych: dinar iracki ma 1000 fils. Japoński jen (JPY) nie ma podjednostek. Zatem „pomnóż przez 100, aby wykonać arytmetykę liczb całkowitych” nie zawsze jest poprawną odpowiedzią.
Dodatkowo w przypadku obliczeń pieniężnych musisz również śledzić walutę. Nie możesz dodać dolara amerykańskiego (USD) do rupii indyjskiej (INR) (bez wcześniejszej zamiany jednego na drugiego).
Istnieją również ograniczenia dotyczące maksymalnej ilości, którą można przedstawić za pomocą typu danych typu integer JavaScript.
W obliczeniach pieniężnych należy również pamiętać, że pieniądze mają skończoną precyzję (zazwyczaj 0-3 miejsca po przecinku) i należy je zaokrąglać w określony sposób (np. „Normalne” zaokrąglanie w porównaniu do zaokrąglania bankierów). Rodzaj zaokrąglania, które ma być wykonane, może się również różnić w zależności od jurysdykcji / waluty.
Jak radzić sobie z pieniędzmi w javascript ma bardzo dobre omówienie odpowiednich punktów.
Podczas moich poszukiwań znalazłem bibliotekę dinero.js, która rozwiązuje wiele problemów związanych z obliczeniami pieniężnymi . Nie używałem go jeszcze w systemie produkcyjnym, więc nie mogę dać na jego temat opinii.
źródło