为了创建跨平台代码,我想用 JavaScript 开发一个简单的金融应用程序。所需的计算涉及复利和相对较长的十进制数。我想知道在使用 JavaScript 进行此类数学运算时要避免哪些错误——如果有可能的话!
7 回答
您可能应该将十进制值缩放 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 Douglas Crockford:JavaScript:好的部分:附录 A - 糟糕的部分(第 105 页)。
2 David Flanagan:JavaScript:权威指南,第四版:3.1.3 浮点文字(第 31 页)。
将每个值缩放 100 是解决方案。手工操作可能是没有用的,因为您可以找到为您执行此操作的库。我推荐 moneysafe,它提供了一个非常适合 ES6 应用程序的函数式 API:
const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8
https://github.com/ericelliott/moneysafe
适用于 Node.js 和浏览器。
由于只有两位小数,因此没有“精确”的财务计算之类的东西,但这是一个更普遍的问题。
在 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()
如果愿意,您可以将自己的舍入算法内置到 中。
您的问题源于浮点计算的不准确性。如果您只是使用舍入来解决这个问题,那么在乘法和除法时会出现更大的错误。
解决方法如下,解释如下:
您需要考虑其背后的数学才能理解它。像 1/3 这样的实数不能用十进制值在数学中表示,因为它们是无穷无尽的(例如 - .333333333333333 ...)。一些十进制数字不能正确地用二进制表示。例如,0.1 不能以有限的位数正确表示为二进制。
更详细的描述看这里:http ://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
看一下解决方案的实现:http: //floating-point-gui.de/languages/javascript/
不幸的是,到目前为止,所有答案都忽略了并非所有货币都有 100 个子单位的事实(例如,美分是美元 (USD) 的子单位)。像伊拉克第纳尔 (IQD) 这样的货币有 1000 个子单位:一个伊拉克第纳尔有 1000 个菲尔。日元 (JPY) 没有子单位。所以“乘以 100 进行整数运算”并不总是正确的答案。
此外,对于货币计算,您还需要跟踪货币。您不能将美元 (USD) 添加到印度卢比 (INR)(不先将一个转换为另一个)。
JavaScript 的整数数据类型可以表示的最大数量也有限制。
在货币计算中,您还必须记住,货币具有有限的精度(通常为 0-3 个小数点)并且需要以特定方式进行舍入(例如,“正常”舍入与银行家的舍入)。要执行的舍入类型也可能因司法管辖区/货币而异。
How to handle money in javascript对相关点有很好的讨论。
在我的搜索中,我发现了dinero.js库,它解决了货币计算方面的许多问题。还没有在生产系统中使用它,所以不能给出一个明智的意见。
由于其编码的二进制性质,一些十进制数不能以完美的准确度表示。例如
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);
有一个用于财务计算的专用库,其中包含大量文档。财务.js