为了创建跨平台代码,我想用JavaScript开发一个简单的财务应用程序。所需的计算涉及复利和相对较长的十进制数。我想知道使用JavaScript进行此类数学运算时应避免的错误-如果有可能的话!
为了创建跨平台代码,我想用JavaScript开发一个简单的财务应用程序。所需的计算涉及复利和相对较长的十进制数。我想知道使用JavaScript进行此类数学运算时应避免的错误-如果有可能的话!
Answers:
您可能应该将小数点后的值乘以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页)。
3000.57 + 0.11 === 3000.68
回报false
。
解决方案是将每个值缩放100。手动执行此操作可能没什么用,因为您可以找到为您执行此操作的库。我建议使用moneysafe,它提供的功能API非常适合ES6应用程序:
const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8
https://github.com/ericelliott/moneysafe
在Node.js和浏览器中均可使用。
in$, $
值名称与以前没有使用过该程序包的人模棱两可。我知道这是Eric以这种方式命名的选择,但我仍然觉得很错误,我可能会在import / destructed require语句中重命名它们。
Money$afe has not yet been tested in production at scale.
。只是指出这一点,以便任何人都可以考虑是否适合他们的用例
由于只有两位小数,所以没有“精确”的财务计算,但这是一个更普遍的问题。
在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(0.1)
,这意味着JavaScript词法分析器从源中读取字符串“ 0.1”,然后将其转换为二进制浮点,然后您已经进行了意外的舍入。问题在于表示(二进制还是十进制),而不是精度。
您的问题源于浮点计算的不准确性。如果您仅使用舍入来解决此问题,那么在进行乘法和除法运算时会遇到更大的错误。
解决方案如下,解释如下:
您需要考虑其背后的数学知识才能理解它。像1/3这样的实数不能用十进制值表示在数学中,因为它们是无穷的(例如-.333333333333333 ...)。有些十进制数字不能正确用二进制表示。例如,0.1不能以有限的位数正确地用二进制表示。
有关更多详细说明,请参见此处:http : //docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
看一下解决方案的实现: http : //floating-point-gui.de/languages/javascript/
由于其编码的二进制性质,某些十进制数不能完全准确地表示。例如
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);
有一个专门的库,用于财务计算和完善的文档。Finance.js
不幸的是,到目前为止,所有答案都忽略了以下事实:并非所有货币都具有100个子单位(例如,分是美元(USD)的子单位)。伊拉克第纳尔(IQD)等货币具有1000个子单元:伊拉克第纳尔具有1000 fils。日元(JPY)没有子单位。因此,“乘以100进行整数运算”并不总是正确的答案。
此外,对于货币计算,您还需要跟踪货币。您无法将美元(USD)添加到印度卢比(INR)(必须先将一个货币转换为另一个货币)。
JavaScript的整数数据类型可以表示的最大数量也有限制。
在进行货币计算时,还必须记住,货币具有有限的精度(通常为0-3小数点),并且舍入需要以特定方式进行(例如,“正常”舍入与银行家舍入)。要执行的舍入类型也可能会因司法管辖区/货币而异。
如何用javascript处理金钱对相关要点进行了很好的讨论。
在搜索中,我发现了dinero.js库,该库解决了货币计算中的许多问题。尚未在生产系统中使用过它,因此无法给出明智的意见。