Skip to content

Commit

Permalink
Functions: Add examples for bit functions and operators (#18206)
Browse files Browse the repository at this point in the history
  • Loading branch information
qiancai authored Jul 31, 2024
1 parent 250fff7 commit 550048c
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 8 deletions.
292 changes: 285 additions & 7 deletions functions-and-operators/bit-functions-and-operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,291 @@ TiDB 支持使用 MySQL 8.0 中提供的所有[位函数和操作符](https://de

| 函数和操作符名 | 功能描述 |
| -------------- | ------------------------------------- |
| [`BIT_COUNT()`](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#function_bit-count) | 返回参数二进制表示中为 1 的个数 |
| [&](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-and) | 位与 |
| [~](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-invert) | 按位取反 |
| [\|](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-or) | 位或 |
| [^](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-xor) | 位亦或 |
| [<<](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_left-shift) | 左移 |
| [>>](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_right-shift) | 右移 |
| [`BIT_COUNT()`](#bit_count) | 返回参数二进制表示中为 1 的个数 |
| [`&`](#按位与) | 按位与 |
| [`~`](#按位取反) | 按位取反 |
| [`\|`](#按位或) | 按位或 |
| [`^`](#按位异或) | 按位异或 |
| [`<<`](#左移) | 左移 |
| [`>>`](#右移) | 右移 |

## [`BIT_COUNT()`](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#function_bit-count)

`BIT_COUNT(expr)` 函数返回 `expr` 中为 1 的位数。

```sql
SELECT BIT_COUNT(b'00101001');
```

```
+------------------------+
| BIT_COUNT(b'00101001') |
+------------------------+
| 3 |
+------------------------+
1 row in set (0.00 sec)
```

> **注意:**
>
> 当输入的 `expr` 参数是一个二进制数时,你需要在这个数之前显式地指定 `b`,比如 `b'00101001'`。否则,该函数会将其视为字符串处理而返回不同的结果。例如,`BIT_COUNT('00101001')` 会返回 `7`,因为它会将字符串 `'00101001'` 转换为十进制数 `101001`,并计算 `101001` 的二进制表示 `11000100001010001``1` 的位数。
下面的示例与前面的类似,但使用的参数是十六进制数而非二进制数。`CONV()` 函数用于将 `0x29` 从十六进制转换为二进制,可以看到 `0x29` 等价于二进制的 `00101001`

```sql
SELECT BIT_COUNT(0x29), CONV(0x29,16,2);
```

```
+-----------------+-----------------+
| BIT_COUNT(0x29) | CONV(0x29,16,2) |
+-----------------+-----------------+
| 3 | 101001 |
+-----------------+-----------------+
1 row in set (0.01 sec)
```

`BIT_COUNT(expr)` 函数的一个常见用法是将子网掩码转换为 [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) 表示法。在下面的示例中,子网掩码 `255.255.255.0` 被转换为其 CIDR 表示形式 `24`

```sql
SELECT BIT_COUNT(INET_ATON('255.255.255.0'));
```

```
+---------------------------------------+
| BIT_COUNT(INET_ATON('255.255.255.0')) |
+---------------------------------------+
| 24 |
+---------------------------------------+
1 row in set (0.00 sec)
```

## [`&`(按位与)](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-and)

`&` 操作符用于执行按位与 (bitwise AND) 操作。它会比较两个数中的对应位,如果两个对应位都是 1,则结果中的对应位为 1,否则为 0。

例如,对 `1010``1100` 进行按位与操作会返回 `1000`,因为在这两个数中只有最左边的第一位都是 1。

```
1010
& 1100
----
1000
```

在 SQL 中,可以这样使用 `&` 操作符:

```sql
SELECT CONV(b'1010' & b'1000',10,2);
```

```
+------------------------------+
| CONV(b'1010' & b'1000',10,2) |
+------------------------------+
| 1000 |
+------------------------------+
1 row in set (0.00 sec)
```

你可以将 `&` 操作符与 `INET_NTOA()``INET_ATON()` 函数结合在一起使用,对 IP 地址和网络掩码进行按位与操作,以获取网络地址。这对于判断多个 IP 地址是否属于同一网络非常有用。

在以下示例中,使用掩码 `255.255.255.0` 时,IP 地址 `192.168.1.1``192.168.1.2` 都属于同一网络 `192.168.1.0/24`

```sql
SELECT INET_NTOA(INET_ATON('192.168.1.1') & INET_ATON('255.255.255.0'));
```

```
+------------------------------------------------------------------+
| INET_NTOA(INET_ATON('192.168.1.1') & INET_ATON('255.255.255.0')) |
+------------------------------------------------------------------+
| 192.168.1.0 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
```

```sql
SELECT INET_NTOA(INET_ATON('192.168.1.2') & INET_ATON('255.255.255.0'));
```

```
+------------------------------------------------------------------+
| INET_NTOA(INET_ATON('192.168.1.2') & INET_ATON('255.255.255.0')) |
+------------------------------------------------------------------+
| 192.168.1.0 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
```

## [`~`(按位取反)](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-invert)

`~` 操作符用于对给定的值进行按位取反(bitwise NOT)操作。它会对给定值中的每一位进行取反:0 的位变为 1,1 的位变为 0。

在进行取反操作之前,它会先将给定的值扩展到 64 位。

以二进制数 `1111000011110000` 为例。当扩展到 64 位并进行取反后,其结果如下:

```
Original (16 bits): 1111000011110000
Expanded and inverted (64 bits): 1111111111111111111111111111111111111111111111110000111100001111
```

在 SQL 中,可以这样使用 `~` 操作符:

```sql
SELECT CONV(~ b'1111000011110000',10,2);
+------------------------------------------------------------------+
| CONV(~ b'1111000011110000',10,2) |
+------------------------------------------------------------------+
| 1111111111111111111111111111111111111111111111110000111100001111 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
```

如果需要对取反后的结果再次取反,可以再次应用 `~` 操作符。

```sql
SELECT CONV(~ b'1111111111111111111111111111111111111111111111110000111100001111',10,2);
```

```
+----------------------------------------------------------------------------------+
| CONV(~ b'1111111111111111111111111111111111111111111111110000111100001111',10,2) |
+----------------------------------------------------------------------------------+
| 1111000011110000 |
+----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```

## [`|`(按位或)](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-or)

`|` 操作符用于执行按位或 (bitwise OR) 操作。它会比较两个数中的对应位,如果至少有一个对应位为 1,则结果中的对应位为 1。

例如,对 `1010``1100` 进行按位或操作会返回 `1110`,因为在这两个数的前三位中,至少有一个数的对应位为 1。

```
1010
| 1100
----
1110
```

在 SQL 中,可以这样使用 `|` 操作符:

```sql
SELECT CONV(b'1010' | b'1100',10,2);
```

```
+------------------------------+
| CONV(b'1010' | b'1100',10,2) |
+------------------------------+
| 1110 |
+------------------------------+
1 row in set (0.00 sec)
```

## [`^`(按位异或)](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_bitwise-xor)

`^` 操作符用于执行按位异或 (bitwise XOR) 操作。它会比较两个数中的对应位,如果对应位不同,则结果中的对应位为 1。

例如,对 `1010``1100` 进行按位异或操作会返回 `0110`,因为这两个数中的第二位和第三位都不同。

```
1010
^ 1100
----
0110
```

在 SQL 中,可以这样使用 `^` 操作符:

```sql
SELECT CONV(b'1010' ^ b'1100',10,2);
```

```
+------------------------------+
| CONV(b'1010' ^ b'1100',10,2) |
+------------------------------+
| 110 |
+------------------------------+
1 row in set (0.00 sec)
```

需要注意的是,由于省略了前导零,结果会显示为 `110` 而不是 `0110`

## [`<<`(左移)](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_left-shift)

`<<` 操作符用于执行左移操作。它会将一个数中的所有位向左移动指定的位数,并用零填充右侧空出的位。

例如,下面的语句使用了 `1<<n` 将二进制数 `1` 向左移动了 `n` 位:

```sql
WITH RECURSIVE cte(n) AS (
SELECT 0 AS n
UNION ALL
SELECT 1+n FROM cte WHERE n<10
)
SELECT n,1<<n,LPAD(CONV(1<<n,10,2),11,0) FROM cte;
```

```
+------+------+----------------------------+
| n | 1<<n | LPAD(CONV(1<<n,10,2),11,0) |
+------+------+----------------------------+
| 0 | 1 | 00000000001 |
| 1 | 2 | 00000000010 |
| 2 | 4 | 00000000100 |
| 3 | 8 | 00000001000 |
| 4 | 16 | 00000010000 |
| 5 | 32 | 00000100000 |
| 6 | 64 | 00001000000 |
| 7 | 128 | 00010000000 |
| 8 | 256 | 00100000000 |
| 9 | 512 | 01000000000 |
| 10 | 1024 | 10000000000 |
+------+------+----------------------------+
11 rows in set (0.00 sec)
```

## [`>>`(右移)](https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html#operator_right-shift)

`>>` 操作符用于执行右移操作。它会将数中的所有位向右移动指定的位数,并用零填充左侧空出的位。

例如,下面的语句使用了 `1024>>n` 将数字 `1024`(二进制为 `10000000000`)向右移动了 `n` 位:

```sql
WITH RECURSIVE cte(n) AS (
SELECT 0 AS n
UNION ALL
SELECT n+1 FROM cte WHERE n<11
)
SELECT n,1024>>n,LPAD(CONV(1024>>n,10,2),11,0) FROM cte;
```

```
+------+---------+-------------------------------+
| n | 1024>>n | LPAD(CONV(1024>>n,10,2),11,0) |
+------+---------+-------------------------------+
| 0 | 1024 | 10000000000 |
| 1 | 512 | 01000000000 |
| 2 | 256 | 00100000000 |
| 3 | 128 | 00010000000 |
| 4 | 64 | 00001000000 |
| 5 | 32 | 00000100000 |
| 6 | 16 | 00000010000 |
| 7 | 8 | 00000001000 |
| 8 | 4 | 00000000100 |
| 9 | 2 | 00000000010 |
| 10 | 1 | 00000000001 |
| 11 | 0 | 00000000000 |
+------+---------+-------------------------------+
12 rows in set (0.00 sec)
```

`>>` 操作符还可以用于提取一个大数中的特定部分,例如从 TiDB [TSO](/tso.md) 时间戳中提取 UNIX 时间戳。

## MySQL 兼容性

Expand Down
2 changes: 1 addition & 1 deletion tso.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ FROM_UNIXTIME((@ts >> 18)/1000): 2023-08-27 20:33:41.6870
1 row in set (0.00 sec)
```

`>> 18` 操作表示按位[右移](/functions-and-operators/bit-functions-and-operators.md) 18 位,用于提取物理时间戳。由于物理时间戳以毫秒为单位,与更常见的以秒为单位的 UNIX 时间戳格式不同,因此需要除以 1000 将其转换为与 [`FROM_UNIXTIME()`](/functions-and-operators/date-and-time-functions.md) 兼容的格式。这个转换过程与 `TIDB_PARSE_TSO()` 的功能一致。
`>> 18` 操作表示按位[右移](/functions-and-operators/bit-functions-and-operators.md#右移) 18 位,用于提取物理时间戳。由于物理时间戳以毫秒为单位,与更常见的以秒为单位的 UNIX 时间戳格式不同,因此需要除以 1000 将其转换为与 [`FROM_UNIXTIME()`](/functions-and-operators/date-and-time-functions.md) 兼容的格式。这个转换过程与 `TIDB_PARSE_TSO()` 的功能一致。

你还可以将二进制中的逻辑时间戳 `000000000000000100`(即十进制中的 `4`)提取出来。

Expand Down

0 comments on commit 550048c

Please sign in to comment.