AngularJS数学问题在官方指南中

时间:2015-08-30 11:16:35

标签: angularjs math

我从AngularJS开始,阅读开发者指南 - 概念性概述。

我只得到了第一个例子 - 简单的计算器 - 并发现了一个奇怪的AngularJS行为,即以下表达式:

  

{{qty * cost |货币}}

小数字可以工作,大数字会在结果中输出指数,但是在结果之间有一些值,意外地是NaN

当然,香草javascript中的相同数字不会返回NaN

例如:

2222222222 and 200000000000

有人可以解释一下这种行为吗?

1 个答案:

答案 0 :(得分:1)

解决这些问题的最佳方法是尝试重新创建它们。开发人员工具中的控制台非常适合这种情况。

> 2222222222 * 200000000000
444444444400000000000

所以,这里没什么奇怪的。 Javascript可以处理乘法。这意味着我们可以确定奇怪的是从货币过滤器到达。

下一站是过滤器本身的来源。如果您访问Angular module的文档,那么会有一个view source按钮,可以直接转到GitHub上的源文件。

货币过滤器本身并没有太多代码。

function currencyFilter($locale) {
  var formats = $locale.NUMBER_FORMATS;
  return function(amount, currencySymbol, fractionSize) {
    if (isUndefined(currencySymbol)) {
      currencySymbol = formats.CURRENCY_SYM;
    }

    if (isUndefined(fractionSize)) {
      fractionSize = formats.PATTERNS[1].maxFrac;
    }

    // if null or undefined pass it through
    return (amount == null)
        ? amount
        : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
            replace(/\u00A4/g, currencySymbol);
  };
}

唯一使用amount变量的地方是formatNumber调用,还有来自$locale.NUMBER_FORMATS的其他内容。

快速查看src/ngLocale/angular-locale_en-us.js向我们展示了我们关注的内容。

formats.PATTERNS[1] = {
  "gSize": 3,
  "lgSize": 3,
  "maxFrac": 2,
  "minFrac": 2,
  "minInt": 1,
  "negPre": "-\u00a4",
  "negSuf": "",
  "posPre": "\u00a4",
  "posSuf": ""
}

其他参数只是格式化细节。现在让我们来看看这个formatNumber方法。这很长,所以我会尝试切入重要的部分。

在早期,我们的号码将转换为字符串var numStr = number + '',

在此之后通过读取来干燥运行代码变得非常重要,所以回到演示并在formatNumber函数的开头抛出一个断点(在非缩小版本中的第18580行)角)。

如果我们跳过功能,我们可以在本地范围内看到numStr分配给h。当我们步入第18627行时,h本地变为"NaN"

在18617,我可以看到两个似乎包含我们价值的变量bh

b = 444444444400000000000
h = "444444444400000000000"

在18619 b成为NaNh保持不变。所以违规的代码行是

number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);

现在我们到了某个地方。这段代码中充斥着试图将数字强制转换为字符串的内容,反之亦然。

fractionSize作为该函数的最后一个参数进入。我们之前知道的是我们的区域设置编号格式模式的maxFrac字段。

"maxFrac": 2,

让我们开始替换函数调用中的值,以便我们可以在控制台中运行它。

var number = 444444444400000000000;
var fractionSize = 2;

+(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);

我们可以使用它来确认这确实会创建NaN

现在要分解它以找出原因。最里面的表达式如下

number.toString() + 'e' + fractionSize
// "444444444400000000000e2"

使用+运算符立即将此值强制转换为数字。

+(number.toString() + 'e' + fractionSize)
// 4.444444444e+22

然后这个数字是四舍五入的,但它保持不变。然后它被转换回一个字符串。

Math.round(+(number.toString() + 'e' + fractionSize)).toString()
// "4.444444444e+22"

为了能够阅读此内容,我们可以调用该值x

x = "4.444444444e+22"

现在我们将x与另一个'e'-fractionSize连接起来。

x + 'e' + -fractionSize
// "4.444444444e+22e-2"

我们开始看到这出错了。最后,再次使用+运算符将字符串强制转换为数字。

+(x + 'e' + -fractionSize)
// NaN

宾果

发生什么事了?数字四舍五入的实现似乎有一个优势。之前它会检查该数字是否包含指数,然后相应处理它。但是,如此处所示,存在一些边缘情况,其中大数字在此舍入方法中以两个指数结束。看作是无效的符号,数字不能被强制,最终为NaN。

这个边缘情况几乎肯定与你选择乘以的数字相关的事实,超过了Number.MAX_SAFE_INTEGER(9007199254740991)的界限。一般来说,当您使用这么大的数字时,您可能会发现许多代码无法以您预期的方式处理它们。特别是如果该代码将在字符串和数字之间进行前后转换。

最后,该NaN值将转换回字符串并送入货币过滤器。