Точний фінансовий розрахунок у JavaScript. Що таке готчі?


125

В інтересах створення коду між платформами я хотів би розробити просту фінансову програму в JavaScript. Необхідні розрахунки включають складні відсотки та відносно довгі десяткові числа. Мені хотілося б знати, яких помилок слід уникати при використанні JavaScript для виконання цього виду математики - якщо це взагалі можливо!

Відповіді:


107

Ви, мабуть, повинні масштабувати свої десяткові значення на 100 та представляти всі грошові значення цілими центами. Це уникає проблем з логікою та арифметикою з плаваючою комою . У JavaScript немає десяткового типу даних - єдиним числовим типом даних є плаваюча точка. Тому, як правило, рекомендується обробляти гроші в якості 2550копійок замість 25.50доларів.

Врахуйте, що в JavaScript:

var result = 1.0 + 2.0;     // (result === 3.0) returns true

Але:

var result = 0.1 + 0.2;     // (result === 0.3) returns false

Вираз 0.1 + 0.2 === 0.3повертається false, але, на щастя, ціла арифметика з плаваючою комою є точною, тому помилки десяткового представлення можна уникнути шляхом масштабування 1 .

Зауважимо, що набір реальних чисел нескінченний, лише кінцеве їх число (точніше 18,437,736,874,454,810,627) може бути представлено саме форматом JavaScript з плаваючою комою. Тому подання інших чисел буде наближенням фактичного числа 2 .


1 Дуглас Крокфорд: JavaScript: Хороші частини : Додаток A - Страшні частини (стор. 105) .
2 Девід Фланаган: JavaScript: Посібник, що визначається, четверте видання : 3.1.3 Літерали з плаваючою комою (стор. 31) .


3
І як нагадування, завжди круглі розрахунки до центу, і робіть це найменш вигідним чином для споживача, тобто якщо ви обчислюєте податок, округніть. Якщо ви обчислюєте зароблені відсотки, усікайте.
Джош

6
@Cirrostratus: Ви можете перевірити stackoverflow.com/questions/744099 . Якщо ви продовжуєте використовувати метод масштабування, то, як правило, ви хочете масштабувати своє значення за кількістю десяткових цифр, які ви хочете зберегти в точності. Якщо вам потрібно 2 десяткових знаки, масштабуйте на 100, якщо вам потрібно 4, масштабуйте на 10000.
Даніель Вассалло,

2
... Що стосується значення 3000,57, так, якщо ви зберігаєте це значення у змінних JavaScript, і ви маєте намір робити арифметику на ньому, можливо, ви захочете зберегти його в масштабі 300057 (кількість центів). Тому що 3000.57 + 0.11 === 3000.68повертається false.
Даніель Вассалло

7
Підрахунок копійок замість доларів не допоможе. Підраховуючи копійки, ви втрачаєте можливість додати 1 до цілого числа приблизно в 10 ^ 16. Підраховуючи долари, ви втрачаєте можливість додавати .01 до числа в 10 ^ 14. Так чи інакше.
скороченняweapon

1
Я щойно створив модуль npm та Bower, який, сподіваюся, допоможе в цьому!
Pensierinmusica

18

Масштабування кожного значення на 100 - це рішення. Робити це вручну, мабуть, марно, оскільки ви можете знайти бібліотеки, які роблять це за вас. Я рекомендую moneysafe, який пропонує функціональний API, добре підходить для програм ES6:

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

https://github.com/ericelliott/moneysafe

Працює і в Node.js, і в браузері.


2
Отримано. Точка "шкала на 100" вже висвітлена у прийнятій відповіді, проте добре, що ви додали варіант програмного пакету із сучасним синтаксисом JavaScript. in$, $ Імена значень FWIW неоднозначні для тих, хто раніше не використовував пакет. Я знаю, що вибір Еріка називав речі таким чином, але я все ще вважаю, що достатньо помилки, що я, мабуть, перейменував би їх у заяві про імпорт / деструктурований вимагати.
james_womack

4
Масштабування на 100 допомагає лише до тих пір, поки ви не захочете зробити щось на зразок обчислення відсотків (виконайте ділення, по суті).
Pointy

2
Я б хотів, щоб я міг повторити коментар кілька разів. Масштабування на 100 просто недостатньо. Єдиним числовим типом даних у JavaScript все ще є тип даних з плаваючою комою, і ви все ще будете мати значні помилки округлення.
Крейг

1
І ще один на один з Рідха: Money$afe has not yet been tested in production at scale.. Просто зазначивши це, щоб кожен міг потім подумати, чи підходить це для їхнього використання
Nobita

7

Не існує такого поняття, як "точний" фінансовий розрахунок через лише дві цифри з десятковим дробом, але це більш загальна проблема.

У JavaScript ви можете масштабувати кожне значення на 100 та використовувати Math.round()щоразу, коли може виникнути частка.

Ви можете використовувати об'єкт для зберігання чисел і включення округлення в його valueOf()метод прототипів . Подобається це:

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

Таким чином, щоразу, коли ви використовуєте Money-об'єкт, він буде представлений як округлений до двох десяткових знаків. Ще необмежене значення все ще доступне через m.amount.

Money.prototype.valueOf()Якщо ви хочете, ви можете створити власний алгоритм округлення .


Мені подобається цей об'єктно-орієнтований підхід, те, що об’єкт Гроші містить обидві значення, дуже корисний. Це саме той тип функціоналу, який я люблю створювати у своїх спеціальних класах Objective-C.
james_womack

4
Недостатньо точно для округлення.
Генрі Ценг

3
Не повинен sys.puts (m + n); //80.76 фактично читає sys.puts (m + n); //80,77? Я вважаю, ви забули округлити .5.
Дейв Л

2
Такий підхід має ряд тонких питань, які можуть вирішити проблему. Наприклад, ви не застосували безпечні методи складання, віднімання, множення тощо, тому ви, ймовірно, зіткнетеся з помилками округлення при поєднанні грошових сум
1800 ІНФОРМАЦІЯ

2
Проблема тут полягає в тому, що, наприклад, Money(0.1)означає, що лексер JavaScript зчитує рядок "0,1" з джерела, а потім перетворює його у двійкову плаваючу крапку і тоді ви вже зробили ненавмисне округлення. Проблема полягає у представленні (двійкові проти десяткових), а не в точності .
mgd


2

Ваша проблема випливає з неточності підрахунків з плаваючою комою. Якщо ви просто використовуєте округлення для вирішення цього питання, у вас буде більша помилка при множенні та діленні.

Рішення нижче, пояснення наступне:

Вам потрібно буде подумати про математику, що стоїть за цим, щоб зрозуміти це. Реальні числа, як 1/3, не можуть бути представлені в математиці з десятковими значеннями, оскільки вони нескінченні (наприклад, .333333333333333 ...). Деякі цифри у десятковій формі не можуть бути представлені у двійковій формі правильно. Наприклад, 0,1 не може бути представлено у двійковій формі правильно з обмеженою кількістю цифр.

Більш детальний опис дивіться тут: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Погляньте на реалізацію рішення: http://floating-point-gui.de/languages/javascript/


1

Через бінарний характер їх кодування деякі десяткові числа не можуть бути представлені з ідеальною точністю. Наприклад

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

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

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

Якщо вам потрібно використовувати чистий javascript, тоді вам потрібно думати про рішення для кожного розрахунку. Для наведеного вище коду ми можемо перетворити десяткові числа на цілі числа.

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

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

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

Уникнення проблем з десятковою математикою в JavaScript

Є спеціальна бібліотека для фінансових розрахунків з великою документацією. Finance.js


Мені подобається, що Finance.js також має приклади програм
james_womack

0

На жаль, усі відповіді поки що ігнорують той факт, що не всі валюти мають 100 підрозділів (наприклад, цент є підрозділом долара США (USD)). Такі валюти, як іракський динар (IQD), мають 1000 підрозділів: іракський динар має 1000 карт. Японська ієна (JPY) не має підрозділів. Тому "помножити на 100, щоб зробити цілу арифметику" - це не завжди правильна відповідь.

Додатково для грошових розрахунків потрібно також стежити за валютою. Ви не можете додати долар США (USD) до індійської рупії (INR) (без попереднього перетворення одного в інший).

Існують також обмеження щодо максимальної кількості, яка може бути представлена ​​цілим типом даних JavaScript.

У грошових розрахунках ви також повинні мати на увазі, що гроші мають граничну точність (як правило, 0-3 десяткових знаків), і округлення потрібно проводити особливими способами (наприклад, "нормальне" округлення проти округлення банкіра). Тип округлення, який слід здійснити, також може відрізнятися залежно від юрисдикції / валюти.

Як поводитися з грошима в javascript , дуже добре обговорюються відповідні моменти.

У ході пошуку я знайшов бібліотеку dinero.js, яка вирішує багато питань, що стосуються грошових розрахунків . Ще не використовував його у виробничій системі, тому не можу дати про це усвідомленої думки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.