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

jam*_*ack 115 javascript finance decimal

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

Dan*_*llo 102

您应该将小数值缩放100,并以整分美分表示所有货币值.这是为了避免浮点逻辑和算术问题.JavaScript中没有十进制数据类型 - 唯一的数字数据类型是浮点数.因此,通常建议将钱作为2550美分而不是25.50美元来处理.

在JavaScript中考虑一下:

var result = 1.0 + 2.0;     // (result === 3.0) returns true
Run Code Online (Sandbox Code Playgroud)

但:

var result = 0.1 + 0.2;     // (result === 0.3) returns false
Run Code Online (Sandbox Code Playgroud)

表达式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:The Definitive Guide,Fourth Edition:3.1.3 Floating-Point Literals(第31页).

  • **计算便士而不是美元也无济于事.**计算便士时,你失去了在大约10 ^ 16的整数上加1的能力.计算美元时,您将失去将.01添加到10 ^ 14的数字的能力.这两种方式都是一样的. (7认同)
  • @slashingweapon,如果我错了,任何人都可以随时纠正我,但 10^16 美分是一百万亿美元。对于大多数情况,这不会成为问题。在你处理超过一百万亿美元之前,0.1 + 0.2 就会突然出现。 (7认同)
  • @Cirrostratus:您可以查看http://stackoverflow.com/questions/744099/.如果继续使用缩放方法,通常您需要根据希望保留精度的小数位数来缩放值.如果您需要2位小数,则按100计算,如果需要4,则按10000计算. (6认同)
  • 并且提醒一下,总是围绕分数进行计算,并以最不利于消费者的方式这样做,IE如果你正在计算税收,那就整理一下.如果您正在计算利息,请截断. (3认同)
  • ...关于3000.57值,是的,如果将该值存储在JavaScript变量中,并且您打算对其进行算术运算,则可能需要将其存储为300057(分数).因为`3000.57 + 0.11 === 3000.68`返回`false`. (2认同)

Fra*_*tto 15

将每个值缩放100是解决方案.手工操作可能没用,因为您可以找到为您执行此操作的库.我推荐moneysafe,它提供了一个非常适合ES6应用的功能API:

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

https://github.com/ericelliott/moneysafe

在Node.js和浏览器中都可以使用.

  • 自述文件中还有一个提示:“Money$afe 尚未在生产中进行大规模测试。” 只是指出这一点,以便任何人都可以考虑这是否适合他们的用例 (4认同)
  • 缩放100只会有所帮助,直到您开始想要执行诸如计算百分比(本质上是执行除法)之类的操作。 (3认同)
  • 赞成。已接受的答案中已经涵盖了“按 100 缩放”这一点,但是您最好添加了一个具有现代 JavaScript 语法的软件包选项。FWIW `in$, $` 值名称对于以前没有使用过该包的人来说是不明确的。我知道 Eric 选择以这种方式命名事物,但我仍然觉得这是一个足够的错误,我可能会在导入/解构的 require 语句中重命名它们。 (2认同)
  • 我希望我可以多次为评论点赞。仅按 100 缩放是不够的。JavaScript 中唯一的数字数据类型仍然是浮点数据类型,并且最终仍会出现严重的舍入错误。 (2认同)

mar*_*gti 14

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

此外,对于货币计算,您还需要跟踪货币。您无法将美元 (USD) 添加到印度卢比 (INR)(未先将其中一种兑换成另一种)。

JavaScript 的整数数据类型可以表示的最大数量也有限制。

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

How to handle Money in javascript对相关要点有很好的讨论。

在我的搜索中,我发现dinero.js库可以解决许多有关货币计算的问题。尚未在生产系统中使用它,因此无法对此给出明智的意见。


sel*_*oup 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
Run Code Online (Sandbox Code Playgroud)

这样,每次使用Money对象时,它将被表示为四舍五入到两位小数.仍然可以访问未包含的值m.amount.

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

  • 圆形不够精确. (3认同)
  • 不应该是sys.puts(m + n); //80.76实际读取sys.puts(m + n); //80.77?我相信你忘记了.5了. (3认同)
  • 这种方法有许多微妙的问题可以突然出现.例如,您没有实现加法,减法,乘法等安全方法,因此在合并金额时可能会遇到舍入错误 (2认同)
  • 这里的问题是,如'钱(0.1)`意味着JavaScript的词法分析器从源中读取字符串"0.1",然后将其转换为二进制浮点然后你已经做了一个意外的舍入.问题是_representation_(二进制与十进制)不是关于_precision_. (2认同)

Hen*_*eng 5

您的问题源于浮点计算的不准确。如果您只是使用舍入来解决这个问题,那么在乘法和除法时就会产生更大的误差。

解决办法如下,解释如下:

您需要考虑其背后的数学才能理解它。像 1/3 这样的实数不能在数学中用十进制值表示,因为它们是无穷无尽的(例如 - .333333333333333 ...)。一些十进制数无法正确地用二进制表示。例如,0.1无法用有限的位数正确地用二进制表示。

有关更详细的描述,请参见此处:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

看看解决方案的实现: http://floating-point-gui.de/languages/javascript/


tle*_*jmi 5

使用decimaljs ...它是一个非常好的库,解决了这个问题的严峻部分...

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

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