Precyzyjne obliczenia finansowe w JavaScript. Co to sÄ… Gotchas?

W interesie tworzenia kodu wieloplatformowego, Chciałbym stworzyć prostą aplikację finansową w JavaScript. Wymagane obliczenia obejmują odsetki złożone i stosunkowo długie liczby dziesiętne. Chciałbym wiedzieć, jakich błędów unikać przy użyciu JavaScript do tego typu matematyki-jeśli jest to w ogóle możliwe!

Author: james_womack, 2010-05-20

7 answers

Prawdopodobnie powinieneś przeskalować swoje wartości dziesiętne przez 100 i reprezentować wszystkie wartości pieniężne w całych centach. Ma to na celu uniknięcie problemów z logiką zmiennoprzecinkową i arytmetyką . W JavaScript nie ma dziesiętnego typu danych - jedynym liczbowym typem danych jest zmiennoprzecinkowy. W związku z tym zaleca się, aby traktować pieniądze jako 2550 centów zamiast 25.50 dolarów.

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 postaci zmiennoprzecinkowej jest dokładna, więc błędów reprezentacji dziesiętnej można uniknąć skalując1.

Zauważ, że chociaż zbiór liczb rzeczywistych jest nieskończony, tylko skończona liczba z nich (18,437,736,874,454,810,627) może być dokładnie reprezentowana przez format zmiennoprzecinkowy JavaScript. Dlatego reprezentacja pozostałych liczb będzie przybliżeniem liczby rzeczywistej2.


1 Douglas Crockford: JavaScript: the Good Parts : Appendix A - Awful Parts (page 105) .
2 David Flanagan: JavaScript: the Definitive Guide, Fourth Edition: 3.1.3 literały zmiennoprzecinkowe (Strona 31) .

 115
Author: Daniel Vassallo,
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-05-23 12:09:16

Skalowanie każdej wartości o 100 jest rozwiązaniem. 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:

const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8

Https://github.com/ericelliott/moneysafe

Działa zarówno w Node.js i przeglądarka.

 21
Author: François Zaninotto,
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-08-07 16:10:39

Nie ma czegoś takiego jak "precyzyjne" obliczenia finansowe z powodu tylko dwóch dziesiętnych cyfr ułamkowych, ale to jest bardziej ogólny problem.

W JavaScript można skalować każdą wartość przez 100 i używać Math.round() za każdym razem, gdy może wystąpić ułamek.

Można użyć obiektu do przechowywania liczb i dołączania zaokrąglenia w prototypach metody valueOf(). Tak:

sys = require('sys');

var Money = function(amount) {
        this.amount = amount;
    }
Money.prototype.valueOf = function() {
    return Math.round(this.amount*100)/100;
}

var m = new Money(50.42355446);
var n = new Money(30.342141);

sys.puts(m.amount + n.amount); //80.76569546
sys.puts(m+n); //80.76

W ten sposób, za każdym razem, gdy użyjesz przedmiotu z pieniędzmi, będzie on zaokrąglony do dwóch miejsc po przecinku. The unrounded wartość jest nadal dostępna poprzez m.amount.

Jeśli chcesz, możesz zbudować własny algorytm zaokrąglania do Money.prototype.valueOf().
 7
Author: selfawaresoup,
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
2010-05-20 19:07:52

Użyj decymaljs ... To bardzo dobra biblioteka, która rozwiązuje trudną część problemu ...

Po prostu używaj go w całej swojej operacji.

Https://github.com/MikeMcl/decimal.js/

 3
Author: tlejmi,
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
2019-08-14 14:42:23

Twój problem wynika z niedokładności w obliczeniach zmiennoprzecinkowych. Jeśli używasz zaokrąglenia, aby rozwiązać ten problem, będziesz miał większy błąd podczas mnożenia i dzielenia.

Rozwiązanie jest poniżej, Wyjaśnienie następuje:

Musisz pomyśleć o matematyce stojącej za tym, aby to zrozumieć. Liczby rzeczywiste takie jak 1/3 nie mogą być reprezentowane w matematyce z wartościami dziesiętnymi, ponieważ są nieskończone (np. - .333333333333333 ...). Niektóre liczby dziesiętne nie mogą być reprezentowane w / align = "left" / Na przykład 0.1 nie może być poprawnie reprezentowane w systemie binarnym z ograniczoną liczbą cyfr.

Szczegółowy opis znajdziesz tutaj: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Zapoznaj się z wdrożeniem rozwiązania: http://floating-point-gui.de/languages/javascript/

 2
Author: Henry Tseng,
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-10 17:10:26

Ze względu na binarny charakter ich kodowania, niektóre liczby dziesiętne nie mogą być reprezentowane z doskonałą dokładnością. Na przykład

var money = 600.90;
var price = 200.30;
var total = price * 3;

// Outputs: false
console.log(money >= total);

// Outputs: 600.9000000000001
console.log(total);

Jeśli potrzebujesz użyć czystego javascript, musisz pomyśleć o rozwiązaniu dla każdego obliczenia. Dla powyższego kodu możemy zamienić dziesiętne na całe liczby całkowite.

var money = 60090;
var price = 20030;
var total = price * 3;

// Outputs: true
console.log(money >= total);

// Outputs: 60090
console.log(total);

Unikanie problemów z matematyki dziesiętnej w JavaScript

Istnieje specjalna biblioteka do obliczeń finansowych ze świetną dokumentacją. Finanse.js

 1
Author: Ali Rehman,
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
2019-12-24 16:21:18

Niestety wszystkie odpowiedzi do tej pory ignorują fakt, że nie Wszystkie waluty mają 100 jednostek (np. cent jest jednostką dolara amerykańskiego (USD)). Waluty takie jak Dinar iracki (IQD) mają 1000 sub-jednostek: Dinar iracki ma 1000 fils. Jen japoński (JPY) nie posiada pododdziałów. Więc "mnożenie przez 100, aby wykonać arytmetykę całkowitą" nie zawsze jest poprawną odpowiedzią.

DODATKOWO do obliczeń monetarnych należy również śledzić walutę. Nie możesz dodać dolara amerykańskiego (USD) do rupii indyjskiej (INR) (bez uprzedniej konwersji jednej na drugą).

Istnieją również ograniczenia dotyczące maksymalnej ilości danych, które mogą być reprezentowane przez typ danych Integer JavaScript.

W obliczeniach monetarnych należy również pamiętać, że pieniądz ma skończoną precyzję (Zwykle 0-3 punkty dziesiętne) i zaokrąglanie musi być wykonywane w określony sposób (np." normalne " zaokrąglenie vs.zaokrąglenie bankiera). Rodzaj zaokrąglenia, które należy wykonać, może również różnić się o jurysdykcja / waluta.

Jak radzić sobie z pieniędzmi w javascript ma bardzo dobre omówienie odpowiednich punktów.

W moich poszukiwaniach znalazłem dinero.biblioteka js, która rozwiązuje wiele problemów związanych z obliczeniami monetarnymi wrt . Nie używałem go jeszcze w systemie produkcyjnym, więc nie mogę wydać świadomej opinii na jego temat.

 1
Author: markvgti,
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
2020-03-17 09:49:02