数值计算 --- 平方根倒数快速算法(0x5f3759df,这是什么鬼!!!)
平方根倒数快速算法 --- 向Greg Walsh致敬!

1,牛顿拉夫逊
已知x,要计算,假设
的值为a,则:
,(式1)
如果定义一个自变量为a的函数f(a):
则,令函数f(a)等于0的a就是我们要求的 :
如此一来,我们最开始的问题就转化为要找到能够令函数f(a)等于0的a。如果把函数f(a)画出来,则函数f(a)与a轴的交点(a,0)就是我们要找的根(一个函数的根是指使函数的值为零的自变量)。
以x=1为例,即,a=1,画出函数
的图像可见f(a)与横坐标轴a的交点正好是(a=1,y=0)。

python code:
import matplotlib.pyplot as plt import numpy as np# make data x=1 a = np.linspace(0.01,8, 1000) y = 1/(a**2)-x# plot fig, ax = plt.subplots() ax.plot(a, y, linewidth=2.0,label='f(a)') ax.set(xlim=(-1, 8),ylim=(-2, 6)) ax.legend() ax.grid(True)
2,用牛顿拉夫逊法求x=1时的
函数f(a):
,(式2)
函数f(a)的导数:
,(式3)
初次迭代
step1: 随机给定一个初始值a0,并求出相应的f(a0)
用牛顿拉夫逊法需要逐级逼近,虽然我们已经知道最终的答案是a=1,但我这里假设我并不知道。所以,我随便取了一个真实答案左边的值a0=0.5(注意,这里不能取1左边的值,否则牛顿拉夫逊法会失效),代入式2有:
得到点(0.5,3.0)这里的3.0表示实际答案与真实答案之间的误差err。
step2: 把a0=0.5代入式3计算出该点处的斜率,并利用点斜式求出点(0.5,3.0)处切线line0的方程
line0:

step3: 令y=0,找到切线line0与a轴的交点得到新的a,并称其为a1

python code:
#guess一个初值x0,并求出相应的f(a0)
a0=0.5
fa0=1/(a0**2)-x
print(f"a={a0}, Err:f(a)={fa0}")#利用点斜式求出点(a0,f(a0))处的切线line0
#y-f(a0)=s*(a-a0)
aa=np.linspace(-1, 8, 1000)
s=-2*(a0**(-3))
print(f"slope={s}")
y0=s*(aa-a0)+fa0#令y=0,找到切线line0与a轴的交点得到新的a1
a1=a0-fa0/s
print(f"a_new={a1}")# plot
fig, ax = plt.subplots()
ax.plot(a, fa, linewidth=2.0,label='f(a)')
ax.plot(aa, y0,'--',label='line0')
ax.set(xlim=(-1, 8),ylim=(-2, 6))
ax.legend()
ax.grid(True)
plt.show()
相应的log:

第二次迭代
基于新的a,即上面的a1,在函数图像上找到新的点(a1,f(a1))。画该点处的切线line1,得到line1与a轴的交点又会得到一个新的a,最终完成第二次迭代。具体的计算过程我这里不再赘述,我的python代码注释中有详细的说明。
python code:
#把a1代入函数f(a)求出相应的f(a1)
fa1=1/(a1**2)-x
print(f"a={a1}, Err:f(a)={fa1}")#利用点斜式求出点(a1,f(a1))处的切线line1
#y-f(a1)=s*(a-a1)
s=-2*(a1**(-3))
y1=s*(aa-a1)+fa1
print(f"slope={s}")#令y=0,找到切线line1与a轴的交点得到新的a2
a2=a1-fa1/s
print(f"a_new={a2}")# plot
fig, ax = plt.subplots()
ax.plot(a, fa, linewidth=2.0,label='f(a)')
ax.plot(aa, y0,'--',label='line0')
ax.plot(aa, y1,'--',label='line1')
ax.set(xlim=(-1, 8),ylim=(-2, 6))
ax.legend()
ax.grid(True)
plt.show()
相应的log:

第三次迭代
后续的迭代无非就是个重复上面的过程,求点,画切线和求交点更新a,直到误差err的值足够小,到那个时候,我们就声称已经求得最终的a了,即,的值(准备的说是达到一定精度的近似值)。
python code:
#把a2代入函数f(a)求出相应的f(a2)
fa2=1/(a2**2)-x
print(f"a={a2}, Err:f(a)={fa2}")#利用点斜式求出点(a2,f(a2))处的切线line2
#y-f(a2)=s*(a-a2)
s=-2*(a2**(-3))
y2=s*(aa-a2)+fa2
print(f"slope={s}")#令y=0,找到切线line2与a轴的交点得到新的a3
a3=a2-fa2/s
print(f"a_new={a3}")# plot
fig, ax = plt.subplots()
ax.plot(a, fa, linewidth=2.0,label='f(a)')
ax.plot(aa, y0,'--',label='line0')
ax.plot(aa, y1,'--',label='line1')
ax.plot(aa, y2,'--',label='line2')
ax.set(xlim=(-1, 8),ylim=(-2, 6))
ax.legend()
ax.grid(True)
plt.show()
相应的log:

第四次迭代
python code:
#把a3代入函数f(a)求出相应的f(a3)
fa3=1/(a3**2)-x
print(f"a={a3}, Err:f(a)={fa3}")#利用点斜式求出点(a3,f(a3))处的切线line3
#y-f(a3)=s*(a-a3)
s=-2*(a3**(-3))
y3=s*(aa-a3)+fa3
print(f"slope={s}")#令y=0,找到切线line3与a轴的交点得到新的a4
a4=a3-fa3/s
print(f"a_new={a4}")# plot
fig, ax = plt.subplots()
ax.plot(a, fa, linewidth=2.0,label='f(a)')
ax.plot(aa, y0,'--',label='line0')
ax.plot(aa, y1,'--',label='line1')
ax.plot(aa, y2,'--',label='line2')
ax.plot(aa, y3,'--',label='line3')
ax.set(xlim=(-1, 8),ylim=(-2, 6))
ax.legend()
ax.grid(True)
plt.show()

第五次迭代
python code:
#把a4代入函数f(a)求出相应的f(a4)
fa4=1/(a4**2)-x
print(f"a={a4}, Err:f(a)={fa4}")#利用点斜式求出点(a4,f(a4))处的切线line4
#y-f(a4)=s*(a-a4)
s=-2*(a4**(-3))
y4=s*(aa-a4)+fa4
print(f"slope={s}")#令y=0,找到切线line4与a轴的交点得到新的a5
a5=a4-fa4/s
print(f"a_new={a5}")# plot
fig, ax = plt.subplots()
ax.plot(a, fa, linewidth=2.0,label='f(a)')
ax.plot(aa, y0,'--',label='line0')
ax.plot(aa, y1,'--',label='line1')
ax.plot(aa, y2,'--',label='line2')
ax.plot(aa, y3,'--',label='line3')
ax.plot(aa, y4,'--',label='line4')
ax.set(xlim=(-1, 8),ylim=(-2, 6))
ax.legend()
ax.grid(True)
plt.show()

下面这张表格记录了上面每一步的结果,可以看到牛顿拉夫逊法的收敛速度还是比较快的。经过5次迭代基本上已经非常接近真实值1了。
| iteration num | a | a_new | Err |
| 1 | 0.5 | 0.687 | 3 |
| 2 | 0.687 | 0.868 | 1.12 |
| 3 | 0.868 | 0.975 | 0.32 |
| 4 | 0.975 | 0.9990923 | 0.0513 |
| 5 | 0.9990923 | 0.9999988 | 0.0018 |
在完全熟悉了牛顿拉夫逊法以后,上面的5次迭代过程都可以通过下面的计算通式来表示,从而免去了前面繁琐的计算过程。其中,表示前一次的计算结果,
表示本次迭代更新后的x。

就本例而言,则上式变成:
,(迭代方程)
其中:
代入迭代方程,得到:
,(式4)
现在我们来试一下这个公式,实际上他得到结果将和我们上面的计算结果是一样的。令a的初值a=0.5代入式3,然后不断的把新得到的a代入式4迭代,得到如下结果:

上面的迭代过程透露了一个信息,如果我们初值选择的足够好,我们就能很快完成迭代。这里我们以x=2为例,即求。因为我们都知道根号2的值大约是1.414,所以根号2的倒数也就是约等于1/1.414约等于0.707。为了用牛顿法求得更加精确的值,我们令初值a=0.707并代入迭代公式,得到:
第一次迭代后的估计值:

用python计算器算出来的精确值:

相当于只迭代了一次就已经精确到了小数点后第7位!!!而如果初值仍然是选择0.5的话,需迭代5次才能超过上面迭代一次后的精度。

而这就是平方根倒数快速算法的两大核心:1,采用牛顿拉夫逊法计算近似值。2,尽可能精确的选择牛顿法的初值。现在我们再回到前面推导的结果式4:
,(式4)
如果我们令为y,再令x/2为x2,则式4变为:
这正是平方根倒数快速算法的code中的最后一行 :

这说明了以下几点:
1,这段代码使用了牛顿拉夫逊法(毕竟code中的代码和推导出的公式一样)
2,这段代码中所使用的初值y,必然是一个非常精确的初始值
3,这个非常精确的初始值就是批注为“what the fuck?”那一行求出来的。
4,也许是因为初始值选的好,这段代码中的第二次牛顿迭代被注释掉了。也就是说整个函数只迭代了一次就达到了一个非常高的精度。
如何选择正确的初值?以及WTF中的神秘数字究竟是怎么来的
花开两朵,各表一枝。选择正确而相对精确的初值的关键在于如何求出的近似值,而求
的快速算法在于充分的利用浮点数x在计算机中的表示/编码方式。

因为x为浮点数(注意:我这里的x就是代码中的number)。则根据标准IEEE 754,x的二进制浮点数表示如下(准确的说应该叫normal number的表示):

又因为x不能为负(负数没法进行开根号运算),符号位S默认为0,则浮点数x在计算机中的二进制可表示如下:
对于单精度float而言,p=24,b=127,则:
我们对x取以2为底的对数,得到:
再令:
则上式变为:
即:
,(式5)
注意,T字段所保存的是trailing significand,即,放大一定精度后的有效数字的尾数/有效数字的小数部分(默认隐含了首位1)。计算机在保存T时把小数点右移了23位,即,乘以。因此,在读取T时才有了上面的
。这就是说上面的M实际上是“1.xxxxx...”中的“0.xxxxx...”部分,是一个介于0~1之间的数。
为了更好的理解M,这里插播一个例子,1/3是如何被保存成二进制浮点数的?
计算机使用二进制浮点数表示小数时,采用的是 IEEE 754 浮点数标准。由于1/3是一个无限循环小数,在二进制中它也不能被精确表示,所以计算机只能以有限的精度近似存储它。
1. 十进制转二进制
在十进制下,1/3=0.33333...是一个循环小数。转换到二进制后为:
也是一个二进制的循环小数,但由于计算机只能只能保存有限的位数,这个循环小数在保存时会被截断,得到一个近似值。
2. IEEE 754 浮点数表示
在 IEEE 754 单精度浮点数标准中,32位浮点数的表示结构如下:
- 1 位符号位:表示正数或负数
- 8 位指数:存储实际指数的偏移量(偏移 127)
- 23 位尾数(有效数字):存储归一化的尾数,隐含首位为 1 的小数部分
对于1/3计算机会将其转换为二进制表示,然后使用以下步骤:
标准化二进制小数:将二进制小数表示成规范形式。规范形式要求小数点左侧只能有一位,且必须是1,因此:
计算指数E:指数部分需要加上偏移量(127)。所以,计算机所保存的指数E等于上面的实际指数−2加上127。−2+127=125,再转换为二进制后为 01111101。
有效数字的尾数T:有效数字尾数的精度共 23 位,因此我们在保存小数部分时,去掉整数部分的1不保存:
然后再把小数点右移23位,得到:
4.最终存储形式:将符号位(0,正数)、指数(125 的二进制表示 011111010111110101111101)和尾数组合起来,得到:
这就是1/3的IEEE 754单精度浮点数表示。
由于M是一个0~1之间的小数,人们发现当M=0~1时,函数y=log2(1+M)与y=M的函数值差异很小。


Matlab code:
close all clear allx=0.01:0.01:pi/2; f1=log2(1+x); f2=x; plot(x,f1,x,f2); grid on; legend("y=log2(1+M)","y=x")diff=abs(f1-f2); figure plot(x,diff) legend("diff")
因此,我们认为在x=0~1之间:
基于这一近似,式5变为:
又因为括号中的,正好是浮点数x在计算机中的存储形式(我们这里用
来表示),即:
这里,我们再插播一下。如果还是以上面插播信息中的1/3为例的话。我文章中的x就是1/3(十进制),而
就是上面那个例子中最终保存的
。他们是一个数,只不过一个是实际数,一个是在计算机中存的数。
如此一来,我们利用浮点数x在计算机中默认的二进制存储方式,得到了log2(x)的表示方式:
,(式6)
现在我们再回到计算的近似值问题。根据(式1)我们知道:
对上式两边同时取以2为底的对数,得到:
根据前面推导出的log2(x)的表示方式(式6):
,
其中这个数,如果用十六进制来表示的话就是:
则上式变为:
,(式7)


这个十六进制的数code中的那个神秘数字“5f3759df”已经比较接近了,而这个数表示成十进制是1597463007。

这里我们暂时先不讨论这两个十六进制常数的差异,先看看(式7)究竟表示什么意思:
,(式7)
我们知道a就是我们要求的十进制数x的平方根的倒数,而我们又知道不论十进制数a或x是多少,他在计算机中都要以二进制浮点数的方式被保存为和
的形式。因此,(式7)的意思是说,对于一个已经按照IEEE 754标准被保存好的十进制浮点数x,他在计算机中换了个样子,变成了
,但他仍然等于x。而要想求得
的平方根的倒数,只需按照(式7)就能快速求出近似值
,这个
是与之对应的十进制浮点数a,保存在计算机中的样子。而要想把
再变成a,只需按照浮点数的编码方式解析出来即可。
现在让我们再回到原代码,我们注意到评论为WTF的上下两句所做的正是我在上文中所描述的过程。所不同的是代码中的y是我文中的x,代码中的i是我文中的,代码中的经过神秘数字“5f3759df”计算后的新i是我文中的
,而把新i重新解码后的浮点数y是我文中的a:

现在,我们有了能够快速求解出较为精确的的公式(式7),再加上之前根据牛顿拉夫逊法求得的(式4)
。至此,我们基本上复现了平方根倒数快速算法的全部过程,且和原始code一致(除了magic number之外)。
我们来试试我们现有的快速算法,看看他的效果究竟怎么样,还是以x=1为例,求。
C code:
# include <stdio.h>
# include <math.h>float Q_rsqrt(float number)
{long i;float x2, y;const float threehalfs = 1.5F;x2 = number * 0.5F;y = number;i = *(long*)&y; // evil floating point bit level hackingi = 0x5f3759df - (i >> 1); // what the fuck?y = *(float*)&i;y = y * (threehalfs - (x2 * y * y)); // 1st iteration// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removedreturn y;
}float myQ_rsqrt(float number)
{long i;float x2, y;const float threehalfs = 1.5F;x2 = number * 0.5F;y = number;i = *(long*)&y; // evil floating point bit level hackingi = 0x5f400000 - (i >> 1);y = *(float*)&i;y = y * (threehalfs - (x2 * y * y)); // 1st iteration// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removedreturn y;
}int main() {float x = 4.0f;float y = 0,yy=0;y=Q_rsqrt(x);yy = myQ_rsqrt(x);printf("input x=%f\n", x);printf("ideal result=%f\n", 1/sqrt(x));printf("calc with 5f3759df=%f\n", y);printf("calc with 5f400000=%f\n", yy);return 0;
}
相应的输出为:

就本例而言,二者的计算结果都非常接近准确值1,但5f400000的精度要更高,5f3759df的误差约为0.002。 如果以x=4为例,准确值为0.5,再看看二者的表现:

结果还是5f400000的准确性更高,5f3759df的误差约为0.0009。但上面的两个例子都是平方根的结果正好是整数的情况,例如和
。但如果碰到平方根为无理数的情况呢,我们分别试试x=2和x=3的情况。


有趣的是,在这两个例子中基于magic number的计算结果要比5f400000的精度高。对于x=2而言,5f400000的误差约为0.004,5f3759df的误差约为0.0002。 对于x=3而言,5f400000的误差约为0.006,5f3759df的误差约为0.0005。
这样看来,不论是采取哪种常数去估算初值,基本上都已经能够得到较为准确的结果。毕竟,这一初值还要用牛顿拉夫逊法再迭代一次才是最终的结果。
二者之间在十进制上的差为:
1598029824(5f400000) -1597463007(5f3759df)=566817
(全文完)
--- 作者,松下J27
参考文献(鸣谢):
1,https://en.wikipedia.org/wiki/Newton%27s_method#Examples
2,什么代码让程序员之神感叹“卧槽”?改变游戏行业的平方根倒数算法_哔哩哔哩_bilibili
3,[算法] 平方根倒数速算法中的魔数0x5f3759df的来源 | GeT Left
4,https://i.hsfzxjy.site/uncover-the-secret-of-fast-inverse-square-root-algorithm/
5,https://www.youtube.com/watch?v=p8u_k2LIZyo
6,计算机中的浮点数(一)_浮点表示法-CSDN博客
7,计算机中的浮点数(二)-CSDN博客
版权声明:所有的笔记,可能来自很多不同的网站和说明,在此没法一一列出,如有侵权,请告知,立即删除。欢迎大家转载,但是,如果有人引用或者COPY我的文章,必须在你的文章中注明你所使用的图片或者文字来自于我的文章,否则,侵权必究。 ----松下J27
相关文章:
数值计算 --- 平方根倒数快速算法(0x5f3759df,这是什么鬼!!!)
平方根倒数快速算法 --- 向Greg Walsh致敬! 1,牛顿拉夫逊 已知x,要计算,假设的值为a,则: ,(式1) 如果定义一个自变量为a的函数f(a): 则,令函数f(a)等于0的a就…...
迭代器和生成器的学习笔记
迭代器 Python 迭代器是一种对象,它实现了迭代协议,包括 __iter__() 和 __next__() 方法。迭代器可以让你在数据集中逐个访问元素,而无需关心数据结构的底层实现。与列表或其他集合相比,迭代器可以节省内存,因…...
ES5 在 Web 上的现状
最后一个支持 ES5 的浏览器 IE 11 在 2022 年被微软停止支持,那么今天 Web 上的 ES5 现状如何?在构建生产代码时,Web 开发者的最佳实践是什么? 本文将通过数据来回答这些问题,并基于这些数据为网站开发者和库作者提供一…...
人话学Python-循环语句
一:while语句 while语句的组成由判断条件和执行语句组成。当满足条件时会不断执行后续语句,然后再循环执行的语句结束之后再次回到条件判断,如此循环。 pos 0 ans 0 while pos < 6:ans pos * 4pos 1 print(ans)>>>84"&…...
初识模版!!
初识模版 1.泛型编程1.1 如何实现一个交换函数呢(使得所有数据都可以交换)?1.2 那可以不可以让编译器根据不同的类型利用该模子来生成代码呢? 2.模版类型2.1 模版概念2.2 函数模版的原理2.3 函数模板的实例化2.4 模板参数的匹配原…...
算法之数学--hash算法 2021-03-11(未完待续)
1.hash算法 刷出一道墙 题目描述 Time Limit: 2000 ms Memory Limit: 256 mb 在一面很长的墙壁上,工人们用不同的油漆去刷墙,然而可能有些地方刷过以后觉得不好看,他们会重新刷一下。有些部分因为重复刷了很多次覆盖了很多层油漆ÿ…...
DHCP工作原理
在学习之前先提出几个问题:什么是DHCP?为什么要使用DHCP?在什么场景中使用DHCP?DHCP报文的目的IP和目的MAC是多少?DHCP报文是基于UDP还是基于TCP?DHCP服务器返回的报文中都包含什么信息? DHCP&a…...
服务发现和代理实例的自动更新
☞ 返回总目录 1.服务发现的两种方式 StartFindService 方法 这是一个在后台启动的连续 “FindService” 活动,当服务实例的可用性发生变化时,会通过回调通知调用者。 它返回一个FindServiceHandle,可通过调用StopFindService来停止正在进行…...
Redis的三种持久化方法详解
Redis持久化机制详解 | JavaGuide Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式: 快照(snapshotting,RDB)只追加文件(append-only file, AOF)RDB 和 A…...
OpenAI GPT o1技术报告阅读(5)-安全性对齐以及思维链等的综合评估与思考
✨继续阅读报告:使用大模型来学习推理(Reason) 原文链接:https://openai.com/index/learning-to-reason-with-llms/ 编码 我们训练了一个模型,在2024年国际信息学奥林匹克竞赛(IOI)中得分213分,排名在第…...
nodejs 012:Babel(巴别塔)语言转换与代码兼容
这里写目录标题 安装 Babel配置presets配置:常见的 Babel Presetsplugins配置:以 plugin-transform-class-properties 的类中属性为例index.jsx Babel 是一个独立的 JavaScript 编译器,主要用于将现代 JavaScript 代码转换为旧版本的 JavaScr…...
时间安全精细化管理平台存在未授权访问漏洞
漏洞描述 登录--时间&安全精细化管理平台存在未授权访问漏洞导致与员工信息泄露 FOFA: body"登录--时间&安全精细化管理平台" 漏洞复现 POC: IP/acc/_checkinoutlog_/...
软件卸载工具(windows系统)-geek
有时候软件卸载会很麻烦,使用geek会比较方便。但是针对一些特别大的软件,geek也好像会稍微费点劲(比如MATLAB2022A),不过针对一般常规软件的卸载,geek就可以有效地完全卸载了,使用方法也很简单,…...
第三篇 第14篇 工程计价依据
第三篇 工程计价 第14篇 工程计价依据 14.1 工程造价管理标准体系与工程定额体系 14.1.1 工程造价管理标准体系 1.基础标准 工程造价术语标准建筑工程计价设备材料划分标准有关建设工程费用构成通则。建设工程费用构成和分类是工程计价最重要的基础工作。 2.管理规范 建筑…...
java 异常-Exception
异常的概念 Java 语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常) 执行过程中所发生的异常事件可分为两大类 (1)Error(错误):Java 虚…...
爬虫逆向学习(六):补环境过某数四代
声明:本篇文章内容是整理并分享在学习网上各位大佬的优秀知识后的实战与踩坑记录 引用博客: https://blog.csdn.net/shayuchaor/article/details/103629294 https://blog.csdn.net/qq_36291294/article/details/128600583 https://blog.csdn.net/weixin_…...
IO流体系(FiletOutputStream)
书写步骤: 1.创建字节输出流对象 细节1:参数是字符串表示的路径或者是File对象都是可以的 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。 细节3:如果文件已经存在,则会清空文件 2.写数据 细节:write方法的参数…...
网络设备登录——《路由与交换技术》实验报告
目录 一、实验目的 二、实验设备和环境 三、实验记录 1.通过 Console 登录 步骤1:连接配置电缆。 步骤2:启动PC,运行超级终端。 步骤3:进入Console 配置界面 2.通过 Telnet 登录 步骤1:通过 Console 接口配置 Telnet 用户。 步骤2:配置 super 口令 步骤3:配置登录欢迎…...
CSS——网格布局(display: grid)之下篇
CSS——网格布局(display: grid)之下篇 前面我们介绍了网格布局的基础的创建以及一些比较基础的属性,下面我们将介绍网格布局的剩余部分,还将结合实例来进行细致的讲解(图文并茂,生动形象有内涵࿰…...
低势期操作
《周易》讲事务发展有六个阶段: 第一阶段:潜龙勿用。 第二阶段:见龙在田。 第三阶段:终日乾乾。 第四阶段:或跃在渊。 第五阶段:飞龙在天。 第六阶段:亢龙有悔。 现在大环境不好ÿ…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权
摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题:安全。文章将详细阐述认证(Authentication) 与授权(Authorization的核心概念,对比传统 Session-Cookie 与现代 JWT(JS…...
