JavaScript:整体优势


不久之前, JavaScript 吹嘘了一种新的原始BigInt数据类型 ,可以处理任意精度的数字。 关于动机和用例的必要信息已经被告知 / 翻译 。 我想更多地关注类型转换中夸大的局部“显式性”和意外的TypeError 。 我们会再次责骂或理解并原谅吗?

隐含变得显露?


在隐式类型转换已被长期使用的语言中,它已成为几乎所有会议的模因,而很少有人对诸如此类的复杂性感到惊讶:

1 + {}; // '1[object Object]' 1 + [[0]]; // '10' 1 + new Date; // '1Fri Feb 08 2019 00:32:57 GMT+0300 (,  )' 1 - new Date; // -1549616425060 ... 

我们突然收到一个TypeError,试图添加两个看似数字:

 1 + 1n; // TypeError: Cannot mix BigInt and other types, use explicit conversions 

而且,如果以前的隐含经验并未导致学习该语言的失败,那么就有第二次机会来分解并扔掉ECMA教科书并使用Java。

此外,该语言继续吸引js开发人员:

 1n + '1'; // '11' 

哦,是的,不要忘记一元+运算符:

 +1n; // TypeError: Cannot convert a BigInt value to a number Number(1n); // 1 

简而言之,我们不能在操作中混合使用BigIntNumber 。 因此,如果2 ^ 53-1( MAX_SAFE_INTEGER )足以满足我们的目的,则不建议使用“大整数”。

关键决定


是的,这是这项创新的主要决定。 如果您忘记了这是JavaScript,那么一切都是那么合理:这些隐式转换会导致信息丢失。

当我们将两个不同数字类型的值(大整数和浮点数)相加时,结果的数学值可能超出了它们的可能值范围。 例如,表达式(2n ** 53n + 1n)+ 0.5的值不能用这些类型中的任何一种准确表示。 这不再是整数,而是一个实数,但float64格式不再保证其准确性:

 2n ** 53n + 1n; // 9007199254740993n Number(2n ** 53n + 1n) + 0.5; // 9007199254740992 

在大多数动态语言中,表示整数和浮点数的类型时,前者写为1 ,后者写为1.0 。 因此,在操作数中存在小数点分隔符的算术运算期间,我们可以得出结论,计算中的浮点精度是可以接受的。 但是JavaScript并不是其中之一, 1是浮点数! 这意味着计算2n ** 53n +1将返回浮点数2 ^ 53。 反过来,这破坏了BigInt的关键功能:

 2 ** 53 === 2 ** 53 + 1; // true 

嗯,也没有理由谈论“数字塔”的实现,因为您不会成功地将现有数字作为通用数字数据类型(出于同样的原因)。

为避免此问题,禁止在操作中在NumberBigInt之间进行隐式转换。 结果,“大整数”不能安全地转换为任何需要通常数字的JavaScript或Web API函数:

 Math.max(1n, 10n); // TypeError 

您必须使用Number()BigInt()显式选择两种类型之一。

另外,对于混合类型的操作,有一个关于复杂的实现或性能损失的解释 ,这在折衷的语言创新中很常见。

当然,这适用于其他原语的隐式数值转换:

 1 + true; // 2 1n + true; // TypeError 1 + null; // 1 1n + null; // TypeError 

但是,以下(已经)的串联将起作用,因为预期结果是字符串:

 1n + [0]; // '10' 1n + {}; // '1[object Object]' 1n + (_ => 1); // '1_ => 1' 

另一个例外是NumberBigInt之间的比较运算符(例如<>== )形式。 由于结果是布尔值,因此也不会损失准确性。

好吧,如果您还记得以前的新Symbol符号数据类型,那么TypeError不再看起来像是一个如此激进的补充吗?

 Symbol() + 1; // TypeError: Cannot convert a Symbol value to a number 

是的,但是没有。 实际上,概念上的符号根本不是数字,而是一个整体-非常:

  1. 符号极不可能落入这种情况。 但是,这非常可疑,TypeError在这里非常合适。
  2. 当确实没有错的时候,运算中的“大整体”很有可能成为运算元之一。

由于与asm.js的兼容性问题,一元+运算符会引发异常,其中Number是预期的。 一元加号不能以与Number相同的方式与BigInt一起使用 ,因为在这种情况下,先前的asm.js代码将变得模棱两可。

替代报价


尽管BigInt实现相对简单和“干净”,但Axel Rauschmeyer强调缺乏创新。 即,它仅与现有Number及其随后的部分向后兼容:
最多使用53位整数。 如果需要更多位,请使用整数
作为替代方案,他提出了以下建议

Number成为新的IntDouble的超类型:

  • typeof 123.0 ==='number'Number.isDouble(123.0)=== true
  • typeof 123 ==='number'Number.isInt(123)=== true

使用Number.asInt()Number.asDouble()转换的新函数。 当然,还有运算符重载和必要的强制转换:

  • Int×Double = Double(cast)
  • Double×Int = Double(带强制转换)
  • 双倍×双倍=双倍
  • Int×Int = Int(除除运算符外的所有运算符)

有趣的是,在简化版本中,这句话在管理时(首先)没有在语言中添加新类型。 取而代之的是,“数字类型”定义扩展了:除了所有可能的64位双精度数字(IEEE 754-2008),数字现在还包括所有整数。 结果,“不正确的数字” 123.0和“精确的数字” 123是单个数字类型的分开的数字。

它看起来非常熟悉且合理。 但是,这是对现有数字的严重升级,很可能会“破坏网络”及其工具:

  • 11.0之间存在差异,以前是没有的。 现有代码可互换使用,升级后可能导致混乱(与最初存在这种差异的语言不同)。
  • 1 === 1.0 (应该是升级)并且同时Number.isDouble(1)!== Number.isDouble(1.0)时会起作用 :同样,就像这样。
  • 等式2 ^ 53和2 ^ 53 +1的“特殊性”消失了,这将破坏依赖于此的代码。
  • 与asm.js或更多版本存在相同的兼容性问题。

因此,最后,我们有了一种折衷的解决方案,采用了新的单独数据类型。 值得强调的是,还考虑并讨论了另一种选择。

当你坐在两把椅子上


实际上, 委员会的评论开头是这样的:
在保持用户直觉和保持精度之间找到平衡

一方面,我终于想在语言中添加一些“精确”的东西。 另一方面,保持许多开发人员已经熟悉的行为。

只是无法添加此“精确”字,因为您无法破坏它:数学,语言的人体工程学,asm.js, 类型系统进一步扩展可能性 ,生产力以及最终的网络本身! 而且,您不能同时破坏所有这些,从而导致相同的结果。

而且您无法打破语言用户的直觉,当然,这也引起了激烈的争论 。 是的,结果可行吗?

Source: https://habr.com/ru/post/zh-CN439402/


All Articles