[JavaScript] base conversion & bit operation, what do you know?

Posted by pinochio on Sat, 22 Feb 2020 09:46:22 +0100

Preface

Base and bit operations are rarely touched in general code, but this does not mean that we can not learn it. As a programmer, these are the basics. If you haven't learned this knowledge, don't panic. The next knowledge won't be very difficult. In this article, you will learn:

  • Binary conversion
  • Bitwise operator
  • Javascript base conversion
  • Manual realization of base conversion

Binary conversion

The following uses the common decimal and binary conversion as an example, and the conversion of other bases is the same and the same. You can think about it yourself if you are interested.

Decimal to binary

When counting according to the rule of "every decimal one", every ten identical units form a higher unit adjacent to it. This counting method is called the decimal counting method, which is called the decimal system for short. This is our most commonly used counting method.

integer

Integers are converted to binary using "divide by two, arrange in reverse order". The following is an example of 18 conversion to binary:

//Two out of two
18 / 2 = 9...0
 9 / 2 = 4...1
 4 / 2 = 2...0
 2 / 2 = 1...0
 1 / 2 = 0...1

//Reverse order
10010

In this way, the binary representation of 18 can be obtained by arranging the remainder in reverse order

decimal

Decimals are "multiplied by two, rounded, and arranged in order". Due to different methods, they need to be calculated separately. Here is an example of converting 16.125 to binary:

16 / 2 = 8...0
 8 / 2 = 4...0
 4 / 2 = 2...0
 2 / 2 = 1...0
 1 / 2 = 0...1

0.125 * 2 = 0.25
0.25 * 2  = 0.5
0.5  * 2  = 1

10000.001

The result of decimal multiplication is arranged in the order of integers to get the binary representation of decimal places

Binary to decimal

When counting according to the rule of "every two in one", every two identical units form a higher unit adjacent to it. This counting method is called binary counting method, or binary for short. When binary counting is used, only two independent symbols "0" and "1" are used.

integer

Integers use "add by weight" method, that is, binary numbers are first written as weighted coefficient expansion, and then sum according to decimal addition rules. The following is an example of 101010 converting decimal:

2^5 2^4 2^3 2^2 2^1 2^0   
 1   0   1   0   1   0
------------------------
32 + 0 + 8 + 0 + 2 + 0 = 42

The number on the right is the 0 power of 2, the 1 power of 2, the 2 power of 2... Just take the result with the number of digits as 1, and add them to get the decimal system.

decimal

10110.11 decimals:

2^4 2^3 2^2 2^1 2^0 2^-1 2^-2
 1   0   1   1   0 .  1   1
-------------------------------
16 + 0 + 4 + 2 + 0 + 0.5 + 0.25 = 22.75

Bitwise operator

Bitwise operators treat their operands as a 32-bit sequence of bits (consisting of 0 and 1). The first 31 bits represent the value of an integer, the 32nd bit represents the symbol of an integer, the 0 represents a positive number, and the 1 represents a negative number. For example, the decimal number 18 is 10010 in binary. Bitwise operators operate on the binary form of numbers, but the return value is still a standard JavaScript value.

Bitwise AND

For each bit, only when the corresponding bit of two operands is 1, the result is 1, otherwise 0.

Usage: A & B.

     9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 & 9 (base 10) = 00000000000000000000000000001000 (base 2) = 8 (base 10)

When judging a digital parity, you can use a & 1

function assert(n) {
    return n & 1 ? "Odd number" : "Even numbers"
}
assert(3) // Odd number

Because the last bit of odd binary is 1, and the last bit of 1 is 1, the result is 1 through the & operator

Bitwise OR

For each bit, when there is at least one bit corresponding to two operands, the result is 1, otherwise 0.

Usage: a | b

     9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 | 9 (base 10) = 00000000000000000000000000001111 (base 2) = 15 (base 10)

To round down a floating-point number to an integer, use a | 0

12.1 | 0 // 12
12.9 | 0 // 12

XOR (XOR)

For each bit, when there is only one 1 corresponding to two operands, the result is 1, otherwise 0.

Usage: a ^ b

     9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)

Bitwise NOT

Reverse the bit of the operand, i.e. 0 becomes 1, 1 becomes 0.

Usage: ~ a

 9 (base 10) = 00000000000000000000000000001001 (base 2)
               --------------------------------
~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)

The floating-point number can be rounded down to an integer through two reverse operations

~~16.125 // 16
~~16.725 // 16

Left shift

Move the binary form of a to B (< 32) bit to the left, and fill with 0 to the right.

Usage: a < B

     9 (base 10): 00000000000000000000000000001001 (base 2)
                  --------------------------------
9 << 2 (base 10): 00000000000000000000000000100100 (base 2) = 36 (base 10)

Moving one bit to the left is equivalent to multiplying 2 on the basis of the original number. Using this feature, the n-power of 2 is realized:

function power(n) {
    return 1 << n
}
power(3) // 8

Signed shift right

Move the binary representation of a to the right by B (< 32) bits, and discard the moved bits.

Usage: a > > b

     9 (base 10): 00000000000000000000000000001001 (base 2)
                  --------------------------------
9 >> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)

In contrast, - 9 > > 2 gets - 3 because the symbol is preserved.

     -9 (base 10): 11111111111111111111111111110111 (base 2)
                   --------------------------------
-9 >> 2 (base 10): 11111111111111111111111111111101 (base 2) = -3 (base 10)

Opposite to the left shift, shift one digit to the right based on the original number divided by 2

64 >> 1 // 32

unsigned right shift

Move the binary representation of a to the right by B (< 32), discard the moved bits, and fill the left with 0.

Usage: a > > > b

For non negative numbers, 9 > > 2 and 9 > > 2 are the same results

      9 (base 10): 00000000000000000000000000001001 (base 2)
                   --------------------------------
9 >>> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)

For negative numbers, the result is very different, because > > > does not keep the sign, when the negative number moves to the right without sign, it will be filled with 0

      -9 (base 10): 11111111111111111111111111110111 (base 2)
                    --------------------------------
-9 >>> 2 (base 10): 00111111111111111111111111111101 (base 2) = 1073741821 (base 10)

You can use unsigned right shift to determine the positive and negative of a number

function isPos(n) {
    return (n === (n >>> 0)) ? true : false;
}

isPos(-1); // false
isPos(1); // true

Although - 1 > > 0 will not shift right, the binary code of - 1 has become a positive binary code, and the result of - 1 > > 0 is 4294967295

Javascript base conversion

toString

toString is often used to convert a variable into a string, or to determine the type of a variable, such as:

let arr = []
Object.prototype.toString.call(arr) // [object Array]

You should not have thought that toString can be used for base conversion. Please see the following example:

(18).toString(2)  // 10010(base 2)
(18).toString(8)  // 22 (base 8)
(18).toString(16) // 12 (base 16)

Parameter specifies the cardinality of the number, which is an integer between 2 and 36. If this parameter is omitted, cardinality 10 is used. This parameter can be understood as the converted decimal representation.

parseInt

parseInt is often used for rounding numbers. It can also pass in parameters for decimal conversion. See the following example:

parseInt(10010, 2) // 18 (base 10)
parseInt(22, 8)    // 18 (base 10)
parseInt(12, 16)   // 18 (base 10)

The second parameter represents the cardinality of the number to be resolved, which is between 2 and 36. If you omit the parameter or its value is 0, the number is resolved on a 10-based basis. If the parameter is less than 2 or greater than 36, parseInt returns NaN.

Remember an interview question like this:

// Q: results returned
[1, 2, 3].map(paseInt)

Next, let's take a step-by-step look at what happened in the process?

parseInt(1, 0) // When the cardinality is 0, take 10 as the cardinality to analyze, and the result is 1
parseInt(2, 1) // The cardinality does not meet the range of 2 ~ 36, and the result is NaN
parseInt(3, 2) // Here, the radix is 2, but 3 is obviously not a binary representation, so the result is NaN

//The result is
[1, NaN, NaN]

Manual realization of base conversion

Although JavaScript has built-in function of base conversion for us, manual implementation of base conversion is conducive to our understanding of the process and improving the logic ability. It's also a good practice for beginners. The following is a simple implementation of the conversion of non negative integers.

Decimal to binary

Based on the idea of "taking the rest from the two"

function toBinary(value) {
    if (isNaN(Number(value))) {
        throw `${value} is not a number` 
    }
    let bits = []
    while (value >= 1) {
        bits.unshift(value % 2)
        value = Math.floor(value / 2)
    }
    return bits.join('')
}

Use

toBinary(36) // 100100
toBinary(12) // 1100

Binary to decimal

Based on the idea of "adding by weight"

function toDecimal(value) {
    let bits = value.toString().split('')
    let res = 0
    while (bits.length) {
        let bit = bits.shift()
        if (bit == 1) {
            // **Is a power operator, for example: 2 * * 3 is 8
            res += 2 ** bits.length
        }
    }
    return res
}

Use

toDecimal(10011) // 19
toDecimal(11111) // 33

Written in the end

This paper introduces the knowledge of base and bit operation for you, aiming to learn from the past. We just need to know about it, because it's really less used in development, at least I've only used ~ ~ to round. The rounding operation similar to ~ ~ is better to use as little as possible. For other developers, it may affect code readability.

Topics: Javascript less REST