浮点数的存储方式、bf16和fp16的区别

目录

  • 1. 小数的二进制转换
  • 2. 浮点数的二进制转换
  • 3. 浮点数的存储
    • 3.1 以fp32为例
    • 3.2 规约形式与非规约形式
  • 4. 各种类型的浮点数
  • 5. BF16和FP16的区别
  • Ref

1. 小数的二进制转换

十进制小数转换成二进制小数采用「乘2取整,顺序排列」法。具体做法是:用 2 2 2 乘十进制小数,可以得到积,将积的整数部分取出,再用 2 2 2 乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。

然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。

二进制小数转十进制小数则十分简单。我们知道二进制表示整数时,最低位代表 2 2 2 0 0 0 次方,往高位依次是 2 2 2 1 1 1 次方, 2 2 2 次方, 3 3 3 次方。那么对应的,二进制数小数点后面,最高位则是 2 2 2 − 1 -1 1 次方,如下图所示:

因此: ( 0.1101 ) 2 = 1 ⋅ 2 − 1 + 1 ⋅ 2 − 2 + 0 ⋅ 2 − 3 + 1 ⋅ 2 − 4 = ( 0.8125 ) 10 (0.1101)_2=1\cdot2^{-1}+1\cdot2^{-2}+0\cdot2^{-3}+1\cdot2^{-4}=(0.8125)_{10} (0.1101)2=121+122+023+124=(0.8125)10

整数可以使用 2 2 2非负幂次的有限和来表示,但小数就不一定能够用 2 2 2幂次的有限和来表示了,部分情况下可能是无限和。

例如对于小数 0.6 0.6 0.6 而言,它的二进制形式为 0. 1001 ‾ 0.\overline{1001} 0.1001,是一个无限循环小数,循环节是 1001 1001 1001

计算机只能识别 0 0 0 1 1 1,这就是为什么我们需要讨论十进制小数到二进制小数的转换。注意到在上述的例子中, 0.6 0.6 0.6 转换成二进制后是一个无限循环小数,由于计算机内存有限,无法存储无限长的数字序列,因此在实际存储这类数字时,只能存储小数点后的有限位数。这就意味着,无论如何,存储时都必须进行截断或四舍五入处理,而这种操作会引入所谓的「浮点数精度误差」。

很显然,对于这类小数,能够存储的位数越多,相应的精度就越高。这就是为什么 double 类型相比于 float 类型能提供更高精度的原因。

⚠️ 浮点数存储遵循IEEE 754规范。

为加深理解,我们使用Python来实现一遍:

def decimal_to_binary(decimal, max_step=30):
    binary = '0.'
    cnt = 0

    while decimal != 0 and cnt < max_step:
        decimal *= 2
        bit = int(decimal)
        binary += str(bit)
        decimal -= bit
        cnt += 1

    return binary

print(decimal_to_binary(0.8125))
print(decimal_to_binary(0.6))

2. 浮点数的二进制转换

粗略地讲,一个浮点数由其整数部分小数部分组成,在知道小数的进制转换后,我们可以将一个十进制浮点数转换成二进制形式。

10.625 10.625 10.625 为例。只需注意到

10.625 = 10 + 0.625 = 8 + 2 + 0.5 + 0.125 = 2 3 + 2 1 + 2 − 1 + 2 − 3 \begin{aligned} 10.625&=10+0.625=8+2+0.5+0.125 \\ &=2^3+2^1+2^{-1}+2^{-3} \end{aligned} 10.625=10+0.625=8+2+0.5+0.125=23+21+21+23

于是 ( 10.625 ) 10 = ( 1010.101 ) 2 (10.625)_{10}=(1010.101)_2 (10.625)10=(1010.101)2。我们还可以进一步将其表示成 2 2 2 为底的科学记数法 1010.101 = 1.010101 × 2 3 1010.101=1.010101\times 2^3 1010.101=1.010101×23

一些其他的例子:

3.5 → 1.11 × 2 1 0.8125 → 1.101 × 2 − 1 − 0.6 → − 1. 0011 ‾ × 2 − 1 \begin{aligned} 3.5 &\to 1.11\times 2^1 \\ 0.8125 &\to 1.101\times 2^{-1} \\ -0.6&\to -1.\overline{0011}\times 2^{-1} \\ \end{aligned} 3.50.81250.61.11×211.101×211.0011×21

由此可见,任何(正)十进制浮点数表示成二进制科学记数法以后,一定是 1 1 1 点几(尾数)乘以 2 2 2 的多少次方(指数)。对于负数来说,就是负 1 1 1 点几(尾数)乘以 2 2 2 的多少次方(指数)。因此,任意一个二进制浮点数都可以表示成下面的形式:

V = ( − 1 ) s × M × 2 E (1) V=(-1)^s\times M\times 2^E\tag{1} V=(1)s×M×2E(1)

其中 s s s 代表符号位(sign), s = 1 s=1 s=1 说明是负数, s = 0 s=0 s=0 说明是正数。 M M M尾数(mantissa), E E E指数(exponent)。因此存储一个浮点数只需要存储 s , M , E s,M,E s,M,E 这三部分。


一些发现:

  • 尾数 M M M最高位总是 1 1 1,因此实际存储时,只需要存储尾数的小数部分(fraction),即小数点后面的数字。
  • M ∈ [ 1 , 2 ) M\in[1, 2) M[1,2)。这是因为, M M M 最高位是 1 1 1,所以至少是 1.000... 1.000... 1.000...,极端情况下是 1.111... 1.111... 1.111...,即 1 + 1 2 + 1 4 + . . . 1+\frac12+\frac14+... 1+21+41+...,即使尾数达到了 2 2 2,即 10.000... 10.000... 10.000...,也会被重新规范化到 1.000... 1.000... 1.000...,即 1 1 1
  • E ∈ Z E\in\mathbb{Z} EZ,可正可负可为 0 0 0
  • 注意到 M × 2 − 1 ∈ [ 0.5 , 1 ) ,    M × 2 1 ∈ [ 2 , 4 ) M\times 2^{-1}\in[0.5,1),\;M\times 2^1\in[2,4) M×21[0.5,1),M×21[2,4)。因此,在十进制下,任何小于 1 1 1 的数转化成二进制后,其指数一定小于 0 0 0;任何大于等于 2 2 2 的数转化成二进制后,其指数一定大于 0 0 0。进一步可知,对于任意非零十进制浮点数 x x x,将其转化成二进制后,其指数为 ⌊ log ⁡ 2 ∣ x ∣ ⌋ \lfloor \log_2|x|\rfloor log2x
  • 只考虑正浮点数(负浮点数亦然)。由上一条知,所有指数将所有的正浮点数 ( 0 , + ∞ ) (0,+\infty) (0,+) 划分成若干个互不相交的区间: ( 0 , + ∞ ) = ⋃ k = − ∞ + ∞ [ 2 k , 2 k + 1 ) (0,+\infty)=\bigcup_{k=-\infty}^{+\infty}[2^k,2^{k+1}) (0,+)=k=+[2k,2k+1)。设有两个符号相同的十进制浮点数 x 1 , x 2 x_1,x_2 x1,x2,它们在二进制下的指数分别为 k 1 , k 2 k_1,k_2 k1,k2,且 k 1 > k 2 k_1>k_2 k1>k2。若 x 1 , x 2 x_1,x_2 x1,x2 均为正,则 x 1 > x 2 x_1>x_2 x1>x2;若 x 1 , x 2 x_1,x_2 x1,x2 均为负,则 x 1 < x 2 x_1<x_2 x1<x2。这说明我们可以仅靠指数来比较两个浮点数的大小(前提是指数不同)。
  • M M M 的位数通常决定了浮点数能表示的精度。例如对于十进制小数 0.6 0.6 0.6 而言,将其转化成二进制后,能存储的尾数位越多,表达自然越精确。 E E E 的位数则决定了浮点数能够表示的数值范围

3. 浮点数的存储

3.1 以fp32为例

IEEE 754规定的浮点数存储格式如下:

最高位是符号位, 1 1 1 代表负数, 0 0 0 代表正数。接下来的若干位是指数位,指数位之后才是尾数位(不存储最高位 1 1 1)。

以fp32(32-bit floating point,32位浮点数)为例,它拥有 1 1 1 位符号位、 8 8 8 位指数位, 23 23 23 位尾数位。

8 8 8 位有符号整数的取值范围是 [ − 128 , 127 ] [-128,127] [128,127],可以表示 256 256 256 个指数。但实际上,我们前面说过,指数不等时可以仅通过指数来比较两个浮点数的大小。为了简化这个比较手续,考虑采用 8 8 8无符号整数进行存储,这样就能仅通过字典序来比较两个指数的大小了。在该设定下,指数的取值范围变成了 [ 0 , 255 ] [0,255] [0,255]

浮点数中还有三个特殊值 0 , ∞ , NaN 0,\infty,\text{NaN} 0,,NaN。IEEE 754规定,需要留一些位置用来表示这些特殊值: 0 0 0 用来留给 0 0 0 255 255 255 用来留给 ∞ , NaN \infty,\text{NaN} ,NaN。挖掉两个位置后,指数的取值范围变成了 [ 1 , 254 ] [1,254] [1,254]。注意到此时还没有负指数,为了平衡正负指数的个数,这里引入指数偏移量 c = 127 c=127 c=127,于是有

存储指数 − 127 = 实际指数 存储指数-127=实际指数 存储指数127=实际指数

故知实际指数的范围为 [ − 126 , 127 ] [-126,127] [126,127]

🧑‍💻 一般地,设 e e e 为指数位的个数,则 c ≜ 2 e − 1 − 1 c\triangleq 2^{e-1}-1 c2e11。fp32的指数偏移量为 2 8 − 1 − 1 = 127 2^{8-1}-1=127 2811=127

例如, 10.625 10.625 10.625 转化成二进制后指数为 3 3 3,那么用来存储的指数值为 127 + 3 = 130 127+3=130 127+3=130,即 10000010 10000010 10000010。尾数部分只需要存储 010101 010101 010101,因此 10.625 10.625 10.625 用fp32表示即为

0 ⏟ 1 个符号位    10000010 ⏟ 8 个指数位    01010100000000000000000 ⏟ 23 个尾数位 \underbrace{0}_{1个符号位}\;\underbrace{10000010}_{8个指数位}\;\underbrace{01010100000000000000000}_{23个尾数位} 1个符号位 08个指数位 1000001023个尾数位 01010100000000000000000

那反过来该如何操作呢?例如,给定一个fp32格式的浮点数 11000001010011000000000000000000,首先分离出它的符号位、指数位和尾数位:

  • 符号位: 1 1 1
  • 指数位: 10000010 10000010 10000010
  • 尾数位: 10011000000000000000000 10011000000000000000000 10011000000000000000000

由符号位可知这是一个负数,将指数位转换成十进制得到 130 130 130,因此实际指数为 130 − 127 = 3 130-127=3 130127=3。由此可知该浮点数的二进制科学记数法表示为 − 1.10011 × 2 3 - 1.10011\times2^3 1.10011×23,从而

− 1.10011 × 2 3 = − 1100.11 = − ( 2 3 + 2 2 + 2 − 1 + 2 − 2 ) = − 12.75 -1.10011\times2^3=-1100.11=-(2^3+2^2+2^{-1}+2^{-2})=-12.75 1.10011×23=1100.11=(23+22+21+22)=12.75

我们还可以用Python来完成这个互相转化的操作:

import struct

def binary_to_float(binary_str):
    assert len(binary_str) == 32, "only support fp32"

    # 4 bytes, 32 bits.
    decimal_float = int(binary_str, 2).to_bytes(4, 'big')
    return struct.unpack('>f', decimal_float)[0]


def float_to_binary(value):
    packed_float = struct.pack('>f', value)
    binary_str = ''.join(f'{byte:08b}' for byte in packed_float)
    return binary_str


print(binary_to_float('11000001010011000000000000000000'))
print(float_to_binary(-12.75))

3.2 规约形式与非规约形式

回到 ( 1 ) (1) (1) 式,已知 M ∈ [ 1 , 2 ) M\in[1,2) M[1,2),设有 e e e 个指数位,那么存储指数 E ∈ [ 0 , 2 e − 1 ] E\in[0,2^e-1] E[0,2e1]。当 E ∈ [ 1 , 2 e − 2 ] E\in[1, 2^e-2] E[1,2e2] 时,此时 ( 1 ) (1) (1) 式又称规约形式

还是以fp32为例,只考虑正数,在规约形式下

  • 最小值: M = 1 ,   E = 1 − 127 = − 126 M=1,\,E=1-127=-126 M=1,E=1127=126,故 V = 2 − 126 ≈ 1.18 × 1 0 − 38 V=2^{-126}\approx1.18\times10^{-38} V=21261.18×1038
  • 最大值: M = 1 + 2 − 1 + ⋯ + 2 − 23 = 2 − 2 − 23 ,   E = 254 − 127 = 127 M=1+2^{-1}+\cdots+2^{-23}=2-2^{-23},\,E=254-127=127 M=1+21++223=2223,E=254127=127,故 V = ( 2 − 2 − 23 ) ⋅ 2 127 ≈ 3.40 × 1 0 38 V=(2-2^{-23})\cdot2^{127}\approx3.40\times 10^{38} V=(2223)21273.40×1038

很显然,fp32无法表示低于最小值的数,低于最小值的数会被视为 0 0 0(下溢)。同样地,高于最大值的数会被视为 ∞ \infty (上溢)。

更具体地:

  • M = 0 , E = 0 M=0,E=0 M=0,E=0 来表示 0 0 0
  • M = 0 , E = 2 e − 1 M=0,E=2^e-1 M=0,E=2e1 来表示 ∞ \infty
  • M ≠ 0 , E = 2 e − 1 M\neq 0,E=2^e-1 M=0,E=2e1 来表示 NaN \text{NaN} NaN

为了让fp32能表示低于最小值的数(即更接近0的数),并且让从最小规约数向 0 0 0 的过渡更加平滑,非规约数便由此引入。这种设计使得浮点数系统能够更细致地处理接近零的小数值。

我们先思考一下,如何让fp32来表示小于 2 − 126 2^{-126} 2126 的数呢?显然存储指数 E E E 已经达到了最小值 1 1 1,而 M ∈ [ 1 , 2 ) M\in[1,2) M[1,2),将 M M M 的隐含最高位由 1 1 1 改为 0 0 0,从而 M ∈ [ 0 , 1 ) M\in[0,1) M[0,1),我们便可以表示比最小规约数还要小的数。

IEEE 754规定,非规约数的实际存储指数为 0 0 0,而计算公式则为 1 − c 1-c 1c,其中 c c c 为指数偏移量。并且 M ∈ ( 0 , 1 ) M\in(0,1) M(0,1)。当 E = 0 , M ≠ 0 E=0,M\neq0 E=0,M=0 时,则按照非规约数来解析fp32。

我们来计算一下非规约形式下的最小值和最大值:

  • 最小值: M = 2 − 23 , E = 0 M=2^{-23},E=0 M=223,E=0 V = 2 − 23 ⋅ 2 − 126 = 2 − 149 ≈ 1.4 × 1 0 − 45 V=2^{-23}\cdot 2^{-126}=2^{-149}\approx 1.4\times 10^{-45} V=2232126=21491.4×1045
  • 最大值: M = 2 − 1 + 2 − 2 + ⋯ + 2 − 23 = 1 − 2 − 23 , E = 0 M=2^{-1}+2^{-2}+\cdots+2^{-23}=1-2^{-23},E=0 M=21+22++223=1223,E=0 V = ( 1 − 2 − 23 ) ⋅ 2 − 126 ≈ 2 − 126 V=(1-2^{-23})\cdot 2^{-126}\approx2^{-126} V=(1223)21262126

我们还可以进一步计算得到一般形式下的一些结论。设一个浮点数有 e e e 个指数位和 f f f 个尾数位,令 c ≜ 2 e − 1 − 1 c\triangleq 2^{e-1}-1 c2e11,则:

  • 规约形式下的最小值: 2 1 − c 2^{1-c} 21c
  • 规约形式下的最大值: ( 2 − 2 − f ) ⋅ 2 c (2-2^{-f})\cdot 2^{c} (22f)2c
  • 非规约形式下的最小值: 2 1 − c − f 2^{1-c-f} 21cf
  • 非规约形式下的最大值: ( 1 − 2 − f ) ⋅ 2 1 − c (1-2^{-f})\cdot 2^{1-c} (12f)21c

可以看出有以下等式成立:

非规约形式下的最小值 + 非规约形式下的最大值 = 规约形式下的最小值 非规约形式下的最小值+非规约形式下的最大值=规约形式下的最小值 非规约形式下的最小值+非规约形式下的最大值=规约形式下的最小值

关于特殊值的一些结论:

E E E M M M形式
[ 1 , 2 e − 2 ] [1,2^e-2] [1,2e2] [ 1 , 2 ) [1,2) [1,2)规约形式
0 0 0 ( 0 , 1 ) (0,1) (0,1)非规约形式
0 0 0 0 0 0
2 e − 1 2^e-1 2e1 0 0 0无穷
2 e − 1 2^e-1 2e1 ≠ 0 \neq0 =0NaN

4. 各种类型的浮点数

名称指数位尾数位
FP16(半精度浮点数) 5 5 5 10 10 10
BF16(Brain Floating) 8 8 8 7 7 7
FP32(单精度浮点数) 8 8 8 23 23 23
FP64(双精度浮点数) 11 11 11 52 52 52
FP128(四精度浮点数) 15 15 15 112 112 112

5. BF16和FP16的区别

先使用之前得到的公式计算一下规约形式下的最小值和最大值:

类型MinMax
FP16 2 − 14 ≈ 6.10 × 1 0 − 5 2^{-14}\approx 6.10\times10^{-5} 2146.10×105 ( 2 − 2 − 10 ) ⋅ 2 15 = 65504 (2-2^{-10})\cdot 2^{15}=65504 (2210)215=65504
BF16 2 − 126 ≈ 1.18 × 1 0 − 38 2^{-126}\approx1.18\times10^{-38} 21261.18×1038 ( 2 − 2 − 7 ) ⋅ 2 127 ≈ 3.39 × 1 0 38 (2-2^{-7})\cdot2^{127}\approx3.39\times10^{38} (227)21273.39×1038
  • BF16和FP32的指数位相同,像是从FP32中截取了前16位一样。也正是因为指数位相同,使得BF16的数值范围与FP32相似
  • BF16相比FP16的数值范围更大,但是精度会更低(深度学习中,数值范围的重要性是远大于数值精度的)。
  • FP16的数值范围过小,容易造成溢出。
  • FP16更多用于需要较高精度但数值范围要求不是特别严格的场合,如某些图形处理和较轻量级的神经网络模型推理。

Ref

[1] https://blog.csdn.net/u013250861/article/details/131152163
[2] https://www.paddlepaddle.org.cn/documentation/docs/zh/dev_guides/amp_precision/amp_op_dev_guide_cn.html
[3] https://cloud.tencent.com/developer/article/1473541
[4] https://www.runoob.com/w3cnote/decimal-decimals-are-converted-to-binary-fractions.html
[5] https://leokongwq.github.io/2017/08/19/computer-how-float-stored.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/551701.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++语言·类和对象

1. 类的引入 C语言结构体中只能定义变量&#xff0c;但在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数&#xff0c;同时C中struct的名称就可以代表类型&#xff0c;不用像C那样为了方便还要typedef一下。 在C中我们管定义的结构体类型叫做类(student)&a…

idea 将项目上传到gitee远程仓库具体操作

目录标题 一、新建仓库二、初始化项目三、addcommit四、配置远程仓库五、拉取远程仓库内容六、push代码到仓库 一、新建仓库 新建仓库教程 注意&#xff1a;远程仓库的初始文件不要与本地存在名字一样的文件&#xff0c;不然拉取会因为冲突而失败。可以把远程一样的初始文件删…

汇舟问卷:推荐一个挣外国人钱项目

在海外&#xff0c;问卷调查作为一种普遍的市场研究手段&#xff0c;它们能够为企业下一季度的营销策略调整提供有力的数据支撑。 每份问卷的报酬金额各不相同&#xff0c;最低为1美元&#xff0c;最高可以达到10几美元。大多数问卷的报酬在3到5美元之间。 然而&#xff0c;在…

JS-42-Node.js01-Node.js介绍

一、浏览器大战 众所周知&#xff0c;在Netscape设计出JavaScript后的短短几个月&#xff0c;JavaScript事实上已经是前端开发的唯一标准。 后来&#xff0c;微软通过IE击败了Netscape后一统桌面&#xff0c;结果几年时间&#xff0c;浏览器毫无进步。&#xff08;2001年推出…

HDFS详解(Hadoop)

Hadoop 分布式文件系统&#xff08;Hadoop Distributed File System&#xff0c;HDFS&#xff09;是 Apache Hadoop 生态系统的核心组件之一&#xff0c;它是设计用于存储大规模数据集并运行在廉价硬件上的分布式文件系统。 1. 分布式存储&#xff1a; HDFS 将文件分割成若干块…

【游戏云服务器推荐】幻兽帕鲁 我的世界 雾锁王国 饥荒联机版 英灵神殿通用云服务器 2-64G随意选 附最新价格对比

更新日期&#xff1a;4月17日&#xff08;京东云采购季持续进行&#xff09; 本文纯原创&#xff0c;侵权必究 《最新对比表》已更新在文章头部—腾讯云文档&#xff0c;文章具有时效性&#xff0c;请以腾讯文档为准&#xff01; 【腾讯文档实时更新】2024年-幻兽帕鲁服务器专…

李飞飞团队发布《2024年人工智能指数报告》,预测人工智能未来发展趋势

昨天&#xff0c;斯坦福大学 Human-Center Artificial Intelligence (HAI)研究中心发布了《2024年人工智能指数报告》。 由斯坦福大学发起的人工智能指数&#xff08;AI Index&#xff09;是一个追踪 AI 动态和进展的非营利性项目&#xff0c;旨在全面研究 AI 行业状况&#xf…

不同质量图在卡尔曼滤波相位解缠中应用探讨

文献来源&#xff1a;不同质量图在卡尔曼滤波相位解缠中应用探讨 闫 满&#xff0c;郭春华 测绘科学技术, 2019, 7(2), 65-73 卡尔曼滤波将相位解缠转化为状态估计问题&#xff0c;实现相位解缠与噪声消除的一并处理。通过建立相位的动 态方程和观测方程来求解真实相位&#x…

The O-one:开源语言模型计算机的革命

在人工智能的浪潮中&#xff0c;The O-one作为一个创新的开源项目&#xff0c;正以其独特的功能和开放性吸引着全球开发者和科技爱好者的目光。这个项目不仅仅是一个简单的语言模型&#xff0c;它是一个能够通过语音交互与计算机进行对话的智能系统&#xff0c;极大地提升了人机…

Java面试题笔记(持续更新)

Java基础 java中的Math.round(-1.5)等于多少&#xff1f; Math的round方法是四舍五入,如果参数是负数,则往大的数如,Math.round(-1.5)-1&#xff0c;如果是Math.round(1.5)则结果为2 JDK和JRE的区别&#xff1f; JDK 是 Java Development ToolKit 的简称&#xff0c;也就是…

Java——代码块

目录 一.代码块概念以及分类 二.普通代码块 三.构造代码块 四.静态代码块 一.代码块概念以及分类 使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字&#xff0c;又可分为以下四种&#xff1a; 普通代码块构造块静态块同步代码块&#xff08;后续讲解多…

MySQL数据库第二天

如何授权和撤销 已经给客户授权&#xff1a; GRANT all on *.* to "用户名""获取IP地址" identified by "密码" 如果想撤销可以使用&#xff1a; revoke all on 数据库.表 form "用户名""获取的IP地址" 补充&#xff1…

基于云的内部威胁该如何应对,基础设施策略该如何部署

德迅云安全发现云的内部威胁对基于云的基础设施的安全性和完整性构成威胁&#xff0c;因此需要采取主动的风险缓解策略。组织可以通过在云中采用访问控制、用户监控、加密、员工培训和事件响应协议的组合来增强对内部威胁的防御能力。 在当今瞬息万变的网络安全世界中&#xff…

安卓官方例程

https://learn.microsoft.com/zh-cn/shows/connecton-demand/202?sourcerecommendations https://learn.microsoft.com/zh-cn/visualstudio/cross-platform/cross-platform-mobile-development-in-visual-studio?viewvs-2022 https://learn.microsoft.com/zh-cn/shows/xamari…

短信防刷之滑动验证码

前言&#xff1a;最近想写一个滑动验证码&#xff0c;前台的样式虽然很好看&#xff0c;但是并不安全&#xff0c;网上也都是一些demo&#xff0c;不是前后台分离的&#xff0c;然后就自己查资料&#xff0c;自己来完成了 滑动验证码 一、为什么要使用滑动验证码 首先&#x…

洁净区气流流型测试及拍摄注意事项-北京中邦兴业解读

洁净区气流流型测试在众多确认和验证项目中虽然看似微不足道&#xff0c;但其重要性却不容忽视。要做好气流流型测试&#xff0c;绝非易事&#xff0c;它要求精细的操作和深入的专业知识。毫不夸张地说&#xff0c;即便是对影视圈的大牌导演而言&#xff0c;若是不了解气流流型…

MySQL死锁与死锁检测

一、什么是MySQL死锁 MySQL中死锁是指两个或多个事务在互相等待对方释放资源&#xff0c;导致无法继续执行的情况。 MySQL系统中当两个或多个事务在并发执行时&#xff0c;就可能会遇到每项事务都持有某些资源同时又请求其他事务持有的资源&#xff0c;从而形成事务之间循环等…

npm命令卡在reify:eslint: timing reifyNode:node_modules/webpack Completed in 475ms不动

1.现象 执行npm install命令时&#xff0c;没有报错&#xff0c;卡在reify:eslint: timing reifyNode:node_modules/webpack Completed in 475ms不动 2.解决办法 &#xff08;1&#xff09;更换淘宝镜像源 原淘宝 npm 域名http://npm.taobao.org 和 http://registry.npm.ta…

springboot 人大金仓 kingbase-备份还原,命令中带密码,支持window和linux

命令带密码参考 Java代码实现国产人大金仓数据库备份还原需求-CSDN博客文章浏览阅读818次&#xff0c;点赞16次&#xff0c;收藏12次。本人在一次项目中&#xff0c;遇到了需要在系统管理中提供给用户备份还原系统数据的功能&#xff0c;由于项目特殊性&#xff0c;项目底层数…

Day:007(3) | Python爬虫:高效数据抓取的编程技术(scrapy框架使用)

Scrapy 保存数据案例-小说保存 spider import scrapyclass XiaoshuoSpiderSpider(scrapy.Spider):name xiaoshuo_spiderallowed_domains [zy200.com]url http://www.zy200.com/5/5943/start_urls [url 11667352.html]def parse(self, response):info response.xpath(&qu…
最新文章