为了创建跨平台代码,我想在JavaScript中开发一个简单的财务应用程序。所需的计算涉及复合利息和相对较长的十进制数。我想知道在使用JavaScript进行这种类型的数学时要避免哪些错误 - 如果有可能的话!
答案 0 :(得分:97)
您应该将小数值缩放100,并以整分美分表示所有货币值。这是为了避免problems with floating-point logic and arithmetic。 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 道格拉斯·克罗克福德:JavaScript: The Good Parts: Appendix A - Awful Parts (page 105) 2 David Flanagan:JavaScript: The Definitive Guide, Fourth Edition: 3.1.3 Floating-Point Literals (page 31)。
答案 1 :(得分:12)
将每个值缩放100是解决方案。手工操作可能没用,因为您可以找到为您执行此操作的库。我推荐moneysafe,它提供了一个非常适合ES6应用的功能API:
const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8
https://github.com/ericelliott/moneysafe
在Node.js和浏览器中都可以使用。
答案 2 :(得分:6)
没有“精确”的财务计算这样的事情,因为只有两个小数位数,但这是一个更普遍的问题。
在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()
中。
答案 3 :(得分: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/
答案 4 :(得分:1)
如果要构建真正精确的应用程序,则不应信任JavaScript库。相反,您可以使用Web汇编。
答案 5 :(得分:1)
不幸的是,到目前为止,所有答案都忽略了以下事实:并非所有货币都具有100个子单位(例如,分是美元(USD)的子单位)。伊拉克第纳尔(IQD)等货币有1000个子单元:伊拉克第纳尔有1000 fils。日元(JPY)没有子单位。因此,“乘以100进行整数运算”并不总是正确的答案。
此外,对于货币计算,您还需要跟踪货币。您不能将美元(USD)添加到印度卢比(INR)(必须先将一个美元转换为另一个美元)。
JavaScript的整数数据类型可以表示的最大数量也受到限制。
在货币计算中,您还必须记住,货币具有有限的精度(通常为0-3小数点),并且舍入需要以特定方式进行(例如,“正常”舍入与银行家舍入)。四舍五入的类型也可能因管辖区/货币而异。
How to handle money in javascript对相关要点进行了很好的讨论。
在搜索中,我发现了dinero.js的addresses many of the issues wrt monetary calculations库。尚未在生产系统中使用过它,因此无法对它给出知情的意见。
答案 6 :(得分:0)
答案 7 :(得分:0)
由于其编码的二进制性质,因此某些十进制数不能完全准确地表示。例如
private ObservableCollection<ScheduleDisplayObject> _displayObjects;
public ObservableCollection<ScheduleDisplayObject> DisplayObjects
{
get
{
if (_displayObjects == null)
{
_displayObjects = new ObservableCollection<ScheduleDisplayObject>();
foreach (ScheduleObject item in ScheduleManager.getList())
{
_displayObjects.Add(new ScheduleDisplayObject(item));
}
}
return _displayObjects;
}
}
如果您需要使用纯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);
Avoiding Problems with Decimal Math in JavaScript
有一个专门的库,用于财务计算和完善的文档。 Finance.js