Numeric separators S4
中文标题:数字分隔符
提案速览
该提案通过允许在数字之间使用下划线(_)分隔符,解决了 JavaScript 中大型数字字面量的可读性问题。它扩展了现有的 NumericLiteral 生成,支持在十进制、二进制、八进制、十六进制和 BigInt 字面量中使用分隔符,并对放置位置有限制(例如,不能紧邻小数点或位于两端)。
Note
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
数字分隔符
第 4 阶段
这是一个提案,是早期草案与 Christophe Porteneuve 的 proposal-numeric-underscores 合并的结果,旨在扩展现有的 NumericLiteral,允许在数字之间使用分隔符。
致谢
本提案目前由 @samuelgoto、@rwaldron 和 @leobalter 负责推进。
本提案最初由 @samuelgoto、@ajklein、@domenic、@rwaldron 和 @tdd 开发。
动机
此功能使开发人员能够通过在数字组之间创建视觉分隔来提高数字字面量的可读性。大的数字字面量很难让人眼快速解析,尤其是在数字重复较长的情况下。这既影响了获取正确数值/数量级的能力……
1000000000 // 这是十亿?一亿?还是一千万?
101475938.38 // 这是什么量级?这是 10 的多少次方?
……也未能传达一些用例信息,例如使用整数的定点运算。例如,金融计算通常使用 4 到 6 位的定点运算,但即使将金额存储为分,在字面量中没有分隔符时也不容易立即理解:
const FEE = 12300;
// 这是 12,300?还是 123,因为是以分为单位?
const AMOUNT = 1234500;
// 这是 1,234,500?还是以分为单位,即 12,345?或者金融上的 4 位定点,即 123.45?
使用下划线(_,U+005F)作为分隔符有助于提高数字字面量的可读性,无论是整数还是浮点数(在 JS 中,所有数字都是浮点数):
1_000_000_000 // 啊,十亿
101_475_938.38 // 这是数亿
let fee = 123_00; // $123(显然是 12300 分)
let fee = 12_300; // $12,300(哇,那笔费用!)
let amount = 12345_00; // 12,345(显然是 1234500 分)
let amount = 123_4500; // 123.45(4 位定点金融)
let amount = 1_234_500; // 1,234,500
此外,这也适用于小数部分和指数部分:
0.000_001 // 百万分之一
1e10_000 // 10^10000 -- 诚然,这远远不那么有用 / 不在范围内...
示例
(以下示例也出现在本提案的 Babel 转换插件 README.md 中。)
常规数字字面量
let budget = 1_000_000_000_000;
// `budget` 的值是多少?是 1 万亿!
//
// 让我们确认一下:
console.log(budget === 10 ** 12); // true
二进制字面量
let nibbles = 0b1010_0001_1000_0101;
// 第 7 位是开启的吗?当然是!
// 0b1010_0001_1000_0101
// ^
//
// 我们可以再次确认:
console.log(!!(nibbles & (1 << 7))); // true
十六进制字面量
// 消息以 24 位值发送,但应
// 视为 3 个不同的字节:
let message = 0xA0_B0_C0;
// 最高字节的值是多少?是 A0,即 160。
// 我们可以确认这一点:
let a = (message >> 16) & 0xFF;
console.log(a.toString(16), a); // a0, 160
// 中间字节的值是多少?是 B0,即 176。
// 让我们确认一下...
let b = (message >> 8) & 0xFF;
console.log(b.toString(16), b); // b0, 176
// 最低字节的值是多少?是 C0,即 192。
// 再次,让我们验证一下:
let c = message & 0xFF;
console.log(c.toString(16), b); // c0, 192
BigInt 字面量
数字分隔符也可在 BigInt 字面量中使用。
// 验证最大有符号 64 位数字:
const max = 2n ** (64n - 1n) - 1n;
console.log(max === 9_223_372_036_854_775_807n);
它也可以像数字字面量一样使用。
let budget = 1_000_000_000_000n;
// `budget` 的值是多少?是 1 万亿!
//
// 让我们确认一下:
console.log(budget === BigInt(10 ** 12)); // true
数字分隔符仅允许出现在 BigInt 字面量的数字之间,而不能紧邻 BigInt 的 n 后缀。
// 有效
1_1n;
1_000n;
99999999_111111111_00000000n;
// 无效:抛出 SyntaxError!
1_n;
0_n;
1000000_n;
1_000_000_n;
八进制字面量
虽然好处不大,但数字分隔符在八进制字面量生产中可用是因为通常在所有非遗留生产中都可用。换句话说,该特性的意图是在非遗留数字字面量类型中广泛可用。
let x = 0o1234_5670;
let partA = (x & 0o7777_0000) >> 12; // 每个数字 3 位
let partB = x & 0o0000_7777;
console.log(partA.toString(8)); // 1234
console.log(partB.toString(8)); // 5670
规范
你可以看到规范设计的草稿这里,以及更详细的版本这里。
背景
替代语法
我们的初步策略是从更严格的规则开始(即禁止这两种习惯用法),并在以后需要时放宽(而不是从更宽泛的规则开始,并担心向后兼容性而试图收紧它)。
除此之外,我们找不到好的/实际的证据表明 (a) 连续的多个下划线或 (b) 数字前后的下划线被有效使用,因此我们选择在需要/需要时将其留到以后的阶段。
字符
在阶段 1 验收时,_ 被认可。
以下示例展示了数字分隔符在其他编程语言中的使用方式:
_ (Java, Python, Perl, Ruby, Rust, Julia, Ada, C#)
' (C++)。
构建规范
参考
先前工作
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_F00D;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;
请注意,前两个示例在任何情况下实际上都不太可能是正确的。
权衡:
float pi1 = 3_.1415F; // 无效;不能在小数点旁边放置下划线
float pi2 = 3._1415F; // 无效;不能在小数点旁边放置下划线
int x1 = _52; // 这是一个标识符,而不是数字字面量
int x2 = 5_2; // 有效(十进制字面量)
int x3 = 52_; // 无效;不能在字面量末尾放置下划线
int x4 = 5_______2; // 有效(十进制字面量)
int x5 = 0_x52; // 无效;不能在 0x 基数前缀中放置下划线
int x6 = 0x_52; // 无效;不能在数字开头放置下划线
int x7 = 0x5_2; // 有效(十六进制字面量)
int x8 = 0x52_; // 无效;不能在数字末尾放置下划线
int x9 = 0_52; // 有效(八进制字面量)
int x10 = 05_2; // 有效(八进制字面量)
int x11 = 052_; // 无效;不能在数字末尾放置下划线
- C++:单个,位于数字之间(选择不同的分隔符
')。
int m = 36'000'000 // 数字分隔符使大数值更可读
let m = 36_000_000 // 允许在数字之间使用下划线 (_) 以提高可读性
3.14_15_92 # 一个非常重要的数字
4_294_967_296 # 下划线用于清晰度
0xff # 十六进制
0xdead_beef # 更多十六进制
0b1111_1111_1001_0000_i32; // 类型 i32
1_234.0E+18f64
julia> 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010
(10000,5.0e-9,0xdeadbeef,0xb2)
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
进行中的提案
# 按千位分组十进制数字
amount = 10_000_000.0
# 按字分组十六进制地址
addr = 0xCAFE_F00D
# 在二进制字面量中将位分组为半字节
flags = 0b_0011_1111_0100_1110
# 同样,用于字符串转换
flags = int('0b_1111_0000', 2)
int bin = 0b1001_1010_0001_0100;
int hex = 0x1b_a0_44_fe;
int dec = 33_554_432;
int weird = 1_2__3___4____5_____6______7_______8________9;
double real = 1_000.111_1e-1_000;
相关工作
实现