用JavaScript进行精确的财务计算。陷阱是什么?

时间:2021-11-04 03:56:43

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执行这种类型的数学时应该避免哪些错误——如果可能的话!

4 个解决方案

#1


88  

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 2550 cents instead of 25.50 dollars.

你应该把十进制的值换算成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.3 returns 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,但幸运的是浮点数算法是精确的,因此通过scaling1可以避免十进制表示错误。

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.

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


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 Floating-Point Literals (page 31).

Douglas Crockford: JavaScript:好的部分:附录A -可怕的部分(第105页)。2 David Flanagan: JavaScript:最终指南,第四版:3.1.3浮点文字(第31页)。

#2


7  

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.

在节点工作。js和浏览器。

#3


5  

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:

您可以使用一个对象来存储数字,并将舍入包含在它的prototype 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.

这样,每次你使用货币对象时,它都会被表示成四舍五入到两个小数。未圆的值仍然可以通过m.d iv访问。

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

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

#4


2  

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这样的实数不能用小数来表示,因为它们是无穷无尽的(例如- . 3333333333333333333333333333333333333…)。有些十进制的数字不能正确地用二进制表示。例如,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://float-pointgui.de/languages/javascript/。

#1


88  

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 2550 cents instead of 25.50 dollars.

你应该把十进制的值换算成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.3 returns 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,但幸运的是浮点数算法是精确的,因此通过scaling1可以避免十进制表示错误。

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.

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


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 Floating-Point Literals (page 31).

Douglas Crockford: JavaScript:好的部分:附录A -可怕的部分(第105页)。2 David Flanagan: JavaScript:最终指南,第四版:3.1.3浮点文字(第31页)。

#2


7  

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.

在节点工作。js和浏览器。

#3


5  

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:

您可以使用一个对象来存储数字,并将舍入包含在它的prototype 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.

这样,每次你使用货币对象时,它都会被表示成四舍五入到两个小数。未圆的值仍然可以通过m.d iv访问。

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

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

#4


2  

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这样的实数不能用小数来表示,因为它们是无穷无尽的(例如- . 3333333333333333333333333333333333333…)。有些十进制的数字不能正确地用二进制表示。例如,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://float-pointgui.de/languages/javascript/。