在 JavaScript 中精确的财务计算?

为了创建跨平台代码,我想用 JavaScript 开发一个简单的金融应用程序。所需的计算包括复利和相对较长的十进制数。我想知道在使用 JavaScript 进行这种类型的数学计算时应该避免哪些错误ーー如果可能的话!

78811 次浏览

您可能应该将小数值按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,但幸运的是,浮点整数算法是精确的,因此可以通过 scaling1避免小数错误。

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


1 道格拉斯·克罗克福特: < strong > JavaScript: 好的部分 : 附录 A-糟糕的部分(第105页)
大卫 · 弗拉纳根: < strong > JavaScript: 最终指南,第四版 : 3.1.3浮点文字(第31页)

没有所谓的“精确”财务计算,因为只有两个小数位,但这是一个更普遍的问题。

在 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-object 时,它都会被四舍五入为两个小数。未四舍五入的值仍然可以通过 m.amount访问。

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

你的问题源于浮点计算的不准确性。如果你只是用四舍五入来解决这个问题,那么当你乘除的时候会有更大的误差。

解决方案如下,解释如下:

你需要考虑背后的数学来理解它。像1/3这样的实数不能用十进制表示,因为它们是无穷无尽的(例如-.33333333333333333333...)。有些十进制数字无法用二进制正确表示。例如,0.1不能用有限数字的二进制正确表示。

更详细的描述请看这里: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

看一下解决方案的实现: < a href = “ http://float-point-gui.de/language/javascript/”rel = “ norefrer”> http://floating-point-gui.de/languages/javascript/

解决方案是将每个值缩放100。手工完成可能是无用的,因为您可以找到为您完成这项工作的库。我推荐 moneysafe,它提供了一个非常适合 ES6应用程序的功能性 API:

const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8

Https://github.com/ericelliott/moneysafe

既可以在 Node.js 中使用,也可以在浏览器中使用。

它是一个非常好的库,可以解决一个棘手的问题..。

只要在你所有的行动中使用它。

Https://github.com/mikemcl/decimal.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);

在 JavaScript 中避免十进制数学问题

有一个专门的图书馆为财务计算与伟大的文档

不幸的是,到目前为止,所有的答案都忽略了一个事实,即并非所有货币都有100个子单位(例如,美分是美元的子单位)。像伊拉克第纳尔(IQD)这样的货币有1000个子单位: 伊拉克第纳尔有1000个文件。日元(JPY)没有子单位。所以“乘以100做整数运算”并不总是正确的答案。

此外,对于货币计算,您还需要跟踪货币。你不能把一美元(美元)加到一个印度卢比(INR)(没有首先把一个转换成另一个)。

对于 JavaScript 的整数数据类型所能表示的最大数量也有一些限制。

在货币计算中,你还必须记住,货币的精度是有限的(通常是0-3个小数点) ,四舍五入需要以特定的方式进行(例如,“正常”四舍五入与银行家的四舍五入)。四舍五入的类型也可能因管辖区/货币而有所不同。

如何在 javascript 中处理金钱有一个很好的相关问题的讨论。

在我的搜索中,我找到了 解决了货币计算的许多问题Dinero.js库。还没有在生产系统中使用过,所以不能给出有根据的意见。

此代码用于货币计算和两位数的整数。

<!DOCTYPE html>
<html>
<body>


<h1>JavaScript Variables</h1>


<p id="test1"></p>
<p id="test2"></p>
<p id="test3"></p>


<script>


function setDecimalPoint(num) {
if (isNaN(parseFloat(num)))
return 0;
else {
var Number = parseFloat(num);
var multiplicator = Math.pow(10, 2);
Number = parseFloat((Number * multiplicator).toFixed(2));
return (Math.round(Number) / multiplicator);
}
}


document.getElementById("test1").innerHTML = "Without our method O/P is: " + (655.93 * 9)/100;
document.getElementById("test2").innerHTML = "Calculator O/P: 59.0337, Our value is: " + setDecimalPoint((655.93 * 9)/100);
document.getElementById("test3").innerHTML = "Calculator O/P: 32.888.175, Our value is: " + setDecimalPoint(756.05 * 43.5);
</script>


</body>
</html>