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


125

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

Answers:


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来避免小数表示错误。

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


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


3
提醒一下,请始终将计算结果四舍五入,并以对消费者最不利的方式进行处理,即IE。如果要计算所赚取的利息,请截断。
乔什

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的整数的能力。当计算美元时,您将无法在数字10 ^ 14处加上0.01。两者都是一样的。
slashingweapon

1
我刚刚创建了一个npm和Bower模块,希望可以帮助完成此任务!
Pensierinmusica 2014年

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语法的软件包选项。FWIW in$, $ 值名称与以前没有使用过该程序包的人模棱两可。我知道这是Eric以这种方式命名的选择,但我仍然觉得很错误,我可能会在import / destructed require语句中重命名它们。
james_womack

4
缩放100只会有所帮助,直到您开始想要执行诸如计算百分比(本质上是执行除法)之类的操作为止。
尖尖的

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()如果愿意,可以将自己的舍入算法内置到中。


我喜欢这种面向对象的方法,Money对象拥有两个值这一事实非常有用。这是我想在自定义的Objective-C类中创建的确切功能类型。
james_womack 2010年

4
它不够精确,无法四舍五入。
曾亨利

3
sys.puts(m + n); //80.76实际上读取sys.puts(m + n); //80.77?我相信您忘了对.5进行四舍五入。
Dave L

2
这种方法存在许多可能会出现的细微问题。例如,您尚未实现加,减,乘等的安全方法,因此在组合金额时您可能会遇到舍入错误
1800信息

2
这里的问题是,例如Money(0.1),这意味着JavaScript词法分析器从源中读取字符串“ 0.1”,然后将其转换为二进制浮点,然后您已经进行了意外的舍入。问题在于表示(二进制还是十进制),而不是精度
mgd 2015年


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 fils。日元(JPY)没有子单位。因此,“乘以100进行整数运算”并不总是正确的答案。

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

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

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

如何用javascript处理金钱对相关要点进行了很好的讨论。

在搜索中,我发现了dinero.js库,该库解决了货币计算中的许多问题。尚未在生产系统中使用过它,因此无法给出明智的意见。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.