Numbers and bitwise operations overview
Like other languages Javascript deals pretty well with bytes and a lot of developers tend to choose NodeJS as their favourite backend framework.
Here, I propose to review, recap some “binary” concepts about how primitive type “number” are stored, integer, float, double. It helps to understand how certain operations are performed.
I wanted to make my own visual representation of famous IEEE 754 format, so before to do that, I needed to review some basics, yes school is quite far for me too now.
Result is here : http://darul75.github.io/d3-binary-converter/
Fact
Before digging into the ground, always fun to look at this weird result
0.1 + 0.2 // 0.30000000000000004
Ok, but why ? I would summarize by welcome to binary world.
Some recaps signed vs unsigned.
For illustration, we could consider that integers are stored with only one byte (8 bits)
1 binary => 0000 0001
-1 binary => ???? ????
One complement method
“The ones’ complement form of a negative binary number is the bitwise NOT applied to it.”
So decimal 1 number becomes -1 this way
~00000001 → 11111110
Ok so let’s add 2 + (-1)
0000 0010
+1111 1110
---------
1 0000 0000
Result is
0000 0000
And not 1 as expected
0000 0001
Two’s complement method
“Negating a number (whether negative or positive) is done by inverting all the bits and then adding one to that result.”
Play with 1 and try to compute -1.
0000 0001
- Invert all the bits through the number
1111 1110
- Add one
1111 1110
+0000 0001
---------
1111 1111
Verify (-1) + 1 == 0
1111 1111
0000 0001
---------
1 000000000
Verify (-1) + 2 == 1
1111 1111
0000 0010
---------
1 000000001
Good.
Another approach consist in :
- Starting from the right, find the first ‘1’
- Invert all of the bits to the left of that one
1111 1111
// could be resumed like
-2^7+2^6+2^5+2^4+2^3+2^2+2^0 == -128+64+32+16+8+4+2+1
Shifting
Few recaps on shifting operators.
« (Left shift)
“This operator shifts the first operand the specified number of bits to the left.”
var shift = 1;
var n = 5; // 0000 0101
n = n << shift; // 0000 1010
// note, left shifting is equivalent to operation : n * 2^shift
n << shift == n * Math.pow(2, shift);
» (Sign-propagating right shift)
“This operator shifts the first operand the specified number of bits to the right”.
var shift = 1;
var n = 5; // 0000 0101
n = n >> shift; // 0000 0010
»> (Zero-fill right shift)
“This operator shifts the first operand the specified number of bits to the right.”
Number -1 in binary format representation is:
11111111111111111111111111111111
What happened when you shift this number by 0 ?
var n = -1; // 1111.....
n = n >>> 0; // 4294967296
Surprising but not so much at the end as it will simply coerces number to unsigned one.
Number to binary string format
Sometimes it is nice to get binary representation of a number in javascript. By looking at Mozilla API for number, you will see a nice toString([base]) method.
Let’s try using it:
var n = -10; // ....1010
n.toString(2); // "-1010"
“-1010” is not the real representation of your number and a better approach will be to use zero-fill right shift seen before instead.
(n >>> 0).toString(2);
// 11111111111111111111111111110110 => GOOD
function dec2bin(dec){
return (dec >>> 0).toString(2);
}
~ (Bitwise NOT)
“Yields the inverted value (a.k.a. one’s complement)”
var a = 3; // 0000 0011
var notA = ~3 // 1111 1100
Some of you may have seen this bunch of code somewhere.
var string = 'hello world, I am testing !';
if (~string.indexOf('test')) {
// do something
}
// ok weird... normaly would be something like
if (string.indexOf('test') >= 0) {
// do something
}
indexOf() method return -1 in case of non matching result.
Do you remember -1 in binary representation, a nice list of bits 11111111….
Cool so if I use a NOT on -1 I get ??
!11111....
=> 00000....
=> 0 in decimal
=> meaning boolean false by coercion
=> false
Rewrite our example a little.
var string = 'hello world, I am testing !';
var s1 = 'am';
var s2 = 'nothing else matters';
var containsAm = string.indexOf(s1); // === 15
var containsNothingElseMatters = string.indexOf(s2); // === -1
containsAm = ~containsAm; // -16
containsNothingElseMatters = ~containsNothingElseMatters; // 0
// so what happened when in a condition
if (containsAm) {
// ok
console.log('containsAm');
}
if (containsNothingElseMatters) {
// ok
console.log('containsNothingElseMatters');
}
// will output
// > containsAm
Boolean conversion
In previous example, what you need to remember is that in javascript:
- 0 is the same than false
- anything else is true.
You may check by using !! operator.
!!0; // false
!!1; // true
!!18;// true
// but also
!!-18;// true
So when you see something like this in code.
var s = 'wtf';
// same as: s.indexOf('tf') >= 0
if (~s.indexOf('tf')) {
console.log('now you understand what\'s going on');
}
Floating numbers IEEE 754
Our computer is just a serie of 0 and 1 isn’t it ?
And we have seen how some integer numbers (ex: 15) are normalized into a serie of bits.
But how does it work for decimal numbers ? (ex: 15.25)
A decimal number is a number that can be expressed with a fraction where denominator is a power of ten.
55.25 === 5525 / 100 === 5525 / (10^2)
. Ok nice but my computer does not care about power decimal and expects a binary representation instead.
Here comes IEEE 754 Binary Floating Point representation.
I won’t give all details but you can check how it works here
Javascript uses 64 bits representation precision:
- 1 bit is sign
- 11 bits exponent
- 52 bits for the fraction.
Sign
This is the easy part, 1 bit indicates a negative number, and a 0 bit indicates a positive number.
Mantissa
Decimal
Best way to understand how it works consist in considering decimal floating representation approach.
Replay with 55.25 number, such a number decimal floating representation would be:
5,525 x 10 ^2
- sign : positive
- mantissa : 5.525
- exponent: 2
The fractional portion of the mantissa is the sum of each digit multiplied by a power of 10:
.525 = 5/10 + 2/100 + 5/1000
Binary
A binary floating-point number is similar.
For example, in the normalized number +1,1011101 x 2^5
- the sign is positive,
- the mantissa is 1,1011101, (we don’t store first bit..)
- and the exponent is 5.
To come back to a binary representation, we shift the decimal point of mantissa by 5:
1.1011101 => 110111.01
Take integer/decimal parts:
110111 => 2^0 + 2^1 + 2^2 + 2^4 + 2^5 => 55
.01 => 2^-2 => 0.25
// result is expected number : 55.25
Exponent
IEEE Double real exponents are stored as 11-bit unsigned integers with a bias of 1023.
In previous example, exponent of 5 “biased” will become:
5 + 1023 = 1028
And its binary
10000000100
Full representation (double precision)
0 10000000100 1011101000000000000000000000000000000000000000000000
References
Binary addition 1
Article 1
Tests 1
Tags: Javascript