0.1 + 0.2 !== 0.3; // output: true
parseInt('2/888') !== parseInt(2/888); // output: true
[100,1, 2, ].sort(); // output: [1, 100, 2]
The object data type is the only non-primitive data type. It includes dates, objects, and arrays, etc.
- Infinity, -Infinity, 0
- 0x31333131 // hexadecimal
- 0232 // octal
- 0b1111 // binary
- 1.2e-1 // scientific notation
NaN is also a number! That sounds counterintuitive, yet it is true. To verify
NaN is a number, enter
typeof NaN in a terminal and press enter. The output is
Not everything that looks like a number is a number. Any String is not a number even when it looks like one. For example
‘1’ is not a number because
typeof '1' is
A BigInt is another Number lookalike; it looks like an integer with ending n. For example
What about floating point representation? Some programming languages such as Apex, C, and C++have the concept of int, in addition to float and double. Stored differently from floating point, examples of ints would include
Let’s go over the three parts one by one
- Signed bit: 0 for positive, 1 for negative
- Exponent: how large or small the number is. A multiplier for the fraction
- Significand/base/fraction: used for more precise numbers. These bits represent increasing negative powers of 2
Let’s see a few floating point representations for an overview of how it works.
0’s representation is the most minimalist and the smallest binary possible. For negative numbers the signed bit will be 1.
For 1, notice the exponent bits go up to 011 1111 1111. In base 10 that binary is 1023. 1023 is important because if the exponent is smaller than that the number represented is between absolute one and zero. If the exponent is larger than that, the number represented is larger than 1.
For -1, notice the representation is identical to that of positive 1, except for the flipped signed bit.
For simplicity let’s go back to positive numbers. Take 1’s representation and increment the exponent by one to get 2. Notice the larger the exponent, the larger the number.
What about floats? 1.5 is a sum of 1 + 0.5, so its representation reflects that. Because significands are increasingly negative powers of 2, the first bit in the significand represents 1/2, and is flipped to 1.
1.75 is a sum of 1 + 0.5 + 0.25, so its representation reflects that. Note that the second bit in the significand represents 1/4, and is flipped to 1.
How does one represent
Infinity with a finite number of bits? Since
Infinity is a super large number, it makes sense to flip all the 1’s in the exponent to 1.
What is beyond Infinity? If you enter
Infinity + 1 you get
NaN! Here is the unrepresentable number represented just like any other number. Notice its representation is
Infinity + 1/2.
How does one represent the largest safe integer? See below. Notice that all of the bits in the significand are flipped to 1, and the floating point is at the end of the 64 bit register.
What happens when we increment the
NUMBER.MAX_SAFE_INTEGER by 1? the floating point is floating off of the 64 bits, a clear indicator that the number is unreliably represented.
9007199254740992 is unreliably represented because both itself and
The representation will silently fail for very small or very large numbers, because those numbers need more than 64 bits to be accurately represented. Their 64 bit representation is unreliable and potentially inaccurate.
There is a safe range to represent integers: Integers from
-Math.pow(2, 53) + 1 to
Math.pow(2, 53) - 1 inclusive have 1:1 mapping between the number and its representation. Within this range, the integer is accurately represented. When outside of this range, consider using BigInt to accurately store integers.
To test out whether
yourNumber is within the safe range, use
Number.isSafeInteger(yourNumber) . The method outputs yes for integers smaller or equal to
Number.MAX_SAFE_INTEGER, and no for larger integers and floats.
Unfortunately, there is no equivalent method to test the safeness of floats. Also you cannot use BigInt to represent floats, since BigInt only represents integers.
The out of the box
Array.prototype.sort(optionalFunction) is simple: it sorts the elements in increasing order and modifies the underlying array. It sorts a string array, but does it sort a numbers array?
For example what is the output of
const arr = [100, 1, 2]; arr.sort(); If it was sorted in ascending order we can expect
[1, 2, 100]; However the result is different!
The result is unsorted and differs from the original! What is going on? According to the official EMA spec on sort, since we omitted the comparator function, each element in the array is converted to a string then compared using the Unicode order. That’s how we got the
[1, 100, 2] result. Lesson learned! Always pass in a comparator function when dealing with number arrays. More details in the docs.
parseInt is deceptively simple. Put in a string or number and get an integer out, right? A basic example of it working is
parseInt('2'); // outputs 2
Omitting the radix
Let’s start with the optional second parameter. What if you omit the radix (aka the base)? In other words, would these outputs be identical?
The only difference is that the second code snippet has 10 as the radix. If you think the default radix is base
10, then the results should be the same. But they differ! What is going on?
In the first code snippet, parseInt looks at the string and deduces that the underlying number is hexadecimal, since the string starts with
32 in hexadecimal is
3 * 16 + 2, parseInt returns
50. For the second example, parseInt has the same string input, but
x is not in base 10, so everything from
x onwards is discarded. Hence the result is
Since the results differ, supply the radix to avoid surprises.
String vs number
Moving onto another parseInt quirk: does parseInt treat the string input and number input equally? One might assume that since parseInt accepts both string and number, that it should treat them equally. So
parseInt('2/5556789', 10) should have the same output as
Again the results differ. Let’s deconstruct what happened here.
In the first code snippet, parseInt looks at the string
'2/5556789'. Because the
‘/’ character is not in base 10, all characters from there onwards are discarded, and
2 is returned. In the second code snippet, the first parameter is a number. The scientific notation for this number is
3.5992009054149796e-7, since large and small numbers have a tendency to be converted to scientific notation. parseInt correctly parses out 3 from that.
Since the results differ from string to number, use parseInt with strings and avoid passing in numbers to it. To get integers from numbers, use
Math.round(number) for consistent and predictable results. In our example,
Math.round(2/5556789) correctly returns
Now you know everything about numbers! Take the quiz and see how well you do!
Warning: the quiz might not be as easy as you think. Feel free to study the resources below before taking the test 🙂
Wikipedia: Double precision IEEE 754 floating point format
What every computer scientist should know about floating-point arithmetic
IEEE 754 visualization
BigInt docs from V8 blog
ParseInt MDN docs
ParseInt() doesn’t always correctly convert to integer
Quiz source code