Skip to content

Commit 48e94e8

Browse files
committed
types-grammar, ch4: filling out discussion of relational comparision operations
1 parent f0e7f61 commit 48e94e8

File tree

3 files changed

+77
-4
lines changed

3 files changed

+77
-4
lines changed

types-grammar/ch1.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,10 @@ I'm not going to cover it exhaustively, but I think a brief primer on how number
694694
695695
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.
696696
697+
| NOTE: |
698+
| :--- |
699+
| Since only 52 of the 64 bits are actually used to represent the base value, `number` doesn't actually have `2^64` values in it. According to the specification for the `number` type[^NumberType], the number of values is precisely `2^64 - 2^53 + 3`, or about 18 quintillion, split about evenly between positive and negative numbers. |
700+
697701
These bits are arranged left-to-right, as so (S = Sign Bit, E = Exponent Bit, M = Mantissa Bit):
698702
699703
```js
@@ -881,7 +885,9 @@ myAge; // NaN
881885
882886
All such invalid operations (mathematical or coercive/numeric) produce the special `number` value called `NaN`.
883887
884-
The historical root of "NaN" (including from the IEEE-754[^IEEE754] specification) is as an acronym for "Not a Number". Unfortunately, that meaning produces confusion, since `NaN` is *absolutely* a `number`.
888+
The historical root of "NaN" (from the IEEE-754[^IEEE754] specification) is as an acronym for "Not a Number". Technically, there are about 9 quadrillion values in the 64-bit IEEE-754 number space designated as "NaN", but JS treats all of them indistinguishably as the single `NaN` value.
889+
890+
Unfortunately, that *not a number* meaning produces confusion, since `NaN` is *absolutely* a `number`.
885891
886892
| TIP: |
887893
| :--- |
@@ -1111,3 +1117,5 @@ Before we move on to discussing JS's built-in object value type, we want to take
11111117
[^UTFUCS]: "JavaScript’s internal character encoding: UCS-2 or UTF-16?"; Mathias Bynens; January 20 2012; https://mathiasbynens.be/notes/javascript-encoding ; Accessed July 2022
11121118
11131119
[^IEEE754]: "IEEE-754"; https://en.wikipedia.org/wiki/IEEE_754 ; Accessed July 2022
1120+
1121+
[^NumberType]: "6.1.6.1 The Number Type", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-ecmascript-language-types-number-type ; Accessed August 2022

types-grammar/ch2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ The `<=` (less-than-or-equal) and `>=` (greater-than-or-equal) operators are eff
441441

442442
| NOTE: |
443443
| :--- |
444-
| Here's an interesting bit of specification nuance: JS doesn't actually define the `>` and `>=` relational comparisons independently. Instead, it defines them as the negation of their complement counterparts. So `x > y` is treated by JS as `!(x <= y)`, and `x >= y` is treated by JS as `!(x < y)`. So JS only needs to specify how `<`, `<=`, and `!` work, and thus gets `>` and `>=` for free! |
444+
| Here's an interesting bit of specification nuance: JS doesn't actually define the underlying greater-than (for `>`) or greater-than-or-equal (for `>=`) operations. Instead, it defines them by reversing the arguments to their *less-than* complement counterparts. So `x > y` is treated by JS essentially as `y <= x`, and `x >= y` is treated by JS essentially as `y < x`. So JS only needs to specify how `<` and `==` work, and thus gets `>` and `>=` for free! |
445445

446446
##### Locale-Aware Relational Comparisons
447447

types-grammar/ch4.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ Different from `SameValue()` and its variations, the specification also defines
270270

271271
The `IsStrictlyEqual()` operation immediately returns `false` if the value-types being compared are different.
272272

273-
If the value-types are the same, `IsStrictlyEqual()` delegates to sub-operations for comparing `number` or `bigint` values. You might logically expect these delegated sub-operations to be the aforementioned numeric-specialized `SameValue()` / `SameValueZero()` operations. However, `IsStrictlyEqual()` instead delegates to `Number:equal()`[^NumberEqual] or `BigInt:equal()`[^BigIntEqual].
273+
If the value-types are the same, `IsStrictlyEqual()` delegates to sub-operations for comparing `number` or `bigint` values. [^NumericAbstractOps] You might logically expect these delegated sub-operations to be the aforementioned numeric-specialized `SameValue()` / `SameValueZero()` operations. However, `IsStrictlyEqual()` instead delegates to `Number:equal()`[^NumberEqual] or `BigInt:equal()`[^BigIntEqual].
274274

275275
The difference between `Number:SameValue()` and `Number:equal()` is that the latter defines corner cases for `0` vs `-0` comparison:
276276

@@ -319,7 +319,66 @@ Moreover, if/once the types are the same, both operations are identical -- `IsLo
319319

320320
### Relational Comparison
321321

322-
// TODO
322+
When values are compared relationally -- that is, is one value "less than" another? -- there's one specific abstract operation that is invoked: `IsLessThan()`. [^LessThan]
323+
324+
```
325+
// IsLessThan() is abstract
326+
327+
IsLessThan(1,2, /*LeftFirst=*/ true ); // true
328+
```
329+
330+
There is no `IsGreaterThan()` operation; instead, the first two arguments to `IsLessThan()` can be reversed to accomplish a "greater than" comparison. To preserve left-to-right evaluation semantics (in the case of nuanced side-effects), `isLessThan()` also takes a third argument (`LeftFirst`); if `false`, this indicates a comparison was reversed and the second parameter should be evaluated before the first.
331+
332+
```
333+
IsLessThan(1,2, /*LeftFirst=*/ true ); // true
334+
335+
// equivalent of a fictional "IsGreaterThan()"
336+
IsLessThan(2,1, /*LeftFirst=*/ false ); // false
337+
```
338+
339+
Similar to `IsLooselyEqual()`, the `IsLessThan()` operation is *coercive*, meaning that it first ensures that the value-types of its two values match, and prefers numeric comparisons. There is no `IsStrictLessThan()` for non-coercive relational comparison.
340+
341+
As an example of coercive relational comparison, if the type of one value is `string` and the type of the other is `bigint`, the `string` is coerced to a `bigint` with the aforementioned `StringToBigInt()` operation. Once the types are the same, `IsLessThan()` proceeds as described in the following sections.
342+
343+
#### String Comparison
344+
345+
When both value are type `string`, `IsLessThan()` checks to see if the lefthand value is a prefix (the first *n* characters[^StringPrefix]) of the righthand; if so, `true` is returned.
346+
347+
If neither string is a prefix of the other, the first character position (start-to-end direction, not left-to-right) that's different between the two strings, is compared for their respective code-unit (numeric) values; the result is then returned.
348+
349+
Generally, code-units follow intuitive lexicographic (aka, dictionary) order:
350+
351+
```
352+
IsLessThan("a","b", /*LeftFirst=*/ true ); // true
353+
```
354+
355+
Even digits are treated as characters (not numbers):
356+
357+
```
358+
IsLessThan("101","12", /*LeftFirst=*/ true ); // true
359+
```
360+
361+
There's even a bit of embedded *humor* in the unicode code-unit ordering:
362+
363+
```
364+
IsLessThan("🐔","🥚", /*LeftFirst=*/ true ); // true
365+
```
366+
367+
At least now we've answered the age old question of *which comes first*?!
368+
369+
#### Numeric Comparison
370+
371+
For numeric comparisons, `IsLessThan()` defers to either the `Number:lessThan()` or `BigInt.lessThan()` operation[^NumericAbstractOps], respectively:
372+
373+
```
374+
IsLessThan(41,42, /*LeftFirst=*/ true ); // true
375+
376+
IsLessThan(-0,0, /*LeftFirst=*/ true ); // false
377+
378+
IsLessThan(NaN,1 /*LeftFirst=*/ true ); // false
379+
380+
IsLessThan(41n,42n, /*LeftFirst=*/ true ); // true
381+
```
323382

324383
[^AbstractOperations]: "7.1 Type Conversion", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-type-conversion ; Accessed August 2022
325384

@@ -347,6 +406,12 @@ Moreover, if/once the types are the same, both operations are identical -- `IsLo
347406

348407
[^LooseEquality]: "7.2.15 IsLooselyEqual(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-islooselyequal ; Accessed August 2022
349408

409+
[^NumericAbstractOps]: "6.1.6 Numeric Types", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-numeric-types ; Accessed August 2022
410+
350411
[^NumberEqual]: "6.1.6.1.13 Number:equal(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-numeric-types-number-equal ; Accessed August 2022
351412

352413
[^BigIntEqual]: "6.1.6.2.13 BigInt:equal(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-numeric-types-bigint-equal ; Accessed August 2022
414+
415+
[^LessThan]: "7.2.14 IsLessThan(x,y,LeftFirst)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-islessthan ; Accessed August 2022
416+
417+
[^StringPrefix]: "7.2.9 IsStringPrefix(p,q)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-isstringprefix ; Accessed August 2022

0 commit comments

Comments
 (0)