{"id":13527798,"url":"https://github.com/tc39/proposal-numeric-separator","last_synced_at":"2025-04-01T10:30:31.752Z","repository":{"id":48635963,"uuid":"88649393","full_name":"tc39/proposal-numeric-separator","owner":"tc39","description":"A proposal to add numeric literal separators in JavaScript. ","archived":true,"fork":false,"pushed_at":"2021-07-16T15:18:56.000Z","size":168,"stargazers_count":331,"open_issues_count":2,"forks_count":32,"subscribers_count":34,"default_branch":"main","last_synced_at":"2024-08-02T06:25:08.257Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://tc39.es/proposal-numeric-separator","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tc39.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-18T16:55:19.000Z","updated_at":"2024-07-31T15:17:23.000Z","dependencies_parsed_at":"2022-09-12T15:13:32.991Z","dependency_job_id":null,"html_url":"https://github.com/tc39/proposal-numeric-separator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-numeric-separator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-numeric-separator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-numeric-separator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tc39%2Fproposal-numeric-separator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tc39","download_url":"https://codeload.github.com/tc39/proposal-numeric-separator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222715679,"owners_count":17027699,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T06:02:02.169Z","updated_at":"2024-11-02T12:32:25.455Z","avatar_url":"https://github.com/tc39.png","language":"HTML","readme":"# Numeric Separators\n\n## Stage 4\n\nThis is a [proposal](https://tc39.github.io/process-document/), the result of a merge between an earlier draft of itself and Christophe Porteneuve's [proposal-numeric-underscores](https://github.com/tdd/proposal-numeric-underscores), to extend the existing [_NumericLiteral_](https://tc39.github.io/ecma262/#prod-NumericLiteral) to allow a separator character between digits.\n\n## Acknowledgements\n\nThis proposal is currently championed by @samuelgoto, @rwaldron, and @leobalter.\n\nThis proposal was originally developed by @samuelgoto, @ajklein, @domenic, @rwaldron and @tdd.\n\n## Motivation\n\nThis feature enables developers to make their numeric literals more readable by creating a visual separation between groups of digits. Large numeric literals are difficult for the human eye to parse quickly, especially when there are long digit repetitions. This impairs both the ability to get the correct value / order of magnitude...\n\n```js\n1000000000   // Is this a billion? a hundred millions? Ten millions?\n101475938.38 // what scale is this? what power of 10?\n```\n\n...but also fails to convey some use-case information, such as fixed-point arithmetic using integers.  For instance, financial computations often work in 4- to 6-digit fixed-point arithmetics, but even storing amounts as cents is not immediately obvious without separators in literals:\n\n```js\nconst FEE = 12300;\n// is this 12,300? Or 123, because it's in cents?\n\nconst AMOUNT = 1234500;\n// is this 1,234,500? Or cents, hence 12,345? Or financial, 4-fixed 123.45?\n```\nUsing underscores (`_`, U+005F) as separators helps improve readability for numeric literals, both integers and floating-point (and in JS, it's all floating-point anyway):\n```js\n1_000_000_000           // Ah, so a billion\n101_475_938.38          // And this is hundreds of millions\n\nlet fee = 123_00;       // $123 (12300 cents, apparently)\nlet fee = 12_300;       // $12,300 (woah, that fee!)\nlet amount = 12345_00;  // 12,345 (1234500 cents, apparently)\nlet amount = 123_4500;  // 123.45 (4-fixed financial)\nlet amount = 1_234_500; // 1,234,500\n```\n\nAlso, this works on the fractional and exponent parts, too:\n\n```js\n0.000_001 // 1 millionth\n1e10_000  // 10^10000 -- granted, far less useful / in-range...\n```\n\n## Examples\n\n(The following examples also appear in the README.md of Babel transform plugin for this proposal.)\n\n### Regular Number Literals\n\n```js\nlet budget = 1_000_000_000_000;\n\n// What is the value of `budget`? It's 1 trillion!\n// \n// Let's confirm:\nconsole.log(budget === 10 ** 12); // true\n```\n\n### Binary Literals\n\n```js\nlet nibbles = 0b1010_0001_1000_0101;\n\n// Is bit 7 on? It sure is!\n// 0b1010_0001_1000_0101\n//             ^\n//\n// We can double check: \nconsole.log(!!(nibbles \u0026 (1 \u003c\u003c 7))); // true\n```\n\n### Hex Literal\n\n```js\n// Messages are sent as 24 bit values, but should be \n// treated as 3 distinct bytes:\nlet message = 0xA0_B0_C0;\n\n// What's the value of the upper most byte? It's A0, or 160.\n// We can confirm that:\nlet a = (message \u003e\u003e 16) \u0026 0xFF; \nconsole.log(a.toString(16), a); // a0, 160\n\n// What's the value of the middle byte? It's B0, or 176.\n// Let's just make sure...\nlet b = (message \u003e\u003e 8) \u0026 0xFF;\nconsole.log(b.toString(16), b); // b0, 176\n\n// What's the value of the lower most byte? It's C0, or 192.\n// Again, let's prove that:\nlet c = message \u0026 0xFF;\nconsole.log(c.toString(16), b); // c0, 192\n```\n\n### BigInt Literal\n\nNumeric Separators are also available within BigInt literals.\n\n```js\n// Verifying max signed 64 bit numbers:\nconst max = 2n ** (64n - 1n) - 1n;\nconsole.log(max === 9_223_372_036_854_775_807n);\n```\n\nIt can also be used similarly to Number literals\n\n```js\nlet budget = 1_000_000_000_000n;\n\n// What is the value of `budget`? It's 1 trillion!\n// \n// Let's confirm:\nconsole.log(budget === BigInt(10 ** 12)); // true\n```\n\nNumeric Separators are only allowed between digits of BigInt literals, and not immediately before the BigInt `n` suffix.\n\n```js\n// Valid\n1_1n;\n1_000n;\n99999999_111111111_00000000n;\n\n// Invalid: SyntaxError!\n1_n;\n0_n;\n1000000_n;\n1_000_000_n;\n```\n\n\n\n### Octal Literal\n\nWhile there isn't much of a benefit, numeric separators are available in the Octal Literal productions out of conventially being generally available in non-legacy productions. In other words, the intent for feature is to be broad available in non-legacy numeric literal types.\n\n```js\nlet x = 0o1234_5670;\nlet partA = (x \u0026 0o7777_0000) \u003e\u003e 12; // 3 bits per digit\nlet partB = x \u0026 0o0000_7777;\nconsole.log(partA.toString(8)); // 1234\nconsole.log(partB.toString(8)); // 5670\n```\n\n## Specification\n\nYou can see what the specification design looks like [here](spec.md) and a more detailed version [here](https://tc39.github.io/proposal-numeric-separator).\n\n## Background\n\n### Alternative Syntax\n\nOur strawnman strategy is to **start with** a more restrictive rule (i.e. disallow both idioms) and losen it upon later if needed (as opposed to starting more broadly and worrying about backwards compatibility trying to tighten it up later).\n\nIn addition to that, we couldn't find good/practical evicence where (a) multiple consecutive underscores or (b) underscores before/after numbers are used effectively, so we chose to leave that addition to a later stage if needed/desired.\n\n### Character\n\nThe `_` was agreed to as part of Stage 1 acceptance.\nThe following examples show numeric separators as they appear in other programming languages:\n\n- **`_` (Java, Python, Perl, Ruby, Rust, Julia, Ada, C#)**\n- `'` (C++)\n\n## Building the spec\n\n```sh\nnpm i\nnpm run build\n```\n\n## References\n\n### Prior art\n\n* [Java7](https://docs.oracle.com/javase/7/docs/technotes/guides/language/underscores-literals.html): multiple, only between digits.\n\n```java\nfloat pi = \t3.14_15F;\nlong hexBytes = 0xFF_EC_DE_5E;\nlong hexWords = 0xCAFE_F00D;\nlong maxLong = 0x7fff_ffff_ffff_ffffL;\nbyte nybbles = 0b0010_0101;\nlong bytes = 0b11010010_01101001_10010100_10010010;\n```\n\n\u003e Note that the first two examples are actually unlikely to be correct in any circumstance.\nTrade-offs:\n\n```java\nfloat pi1 = 3_.1415F;      // Invalid; cannot put underscores adjacent to a decimal point\nfloat pi2 = 3._1415F;      // Invalid; cannot put underscores adjacent to a decimal point\n\nint x1 = _52;              // This is an identifier, not a numeric literal\nint x2 = 5_2;              // OK (decimal literal)\nint x3 = 52_;              // Invalid; cannot put underscores at the end of a literal\nint x4 = 5_______2;        // OK (decimal literal)\n\nint x5 = 0_x52;            // Invalid; cannot put underscores in the 0x radix prefix\nint x6 = 0x_52;            // Invalid; cannot put underscores at the beginning of a number\nint x7 = 0x5_2;            // OK (hexadecimal literal)\nint x8 = 0x52_;            // Invalid; cannot put underscores at the end of a number\n\nint x9 = 0_52;             // OK (octal literal)\nint x10 = 05_2;            // OK (octal literal)\nint x11 = 052_;            // Invalid; cannot put underscores at the end of a number\n```\n\n* [C++](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3499.html): single, between digits (different separator chosen `'`).\n\n```c++\nint m = 36'000'000  // digit separators make large values more readable\n```\n\n* [Swift](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html)\n\n```swift\nlet m = 36_000_000 // Underscores (_) are allowed between digits for readability\n```\n\n* [Perl](http://perldoc.perl.org/perldata.html#Scalar-value-constructors): multiple, anywhere\n\n```perl\n 3.14_15_92          # a very important number\n 4_294_967_296       # underscore for legibility\n 0xff                # hex\n 0xdead_beef         # more hex\n```\n\n* [Ruby](http://ruby-doc.org/core-2.3.0/doc/syntax/literals_rdoc.html#label-Numbers): single, only between digits.\n\n```ruby\n1_234\n```\n\n* [Rust](https://doc.rust-lang.org/reference.html#number-literals): multiple, anywhere.\n\n```rust\n0b1111_1111_1001_0000_i32;         // type i32\n1_234.0E+18f64\n```\n\n* [Julia](https://docs.julialang.org/en/release-0.4/manual/integers-and-floating-point-numbers/): single, only between digits.\n\n```julia\njulia\u003e 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010\n(10000,5.0e-9,0xdeadbeef,0xb2)\n```\n\n* [Ada](http://archive.adaic.com/standards/83lrm/html/lrm-02-04.html#2.4): single, only between digits.\n\n```ada\n123_456\n3.14159_26\n\n```\n\n* [Kotlin](https://kotlinlang.org/docs/reference/basic-types.html#underscores-in-numeric-literals-since-11)\n\n```kotlin\nval oneMillion = 1_000_000\nval creditCardNumber = 1234_5678_9012_3456L\nval socialSecurityNumber = 999_99_9999L\nval hexBytes = 0xFF_EC_DE_5E\nval bytes = 0b11010010_01101001_10010100_10010010\n```\n\n### Ongoing Proposals\n\n* [Python Proposal: Underscore in Numeric Literals](https://www.python.org/dev/peps/pep-0515/#id19): single, only between digits.\n\n```python\n# grouping decimal numbers by thousands\namount = 10_000_000.0\n\n# grouping hexadecimal addresses by words\naddr = 0xCAFE_F00D\n\n# grouping bits into nibbles in a binary literal\nflags = 0b_0011_1111_0100_1110\n\n# same, for string conversions\nflags = int('0b_1111_0000', 2)\n```\n\n* [C# Proposal: Digit Separators](https://github.com/dotnet/roslyn/issues/216): multiple, only between digits.\n\n```\nint bin = 0b1001_1010_0001_0100;\nint hex = 0x1b_a0_44_fe;\nint dec = 33_554_432;\nint weird = 1_2__3___4____5_____6______7_______8________9;\ndouble real = 1_000.111_1e-1_000;\n```\n\n### Related Work\n\n* [Format Specifier For Thousands Separator](https://www.python.org/dev/peps/pep-0378/)\n\n### Implementations\n\n* [Shipping in V8 v7.5 / Chrome 75](https://v8.dev/blog/v8-release-75#numeric-separators)\n* [Shipping in SpiderMonkey / Firefox 70](https://bugzilla.mozilla.org/show_bug.cgi?id=1435818)\n* [Shipping in JavaScriptCore / Safari 13](https://bugs.webkit.org/show_bug.cgi?id=196351)\n","funding_links":[],"categories":["HTML"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftc39%2Fproposal-numeric-separator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftc39%2Fproposal-numeric-separator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftc39%2Fproposal-numeric-separator/lists"}