Skip to content

Commit eb0ad3b

Browse files
committed
types-grammar, ch1: adding more about alternate numeric literal forms
1 parent 6d80964 commit eb0ad3b

File tree

1 file changed

+113
-15
lines changed

1 file changed

+113
-15
lines changed

types-grammar/ch1.md

Lines changed: 113 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -458,15 +458,19 @@ parseInt("42",10) === parseFloat("42"); // true
458458
parseInt("512px"); // 512
459459
```
460460
461-
Parsing is a character-by-character (left-to-right) operation, that pulls out numeric-looking characters from the string, and puts them into a `number` value. Parsing stops once it encounters a character that's non-numeric (e.g., not `-`, `.` or `0`-`9`). If parsing fails on the first character, both utilities return the special `NaN` value (see "Invalid Number" below).
461+
| NOTE: |
462+
| :--- |
463+
| Parsing is only relevant for string values, as it's a character-by-character (left-to-right) operation. It doesn't make sense to parse the contents of a `boolean`, nor to parse the contents of a `number` or a `null`; there's nothing to parse. If you pass anything other than a string value to `parseInt(..)` / `parseFloat(..)`, those utilties first convert that value to a string and then try to parse it. That's almost certainly problematic (leading to bugs) or wasteful -- `parseInt(42)` is silly, and `parseInt(42.3)` is an abuse of `parseInt(..)` to do the job of `Math.floor(..)`. |
462464
463-
When `parseInt(..)` encounters the `.`, it stops, leaving only the `123` in the result `number` value. `parseFloat(..)` by contrast accepts this character, and keeps right on parsing.
465+
Parsing pulls out numeric-looking characters from the string value, and puts them into a `number` value, stopping once it encounters a character that's non-numeric (e.g., not `-`, `.` or `0`-`9`). If parsing fails on the first character, both utilities return the special `NaN` value (see "Invalid Number" below), indicating the operation was invalid and failed.
464466
465-
The `parseInt(..)` utility specifically, takes as a second argument the `radix`: the numeric base to assume for parsing the string characters into a `number`. `10` is for standard base-10 numbers, `8` is for octal, and `16` is hexadecimal. Any other `radix`, like `23`, assumes `0` - `9` followed by `a` - `z` (case insensitive) character ordination.
467+
When `parseInt(..)` encounters the `.` in `"123.456"`, it stops, using just the `123` in the resulting `number` value. `parseFloat(..)` by contrast accepts this `.` character, and keeps right on parsing a float with any decimal digits after the `.`.
466468
467-
If `radix` is omitted, the behavior of `parseInt(..)` is rather nuanced and confusing, in that it attempts to make a best-guess based on what it sees in the first character. This leads to lots of subtle bugs, so never rely on the default auto-guessing; always specify an explicit radix (like `10` in the calls above).
469+
The `parseInt(..)` utility specifically, takes as an optional -- but *actually*, rather necessary -- second argument, `radix`: the numeric base to assume for interpreting the string characters for the `number` (range `2` - `36`). `10` is for standard base-10 numbers, `2` is for binary, `8` is for octal, and `16` is for hexadecimal. Any other unusual `radix`, like `23`, assumes digits in order, `0` - `9` followed by the `a` - `z` (case insensitive) character ordination. If the specified radix is outside the `2` - `36` range, `parseInt(..)` fails as invalid and returns the `NaN` value.
468470
469-
`parseFloat(..)` always parses with a radix of `10`.
471+
If `radix` is omitted, the behavior of `parseInt(..)` is rather nuanced and confusing, in that it attempts to make a best-guess for a radix, based on what it sees in the first character. This historically has lead to lots of subtle bugs, so never rely on the default auto-guessing; always specify an explicit radix (like `10` in the calls above).
472+
473+
`parseFloat(..)` always parses with a radix of `10`, so no second argument is accepted.
470474
471475
In contrast to parsing-conversion, coercive-conversion is an all-or-nothing sort of operation. Either the entire contents of the string are recognized as numeric (integer or floating-point), or the whole conversion fails (resulting in `NaN` -- again, see "Invalid Number" later in this chapter).
472476
@@ -482,11 +486,101 @@ Number("512px"); // NaN
482486
+"512px"; // NaN
483487
```
484488
489+
### Other Numeric Representations
490+
491+
In addition to defining numbers using traditional base-10 numerals (`0`-`9`), JS supports defining whole-number-only number literals in three other bases: binary (base-2), octal (base-8), and hexadecimal (base-16).
492+
493+
```js
494+
// binary
495+
myAge = 0b101010;
496+
myAge; // 42
497+
498+
// octal
499+
myAge = 0o52;
500+
myAge; // 42
501+
502+
// hexadecimal
503+
myAge = 0x2a;
504+
myAge; // 42
505+
```
506+
507+
As you can see, the prefixes `0b` (binary), `0o` (octal), and `0x` (hexadecimal) signal defining numbers in the different bases, but decimals are not allowed on these numeric literals.
508+
509+
| NOTE: |
510+
| :--- |
511+
| JS syntax allows `0B`, `0O`, and `0X` prefixes as well. However, please don't ever use those uppercase prefix forms. I think any sensible person would agree: `0O` is much easier to confuse at a glance than `0o` (which is, itself, a bit visually ambiguous at a glance). Always stick to the lowercase prefix forms! |
512+
513+
It's important to realize that you're not defining a *different number*, just using a different form to produce the same underlying numeric value.
514+
515+
By default, JS represents the underlying numeric value in output/string fashion with standard base-10 form. However, `number` values have a built-in `toString(..)` method that produces a string representation in any specified base/radix (as with `parseInt(..)`, in the range `2` - `36`):
516+
517+
```js
518+
myAge = 42;
519+
520+
myAge.toString(2); // "101010"
521+
myAge.toString(8); // "52"
522+
myAge.toString(16); // "2a"
523+
myAge.toString(23); // "1j"
524+
myAge.toString(36); // "16"
525+
```
526+
527+
You can round-trip any arbitrary-radix string representation back into a `number` using `parseInt(..)`, with the appropriate radix:
528+
529+
```js
530+
myAge = 42;
531+
532+
parseInt(myAge.toString("23"),23); // 42
533+
```
534+
535+
Another allowed form for specifying number literals is using scientific notation:
536+
537+
```js
538+
myAge = 4.2E1; // or 4.2e1 or 4.2e+1
539+
540+
myAge; // 42
541+
```
542+
543+
`4.2E1` (or `4.2e1`) means, `4.2 * (10 ** 1)` (`10` to the `1` power). The exponent can optionally have a sign `+` or `-`. If the sign is omitted, it's assumed to be `+`. A negative exponent makes the number smaller (moves the decimal leftward) rather than larger (moving the decimal rightward):
544+
545+
```js
546+
4.2E-3; // 0.0042
547+
```
548+
549+
This scientific notation form is especially useful for readability when specifying larger powers of `10`:
550+
551+
```js
552+
someBigPowerOf10 = 1000000000;
553+
554+
// vs:
555+
556+
someBigPowerOf10 = 1e9;
557+
```
558+
559+
By default, JS will represent (e.g., as string values, etc) either very large or very small numbers -- specifically, if the values require more than 21 digits of precision -- using this same scientific notation:
560+
561+
```js
562+
ratherBigNumber = 123 ** 11;
563+
ratherBigNumber.toString(); // "9.748913698143826e+22"
564+
565+
prettySmallNumber = 123 ** -11;
566+
prettySmallNumber.toString(); // "1.0257553107587752e-23"
567+
```
568+
569+
Another readability affordance for numeric literals is the ability to insert `_` as a digit separator wherever its convenient/meaningful to do so. For example:
570+
571+
```js
572+
someBigPowerOf10 = 1_000_000_000;
573+
574+
totalCostInPennies = 123_45; // vs 12_345
575+
```
576+
577+
The decision to use `12345` (no separator), `12_345` (like "12,345"), or `123_45` (like "123.45") is entirely up to the author of the code; JS ignores the separators. But depending on the context, `123_45` could be more semantically meaningful (readability wise) than the more traditional three-digit-grouping-from-the-right-separated-with-commas style mimicked with `12_345`.
578+
485579
### IEEE-754 Bitwise Binary Representations
486580
487581
IEEE-754[^IEEE754] is a technical standard for binary representation of decimal numbers. It's widely used by most computer programming languages, including JS, Python, Ruby, etc.
488582
489-
In 64-bit IEEE-754 -- so called "double-precision" because originally IEEE-754 used to be 32-bit, and now it's double that! -- the 64 bits are divided into 52 bits for the number's base value (aka, "fraction", "mantissa", or "significand"), 11 bits for the exponent to raise that value to, and 1 bit for the sign of the ultimate value.
583+
In 64-bit IEEE-754 -- so called "double-precision" because originally IEEE-754 used to be 32-bit, and now it's double that! -- the 64 bits are divided into three sections: 52 bits for the number's base value (aka, "fraction", "mantissa", or "significand"), 11 bits for the exponent to raise `2` to before multiplying, and 1 bit for the sign of the ultimate value.
490584
491585
These bits are arranged left-to-right, as so (S = Sign Bit, E = Exponent Bit, M = Mantissa Bit):
492586
@@ -497,7 +591,8 @@ MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
497591
498592
So, the number `42` (or `42.000000`) would be represented by these bits:
499593
500-
```js
594+
```
595+
// 42:
501596
01000000010001010000000000000000
502597
00000000000000000000000000000000
503598
```
@@ -516,12 +611,13 @@ As you might be able to tell now, this IEEE-754 number representation standard i
516611
517612
The number `42.0000001`, which is only different from `42.000000` by just `0.0000001`, would be represented by these bits:
518613
519-
```js
614+
```
615+
// 42.0000001:
520616
01000000010001010000000000000000
521617
00000000110101101011111110010101
522618
```
523619
524-
Notice how the previous bit pattern and this one differ by quite a few bits in the trailing positions! The binary decimal fraction containing all those extra `1` bits (`1.010100000000..01011111110010101`) converts to base-10 as `1.31250000312500003652`, which multipled by `32` gives us `42.0000001`.
620+
Notice how the previous bit pattern and this one differ by quite a few bits in the trailing positions! The binary decimal fraction containing all those extra `1` bits (`1.010100000000...01011111110010101`) converts to base-10 as `1.31250000312500003652`, which multipled by `32` gives us exactly `42.0000001`.
525621
526622
Now you understand a *bit more* about how IEEE-754 works!
527623
@@ -774,11 +870,11 @@ If you're not properly checking for `NaN` in your programs where you do math or
774870
775871
## BigInteger Values
776872
777-
As the maximum safe integer in JS `number`s is `9007199254740991`, such a relatively low limit can present a problem if a JS program needs to do larger integer math, or even just hold values like 64-bit integer IDs (e.g., Twitter Tweet IDs).
873+
As the maximum safe integer in JS `number`s is `9007199254740991` (see above), such a relatively low limit can present a problem if a JS program needs to perform larger integer math, or even just hold values like 64-bit integer IDs (e.g., Twitter Tweet IDs).
778874
779875
For that reason, JS provides the alternate `bigint` type (BigInteger), which can store arbitrarily large (theoretically not limited, except by finite machine memory and/or JS implementation) integers.
780876
781-
To distinguish a `bigint` from a `number` value, which could otherwise both look the same (`42`), JS requires an `n` suffix on `bigint` values:
877+
To distinguish a `bigint` from a whole (integer) `number` value, which would otherwise both look the same (`42`), JS requires an `n` suffix on `bigint` values:
782878
783879
```js
784880
myAge = 42n; // this is a bigint, not a number
@@ -796,6 +892,8 @@ Number.MAX_SAFE_INTEGER + 2; // 9007199254740992 -- oops!
796892
myBigInt = 9007199254740991n;
797893

798894
myBigInt + 2n; // 9007199254740993n -- phew!
895+
896+
myBigInt ** 2n; // 81129638414606663681390495662081n
799897
```
800898
801899
As you can see, the `bigint` value-type is able to do precise arithmetic above the integer limit of the `number` value-type.
@@ -822,21 +920,21 @@ myAge; // 43n
822920
823921
That's definitely one of the most common usages of the `BigInt(..)` function: to convert `number`s to `bigint`s, for mathematical operation purposes.
824922
825-
But it's not that uncommon to have large integer values represented as strings, especially if those values are coming to the JS environment from other locations, or via exchange formats, which themselves do not support `bigint`-style values.
923+
But it's not that uncommon to represent large integer values as strings, especially if those values are coming to the JS environment from other language environments, or via certain exchange formats, which themselves do not support `bigint`-style values.
826924
827-
As such, `BigInt(..)` is useful to parse those string values and convert them to `bigint`s:
925+
As such, `BigInt(..)` is useful to coerce those string values to `bigint`s:
828926
829927
```js
830928
myBigInt = BigInt("12345678901234567890");
831929

832930
myBigInt; // 12345678901234567890n
833931
```
834932
835-
Unlike `parseInt(..)`, if any character in the string is non-numeric (`0-9` digits or `-`), including `.` or even a trailing `n` suffix character, an exception will be thrown. In other words, `BigInt(..)` is all-or-nothing in its parsing/conversion.
933+
Unlike `parseInt(..)`, if any character in the string is non-numeric (`0-9` digits or `-`), including `.` or even a trailing `n` suffix character, an exception will be thrown. In other words, `BigInt(..)` is an all-or-nothing coercion-conversion, not a parsing-conversion.
836934
837935
| NOTE: |
838936
| :--- |
839-
| I think it's absurd that `BigInt(..)` won't accept the trailing `n` character while string parsing (and effectively ignore it). I lobbied vehemently for that in the TC39 process, but was ultimately denied. In my opinion, it's now a tiny little wart on JS, but a wart nonetheless. |
937+
| I think it's absurd that `BigInt(..)` won't accept the trailing `n` character while string coercing (and thus effectively ignore it). I lobbied vehemently for that behavior, in the TC39 process, but was ultimately denied. In my opinion, it's now a tiny little gotcha wart on JS, but a wart nonetheless. |
840938
841939
## Symbol Values
842940

0 commit comments

Comments
 (0)