为了创建跨平台代码,我想用 JavaScript 开发一个简单的金融应用程序。所需的计算涉及复利和相对较长的十进制数。我想知道在使用 JavaScript 进行此类数学运算时应避免哪些错误——如果可能的话!
JavaScript 中的精确财务计算。什么是陷阱?
您可能应该将十进制值缩放 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 这样的实数不能用十进制值在数学中表示,因为它们是无穷无尽的(例如 - .3333333333333333 ...)。一些十进制数不能正确地用二进制表示。例如,0.1 不能用有限位数的二进制正确表示。
更详细的描述请看这里:http : //docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
看一下解决方案的实现: http : //floating-point-gui.de/languages/javascript/