JavaScript 中的精确财务计算。什么是陷阱?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2876536/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:18:08  来源:igfitidea点击:

Precise Financial Calculation in JavaScript. What Are the Gotchas?

javascriptdecimalfinance

提问by james_womack

In the interest of creating cross-platform code, I'd like to develop a simple financial application in JavaScript. The calculations required involve compound interest and relatively long decimal numbers. I'd like to know what mistakes to avoid when using JavaScript to do this type of math—if it is possible at all!

为了创建跨平台代码,我想用 JavaScript 开发一个简单的金融应用程序。所需的计算涉及复利和相对较长的十进制数。我想知道在使用 JavaScript 进行此类数学运算时应避免哪些错误——如果可能的话!

采纳答案by Daniel Vassallo

You should probably scale your decimal values by 100, and represent all the monetary values in whole cents. This is to avoid problems with floating-point logic and arithmetic. There is no decimal data type in JavaScript - the only numeric data type is floating-point. Therefore it is generally recommended to handle money as 2550cents instead of 25.50dollars.

您可能应该将十进制值缩放 100,并以整分表示所有货币值。这是为了避免浮点逻辑和算术出现问题。JavaScript 中没有十进制数据类型——唯一的数字数据类型是浮点数。因此,通常建议以2550美分而不是25.50美元来处理货币。

Consider that in JavaScript:

在 JavaScript 中考虑一下:

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

But:

但:

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

The expression 0.1 + 0.2 === 0.3returns false, but fortunately integer arithmetic in floating-point is exact, so decimal representation errors can be avoided by scaling1.

表达式0.1 + 0.2 === 0.3返回false,但幸运的是浮点整数运算是精确的,因此可以通过缩放1来避免十进制表示错误。

Note that while the set of real numbers is infinite, only a finite number of them (18,437,736,874,454,810,627 to be exact) can be represented exactly by the JavaScript floating-point format. Therefore the representation of the other numbers will be an approximation of the actual number2.

请注意,虽然实数集是无限的,但只有有限数量(准确地说是 18,437,736,874,454,810,627)可以由 JavaScript 浮点格式精确表示。因此,其他数字的表示将是实际数字2的近似值。



1Douglas Crockford: JavaScript: The Good Parts: Appendix A - Awful Parts (page 105).
2David Flanagan: JavaScript: The Definitive Guide, Fourth Edition: 3.1.3 Floating-Point Literals (page 31).

1Douglas Crockford:JavaScript:好的部分:附录 A - 糟糕的部分(第 105 页)
2David Flanagan:JavaScript:权威指南,第四版:3.1.3 浮点文字(第 31 页)

回答by Fran?ois Zaninotto

Scaling every value by 100 is the solution. Doing it by hand is probably useless, since you can find libraries that do that for you. I recommend moneysafe, which offers a functional API well suited for ES6 applications:

将每个值缩放 100 是解决方案。手工操作可能没用,因为您可以找到为您执行此操作的库。我推荐 moneysafe,它提供了一个非常适合 ES6 应用程序的函数式 API:

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

https://github.com/ericelliott/moneysafe

https://github.com/ericelliott/moneysafe

Works both in Node.js and the browser.

适用于 Node.js 和浏览器。

回答by selfawaresoup

There's no such thing as "precise" financial calculation because of just two decimal fraction digits but that's a more general problem.

由于只有两个小数位数,因此没有“精确”财务计算之类的东西,但这是一个更普遍的问题。

In JavaScript, you can scale every value by 100 and use Math.round()everytime a fraction can occur.

在 JavaScript 中,您可以将每个值缩放 100,并在Math.round()每次出现小数时使用。

You could use an object to store the numbers and include the rounding in its prototypes valueOf()method. Like this:

您可以使用一个对象来存储数字并在其原型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

That way, everytime you use a Money-object, it will be represented as rounded to two decimals. The unrounded value is still accessible via m.amount.

这样,每次使用 Money 对象时,它都会被表示为四舍五入到两位小数。未舍入的值仍可通过 访问m.amount

You can build in your own rounding algorithm into Money.prototype.valueOf(), if you like.

Money.prototype.valueOf()如果愿意,您可以将自己的舍入算法构建到 中。

回答by Henry Tseng

Your problem stems from inaccuracy in floating point calculations. If you're just using rounding to solve this you'll have greater error when you're multiplying and dividing.

您的问题源于浮点计算的不准确。如果您只是使用舍入来解决这个问题,那么在乘法和除法时会出现更大的错误。

The solution is below, an explanation follows:

解决方法如下,解释如下:

You'll need to think about mathematics behind this to understand it. Real numbers like 1/3 cannot be represented in math with decimal values since they're endless (e.g. - .333333333333333 ...). Some numbers in decimal cannot be represented in binary correctly. For example, 0.1 cannot be represented in binary correctly with a limited number of digits.

您需要考虑这背后的数学才能理解它。像 1/3 这样的实数不能用十进制值在数学中表示,因为它们是无穷无尽的(例如 - .3333333333333333 ...)。一些十进制数不能正确地用二进制表示。例如,0.1 不能用有限的数字正确地用二进制表示。

For more detailed description look here: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

更详细的描述请看这里:http: //docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Take a look at the solution implementation:http://floating-point-gui.de/languages/javascript/

看一下解决方案的实现:http : //floating-point-gui.de/languages/javascript/

回答by TLEJMI

use decimaljs ... It a very good library that solves a harsh part of the problem ...

使用decimaljs ...它是一个非常好的库,解决了这个问题的严峻部分...

just use it in all your operation.

只需在您的所有操作中使用它。

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

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

回答by Ali Rehman

Due to the binary nature of their encoding, some decimal numbers cannot be represented with perfect accuracy. For example

由于其编码的二进制性质,某些十进制数无法完全准确地表示。例如

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

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

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

If you need to use pure javascript then you have need to think about solution for every calculation. For above code we can convert decimals to whole integers.

如果您需要使用纯 javascript,那么您需要为每个计算考虑解决方案。对于上面的代码,我们可以将小数转换为整数。

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

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

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

Avoiding Problems with Decimal Math in JavaScript

避免 JavaScript 中的十进制数学问题

There is a dedicated library for financial calculations with great documentation. Finance.js

有一个专门的财务计算库,有很好的文档。金融.js

回答by markvgti

Unfortunately all of the answers so far ignore the fact that not all currencies have 100 sub-units (e.g., the cent is the sub-unit of the US dollar (USD)). Currencies like the Iraqi Dinar (IQD) have 1000 sub-units: an Iraqi Dinar has 1000 fils. The Japanese Yen (JPY) has no sub-units. So "multiply by 100 to do integer arithmetic" isn't always the correct answer.

不幸的是,到目前为止所有的答案都忽略了一个事实,即并非所有货币都有 100 个子单位(例如,美分是美元 (USD) 的子单位)。伊拉克第纳尔 (IQD) 等货币有 1000 个子单位:伊拉克第纳尔有 1000 菲尔斯。日元 (JPY) 没有子单位。所以“乘以 100 来做整数运算”并不总是正确的答案。

Additionally for monetary calculations you also need to keep track of the currency. You can't add a US Dollar (USD) to an Indian Rupee (INR) (without first converting one to the other).

此外,对于货币计算,您还需要跟踪货币。您不能将美元 (USD) 添加到印度卢比 (INR)(无需先将一个货币转换为另一个)。

There are also limitations on the maximum amount that can be represented by JavaScript's integer data type.

JavaScript 的整数数据类型可以表示的最大数量也有限制。

In monetary calculations you also have to keep in mind that money has finite precision (typically 0-3 decimal points) & rounding needs to be done in particular ways (e.g., "normal" rounding vs. banker's rounding). The type of rounding to be performed might also vary by jurisdiction/currency.

在货币计算中,您还必须记住,货币的精度是有限的(通常为 0-3 个小数点)并且需要以特定方式进行舍入(例如,“正常”舍入与银行家舍入)。要执行的舍入类型也可能因司法管辖区/货币而异。

How to handle money in javascripthas a very good discussion of the relevant points.

How to handle money in javascript对相关点有很好的讨论。

In my searches I found the dinero.jslibrary that addresses many of the issues wrt monetary calculations. Haven't used it yet in a production system so can't give an informed opinion on it.

在我的搜索中,我找到了dinero.js库,它解决了货币计算的许多问题。还没有在生产系统中使用它,所以不能对它给出明智的意见。