You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: types-grammar/ch2.md
+30-7Lines changed: 30 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -671,15 +671,17 @@ The temptation to make fun of JS for `0.1 + 0.2 !== 0.3` is strong, I know. But
671
671
| :--- |
672
672
| Pretty much all programmers need to be aware of IEEE-754 and make sure they are careful about these kinds of gotchas. It's somewhat amazing, in a disappointing way, how few of them have any idea how IEEE-754 works. If you've taken your time reading and understanding these concepts so far, you're now in that rare tiny percentage who actually put in the effort to understand the numbers in their programs! |
673
673
674
-
One way to work around such floating-point imprecision is this *very small*`number` value:
674
+
#### Epsilon Threshold
675
+
676
+
A common piece of advice to work around such floating-point imprecision uses this *very small*`number` value defined by JS:
675
677
676
678
```js
677
679
Number.EPSILON; // 2.220446049250313e-16
678
680
```
679
681
680
-
*Epsilon* is defined as the smallest difference JS can represent between `1` and the next value greater than `1`. While this value is implementation/platform dependent, it's typically about `2.2E16`, or `2^-52`. This value is the maximum amount of floating-point representation error (as discussed earlier), so it represents the threshold above which two values are *actually* different rather just skewed by floating-point error.
682
+
*Epsilon* is the smallest difference JS can represent between `1` and the next value greater than `1`. While this value is technically implementation/platform dependent, it's generally about `2.2E-16`, or `2^-52`.
681
683
682
-
Thus, `Number.EPSILON`can used as a *very small* tolerance value to ensure number comparisons are *safe*:
684
+
To those not paying close enough attention to the details here -- including my past self! -- it's generally assumed that any skew in floating point precision from a single operation should never be greater than `Number.EPSILON`. Thus, in theory, we can use `Number.EPSILON`as a *very small* tolerance value to ensure number equality comparisons are *safe*:
683
685
684
686
```js
685
687
functionsafeNumberEquals(a,b) {
@@ -693,17 +695,36 @@ point3b = 0.3;
693
695
safeNumberEquals(point3a,point3b); // true
694
696
```
695
697
696
-
Since JS cannot represent a difference between two values smaller than this `Number.EPSILON`, it should be safe to treat any two number values as "equal" (indistinguishable in JS, anyway) if their difference is less than `Number.EPSILON`.
697
-
698
698
| WARNING: |
699
699
| :--- |
700
-
| If your program needs to deal with smaller values than `2^-52`, or more specifically, smaller differences between values, you should absolutely *not use* the JS `number` value-type. There are decimal-emulation libraries that can offer arbitrary (small or large) precision. Or pick a different language than JS. |
700
+
| In the first edition "Types & Grammar" book, I indeed recommended exactly this approach. I was wrong. I should have researched the topic more closely. |
701
+
702
+
But, it turns out, this approach isn't safe at all:
703
+
704
+
```js
705
+
point3a =10.1+0.2;
706
+
point3b =10.3;
707
+
708
+
safeNumberEquals(point3a,point3b); // false :(
709
+
```
710
+
711
+
Well... that's a bummer!
712
+
713
+
Unfortunately, `Number.EPSILON` only works as a "safely equal" error threshold for certain small numbers/operations, and in other cases, it's far too small, and yields false negatives.
714
+
715
+
You could scale `Number.EPSILON` by some factor to produce a larger threshold that avoids false negatives but still filters out all the floating point skew in your program. But what factor to use is entirely a manual judgement call based on what magnitude of values, and operations on them, your program will entail. There's no automatic way to compute a reliable, universal threshold.
716
+
717
+
Unless you really know what you're doing, you should just *not* use this `Number.EPSILON` threshold approach at all.
718
+
719
+
| TIP: |
720
+
| :--- |
721
+
| If you'd like to read more details and solid advice on this topic, I highly recommend reading this post. [^EpsilonBad] But if we can't use `Number.EPSILON` to avoid the perils of floating-point skew, what do we do? If you can avoid floating-point altogether by scaling all your numbers up so they're all whole number integers (or bigints) while performing math, do so. Only deal with decimal values when you have to output/represent a final value after all the math is done. If that's not possible/practical, use an arbitrary precision decimal emulation library and avoid `number` values entirely. Or do your math in another external programming environment that's not based on IEEE-754. |
701
722
702
723
### Numeric Comparison
703
724
704
725
Like strings, number values can be compared (for both equality and relational ordering) using the same operators.
705
726
706
-
Remember that no matter what form the number value takes when being specified as a literal (base-10, octal, hexadecimal, exponential, etc), the underlying value stored is what will be compared. Also keep in mind the floating point imprecision issues discussed in the previous section, as the comparisons will be sensitive to the exact binary contents, even if the difference between two numbers is much smaller than the `Number.EPSILON` threshold.
727
+
Remember that no matter what form the number value takes when being specified as a literal (base-10, octal, hexadecimal, exponential, etc), the underlying value stored is what will be compared. Also keep in mind the floating point imprecision issues discussed in the previous section, as the comparisons will be sensitive to the exact binary contents.
707
728
708
729
#### Numeric Equality
709
730
@@ -1046,3 +1067,5 @@ The story doesn't end here, though. Far from it! In the next chapter, we'll turn
1046
1067
[^StrictEquality]: "7.2.16 IsStrictlyEqual(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-isstrictlyequal ; Accessed August 2022
1047
1068
1048
1069
[^LooseEquality]: "7.2.15 IsLooselyEqual(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-islooselyequal ; Accessed August 2022
1070
+
1071
+
[^EpsilonBad]: "PLEASE don't follow the code recipe in the accepted answer", Stack Overflow; Daniel Scott; July 2019; https://stackoverflow.com/a/56967003/228852 ; Accessed August 2022
0 commit comments