Data type except object which is immutable.
There are 6 types in primitive types. Boolean type represents the logical entitiy which is true or false. Null type is just null. Undefined type represents when variable has not been assigned a value. Symbol type is an unique value which can be used as key of an object property. String type represents the textual data. BigInt type represent numeric value that exceeds safe integer limit for Number type.
Number type represents all numeric value; integers, floating-point number, +Infinity , -Infinity, NaN. Values are range from -(2^53-1) ~ (2^53-1) represented in 64 bit IEEE(IEEE 754 Double Precision) format.
Through this example, I will illustrate how number is stored and evaluated in Javascript.
Why 0.1
+ 0.2
differs from 0.3
?
A normalized form is a number which is represented as a scientific notation with
ex) 1.1 x 10^(-2), 1.01 x 2^(-2)
In binary base, normalized format always has 1 before radix point.
There are two types; single precision and double precision. As javascript uses double precision, the total bits are 64, sign takes 1bit, exponent takes 11 bits, and significant takes 52bits.
sign
if number is positive, 0 and negative, 1.
exponent
Exponent is stored in offset binary format. Offset binary format works in following steps.
1) Find offset K which, for n bits.
2) add K to given number
3) convert it to binary
Advantage compare to two's component is that we can compare number using lexicographical order.
Ex) convert 3 in 4 bits using offset binary format.
1. K = 7
2. 3+7 = 10
3. 10 = 1010_(2)
significant
As we only consider binary normalized form, 1 always exists before radix point. So we ignore the most front 1
.
It's better to show in example.
Ex) convert 0.375 in binary form.
-> 0.375_(10) = 0.011_(2) = 0 X 2^-1 + 1 X 2^-2 + 1 X 2^-3
first transform to binary
0.1 X 2 = 0 + 0.2
0.2 X 2 = 0 + 0.4
0.4 x 2 = 0 + 0.8
0.8 x 2 = 1 + 0.6
0.6 x 2 = 1 + 0.2
0.2 x 2 = 0 + 0.4
...
-> 0.1_(10) = 0.0001100110011..._(2) = _(2)
represent into normalized scientific notation
0.0001100110011...
= 1.1001100110011..._(2) x 2^(-4)
= x 2^(-4)
transform to IEEE 754 form
exponent
K = 2^(11-1) - 1 = 1023, as exponent is -4, -4 + 1023 = 1019
1019_(10) = 01111111011_(2)
significant
ignore 1
before readix
-> 100110011001...
As number exceed 52bits, we should round number.
-> 10011001...10011010 (52bits)
... in total 0.1 =
0 01111111011 10011001...10011010_(2)
Using same method, we can calculate 0.2, which is
0 01111111100 10011001...10011010_(2)
We add these number in a scientific notation form. With all these procedures, we have rounded number 3 times in total. However, if we just transform 0.3 into binary IEEE 754 form, we just round 1 time. So, this is why there's difference between two values.
Largest integer n such that n and n+1 are both exactly representable as Number value.
Number.MAX_SAFE_INTEGER = 9007199254740991 =
But, it's not the largest integer that can be represented.
Number.MAX_SAFE_INTEGER = 9007199254740992 = 1111...111(53bits)
= 1.111...11(52bits) x
= 0 10000110011 1111...1111(52bits)
We used all places in significant part. Thats why it's the largest value that represented properly.
Straight to the point, only even integer beyond Number.MAX_SAFE_INTEGER can be represented. Odd numbers are rounded to the nearest even number. Why?
Number.MAX_SAFE_INTEGER+1 = 9007199254740992 = 1.111...11(52bits) x + 1
= 1.000..00(52bits) x
This is okay. However, as the significant part is full with 52bits, the only way to represent bigger number is using exponent part. Using exponent part means that we add number multiplied by 2.
For example,
1.0000...1(52bits) x == 1000...0010_(2) (53bits) = 9007199254740994_(10)
NaN and Infinity are both expressed in IEEE form. NaN has exponent part full of 1's and significant part doesn't matters except it is all 0's. This is why we get
console.log(NaN == NaN) // false
Because, NaN is not fixed number.
Infinity has exponent part full of 1's and all 0's in significant part.
As primitive data types are not object, We cannot access its properties or methods. However, it is possible.
name = 'John';
name.length; // 4
name.indexOf('J'); // 0
Why does this happens?
If we treat primitve value like an object(access to method), Javascript automatically creats a wrapper object that wrap this value as an object. This object is used for while and discarded right away. This is called Auto-boxing.
Wrapper objects for number values are instances of Number, string values are wrapped by instances of String and the type for a boolean’s wrapper is Boolean. As you can see, null
and undefined
cannot be wrapped.
typeof "abc"; //"string"
typeof String("abc"); //"string"
typeof new String("abc"); //"object"
typeof (new String("abc")).valueOf(); //"string"
Also, we can store wrapper object by assigning it to a variable.
const pet = new String("dog")
typeof pet; // "object"
pet === "dog"; // false