JavaScript中的精确财务计算。有什么问题?

时间:2010-05-20 18:04:37

标签: javascript decimal finance

为了创建跨平台代码,我想在JavaScript中开发一个简单的财务应用程序。所需的计算涉及复合利息和相对较长的十进制数。我想知道在使用JavaScript进行这种类型的数学时要避免哪些错误 - 如果有可能的话!

8 个答案:

答案 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.jsaddresses many of the issues wrt monetary calculations库。尚未在生产系统中使用过它,因此无法对它给出知情的意见。

答案 6 :(得分:0)

使用decimaljs ...这是一个很好的库,可以解决问题的绝大部分...

只需在您的所有操作中使用它即可。

https://github.com/MikeMcl/decimal.js/

答案 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