首页 > 其他分享 >Fast Inverse Square Root

Fast Inverse Square Root

时间:2023-05-26 17:56:38浏览次数:45  
标签:phi Inverse log Fast mu right mathrm Root left

Fast Inverse Square Root

同时包含 Approximation theory and method ch11.

https://www.youtube.com/watch?v=p8u_k2LIZyo

Fast Inverse Square Root(快速倒数平方根)是一种算法,用于快速计算一个数的倒数平方根。该算法最早出现在Quake III Arena游戏引擎中,用于在计算机图形学中加速向量的归一化过程。

Fast Inverse Square Root算法的中文名称可以直译为"快速倒数平方根"。

今天看到一个很有意思的算法, 是关于快速计算 $1/\sqrt x $ 的. 很奇怪啊, 为什么会需要优化这个的计算呢?

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 hacking
	i  = 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 removed

	return y;
}

https://en.wikipedia.org/wiki/Fast_inverse_square_root

在计算平面的法向量的时候, 我们需要把向量归一化, 也就是所谓的 normalize. 这样在我们计算向量乘法的时候, 假设我们现在有很多很多向量——就不会因为这些法向量的长度有大有小而带来误差.

具体一点的例子就如视频里面所说, 在计算一个多面物体被光源照射的时候, 反射强度就需要各个平面的法向量与光照向量点乘来计算反射强度.

我们考虑对一个float 类型的数做 fast inverse square root.

float 由 sign (1 bit), exponent (8 bit) 和 mantissa(23bit) 组成.

  • Sign 始终是 0 , 因为只有正数才能开方;
  • Exponent 记做 \(\mathrm E\);
  • Mantissa 记做 \(\mathrm M\);

float的值是:

\[\left(1+\frac{\mathrm{M}}{2^{23}}\right) \times 2^{\mathrm{E}-127} \]

这是比较数学的写法, 写得更好理解一点就是

\[\left(1+(\mathrm{M}>>23)\right) \times {(1 << (\mathrm{E}-127)}) \]

或者

(1 + (M >> 23)) << (E - 127)

就是M 右移 23 位, 加上 1 然后乘 Exponent. 乘一个 2 的指数相当于左移对应的位数.

我们先对 \(\left(1+\frac{\mathrm{M}}{2^{23}}\right) \times 2^{\mathrm{E}-127}\) 取对数,

\[\log \left(1+\frac{\mathrm{M}}{2^{23}}\right) + \log 2^{\mathrm{E}-127} = \log \left(1+\frac{\mathrm{M}}{2^{23}}\right) + {\mathrm{E}-127} \]

在中学我们学过, $\ln x $ 在 \(x = 1\) 附近可以用 \(p(x) = x\) 来进行近似计算. 在这里我们需要用一个一次多项式来逼近 \(\log _2(1+x)\) . \(\frac{\mathrm{M}}{2^{23}}\) 是一个在 \([0,1]\) 里的数, 所以我们希望在区间 \([0,1]\) 里能达到最优近似.

以下提到的定理等等, 默认参考的是 Approximation Theory and Method, M. J. D. POWELL.

视频里给出的近似方式, 强制了 \(x\) 的系数是 \(1\):

\[\log _2(1+\mathrm{x}) \approx \mathrm{x} + \mu \]

线说说不强制 \(x\) 的系数的情况吧——用来逼近的集合是 \(\mathscr A = \mathscr P_1\), 目标函数是 \(f = \log _2(1+x)\), 也就是需要找到一组 \(k, x\) 使得在区间 \([0,1]\) 上,

\[f = \log _2(1+x) \approx kx + \mu = p \]

使用 2-norm approximation, 我们希望

\[\arg_{k,\mu} \min\int_0 ^1 [f - p] \mathrm d x \]

\[f = \log _2(1+x) \approx x + \mu = p \]

希望

\[\arg_{k,\mu} \min\int_0 ^1 [f - p] \mathrm d x \]

根据 least square approximation 的 characterization theorem, 我们希望

\[(e*, p) = 0,\quad \forall p \in \mathscr A \]

$e* = f - p ^* $ 是误差函数, \((\cdot , \cdot)\) 是函数的 inner product. 多说一点, 函数的 inner product 是这样定义的: 在区间 \([a,b]\) 上,

\[(f , g) = \int_a^b wfg \mathrm d x \]

其中 \(w\) 是权重函数.

开始动手算, 我们希望误差对所有在 $\mathscr A $ 内的元素都有 0 内积. 对所有的元素有 0 内积, 实际上仅仅需要对 \(\mathscr A\) 的一组基里面所有的元素有 0 内积就行了. 这里\(\mathscr A \subset \mathscr P_2\), \(\mathscr A\) 是一个线性空间, 所以对 \(\mathscr A\) 里面所有的元素有 0 内积.

我们需要找一个 \(\mathscr A\) 的基, 为了后续求解方程组方便, 我们最好找一组正交积. 在内积的定义是在区间 \([a, b]\) 上的定积分, 所以所有相关的函数都跟 \([a, b]\) 有关, 特别得在这里是 \([0,1]\). 记 \(I= [a, b]\) . 我们可以用一套流程来生成多项式空间的正交基 (Theorem 11.3),

首先

\[\phi_0 = 1, x \in I \]

对于 \(j \geqslant 0\), \(\alpha_j\) 是这个:

\[\alpha_i=\left(\phi_i, x \phi_i\right) /\left\|\phi_j\right\|^2 \]

然后得到 \(\phi_1(x)\)

\[\phi_1(x)=\left(x-\alpha_0\right) \phi_0(x), \quad a \leqslant x \leqslant b . \]

后面的流程都用不着了.

% least_square_approximation_syms.m

% define the function that we need to approximate
syms x
f = log(x + 1) / log(2);
interval = [0, 1];

% the approximation set is of dimension (n + 1)
% here it is family of polynomial of degree n
n = 1;


phi0 = 1 + 0*x;
alpha0 = dot_prod(phi0, x * phi0, interval) ./ norm_f(phi0, interval) .^ 2;
phi1 = (x - alpha0) * phi0;

这样我们就得到了一组正交基.

disp("inner_product of the basis is:")
disp(dot_prod(phi0, phi1, interval));
disp("phi0:");
disp(phi0);
disp("phi1:");
disp(phi1);

inner_product of the basis is:
0
 
phi0:
1
 
phi1:
x - 1/2

\[\phi_0 = 1; \quad \phi_1 = x - 1/2, \]

基记为

\[B = \{ \phi_0, \phi_1\}. \]

然后我们来找最优逼近 \(p^*\). 希望 \((p, e^*) = 0, \forall p \in \mathscr A\) 也就是

\[(p, e^*) = 0, \forall p \in \mathscr A \]

也就是

\[(\phi_i, e^*) = 0, \quad i = 0, 1 \]

展开 \(e^*\):

\[(\phi_i, f - p^*) = 0, \quad i = 0, 1 \]

移项,

\[(\phi_i, f) = (\phi_i, p^*) , \quad i = 0, 1 \]

写成含 \(\phi\) 的形式:

\((\phi_i, f)\) 是一个可以计算的确定的数, 右边的 \(p^*\)可以待定系数:

\[p^* = c_0 \phi_0 + c_1 \phi_1 = \sum_{i = ...} c_i \phi _i \]

\[(\phi_i, f) = (\phi_i, c_0 \phi_0 + c_1 \phi_1), \quad i = 0, 1 \]

右边展开,

\[(\phi_i, c_0 \phi_0 + c_1 \phi_1) = c_0 (\phi_i, \phi_0 ) +c_1(\phi_i, \phi_1), \quad i = 0, 1 \]

因为是正交积, $\phi_i $ 在和不是自己内积的时候都变成 0:

\[(\phi_0, c_0 \phi_0 + c_1 \phi_1) = c_0 (\phi_0, \phi_0 ) \\ (\phi_1, c_0 \phi_0 + c_1 \phi_1) = c_1(\phi_1, \phi_1)\\ \]

两个未知数, 两个方程, 实际上就是解一组线性方程组的问题:

\[(\phi_0, f) = c_0 (\phi_0, \phi_0 ) \\ (\phi_1, f) = c_1(\phi_1, \phi_1) \]

这样每个 \(c_i\) 只需要计算一次除法就能得到.

\[c_0 = \frac{(\phi_0, f) }{\| \phi_0 \| ^ 2} \\ c_1 = \frac{(\phi_1, f) }{\| \phi_1 \| ^ 2} \\ \]

拓展到 \(i = 1, ..., n\) 的时候,

\[(\phi_i, f) = \left(\phi_i, \sum_{j = 0}^{n} c_j \phi_j \right), \quad i = 0, ..., n \]

\[(\phi_i, f) = \sum_{i = 0}^{n} c_j \left(\phi_i, \phi_j \right), \quad i = 0, ..., n \]

写得线性代数一点:

\[b = Ac \]

where

\[b_i =(\phi_i, f) , \\ A_{i,j} = \left(\phi_i, \phi_j \right), \\ c_j = c_j. \]

根据 characterization theorem, 线性方程组的解 \(c\) 肯定是存在唯一的. 如果从矩阵 \(A\) 的角度, ...(待补充)

说回当前这个问题来, \(f = \log _2(1+x)\),

用上面的结论我们可以算出:

c0 = dot_prod(phi0, f, interval) / norm_f(phi0, interval) ^ 2;
c1 = dot_prod(phi1, f, interval) / norm_f(phi1, interval) ^ 2;
p_star = c0 * phi0 + c1 * phi1;
disp(double(subs(p_star, x, 0)));
disp(double(subs(p_star, x, 1) - subs(p_star, x, 0)));
    0.0652

    0.9843

为多项式系数, 所以

\[p^* (x) = 0.9843x+ 0.0652 \]

approx_of_p2

但是这样后续处理的时候系数 \(0.9843\) 就不是很方便,

那我们还是强制第一个系数是 1 看看.

我们希望

\[\arg_\mu \min \int _0^1 \left[ \log(x + 1) - (x + \mu)\right]^2 \mathrm dx \]

把这个问题转换成我们熟悉的问题:

\[\arg_\mu \min \int _0^1 \left[ \left( \log(x + 1) - x \right) - \mu)\right]^2 \mathrm dx \]

也就是目标函数是 \(f = \left( \log(x + 1) - x \right)\), 希望用 \(p \in \mathscr A = \mathscr P_0\) 逼近. 根据 characterization theorem, 我们希望 \(\left(f - p^*, 1 \right) = 0\) (\(1\) 是 \(\mathscr A\) 的基, \(p^* = c_0 \cdot 1\)).

\(c_0\) 实际上就是 \(\mu\) 了.

\[c_0 = (f, 1) /(1,1) =\int_0^1 f(x) \mathrm dx \]

f[x_] = Log[x + 1]/Log[2] - x;
Integrate[f[x], {x, 0, 1}]
N[%]

Output:

\[\frac{\log (8)-2}{\log (4)} \\ 0.057305 \]

啊? 怎么和他们的 0.0430357 不一样?

好吧, 0.0430357 是用 uniform norm 或者叫 inf-norm 算的.

不过没关系, inf-norm 也好算. 这里斜率是固定的, 不需要用什么 exchange algorithm, 直接纯代数就能有一个 closed form.

关于 exchange algorithm, 详见____

跟刚才一样, 目标函数是 \(f = \left( \log(x + 1) - x \right)\), 希望用 \(p \in \mathscr A = \mathscr P_0\) 逼近. 根据 characterization theorem, 我们希望有 \(n + 2 = 3\) 个点, 使得在三个点处误差函数 \(e^* = f - p^*\) 符号交替得取到最大最小, 并且最大最小的绝对值一样. \(f\) 的性质也蛮不错的, 在 0 和 1 处函数值都是 0. 所以 \(\mu\) 就是 \(f\) 在区间 \([0,1]\) 最大最小值的平均. 偷个懒, 用 Mathematica 算:

Maximize[{f[x], 0 <= x <= 1}, {x}];
x1 = N[%][[1]];
Minimize[{f[x], 0 <= x <= 1}, {x}];
x2 = N[%][[1]];
(x1 + x2) /2

Output:

0.0430357

好了, 终于拿到了 0.0430357, 我们继续讨论最初的算法. 我们现在有

\[\log _2(1+\mathrm{x}) \approx \mathrm{x} + \mu \]

\(\mu\) 是已知的, 并且我们不仅仅只有 inf-norm 下最优的 \(\mu\) , 我们还有 2-norm 下最优的\(\mu\) , 我们还有 \(x\) 斜率不固定情况下的最优逼近.

\[\begin{align} &\log \left(\left(1+\frac{\mathrm{M}}{2^{23}}\right) \times 2^{\mathrm{E}-127}\right) \\ =& \log \left(1+\frac{\mathrm{M}}{2^{23}}\right) + {\mathrm{E}-127}\\ =&\frac{\mathrm{M}}{2^{23}} + \mu + {\mathrm{E}-127}\\ =&\frac{\mathrm{M}}{2^{23}} +\frac{2^{23}\mathrm{E}}{2^{23}} + \mu-127\\ =&\frac{\mathrm{M} + 2^{23}\mathrm{E}}{2^{23}} + \mu-127\\ \end{align} \]

待续.

标签:phi,Inverse,log,Fast,mu,right,mathrm,Root,left
From: https://www.cnblogs.com/kion/p/17435423.html

相关文章

  • CodeForces 1107B Digital root(找规律)
    传送门每个数字都有个数位和,就是把数字的每一位相加直到数位和是一个个位数。然后题目就要你求第K个数位和为X的数字是多少。写一些数字出来就很容易发现规律了可以看出每一竖列的数位和是相等的,然后就找到规律是9*(k-1)+x,注意数据范围是1e12,是longlong,然后就这么多,就可以直......
  • gitlab 忘记root管理员密码
    1、使用root账户登录服务器2、切换用户为gitsu-git3、进入gitlab控制台gitlab-railsconsoleproduction如报错如下:ERROR:"railsconsole"wascalledwitharguments["production"](Thor::InvocationError)就用下面这条命令(等待一会)gitlab-railsconsole4、等待ruby......
  • Fastjson 很快,但不适合我....
    作者:nyingping来源:juejin.cn/post/7215886869199863869记者:大爷您有什么特长呀?FastJson:我很快。记者:23423乘以4534等于多少?FastJson:等于2343.记者:??FastJson:你就说快不快吧!这个略显马丽苏的标题,各位看官将就着看吧。主要是怕被喷。FastJson真的很好,我用不用我喜不......
  • Mysql:低版本的mysql,5.7-,不知道root密码,如何控制(增、删、改、查)mysql.user:变相跳过mysq
    可以通过直接在mysqld的服务器上,通过os层的文件操作+为mysqld进程发送sighup(-1)信号实现。原理:低版本的mysql,5.7-,其用户账号是通过mysql系统库下的user系统表来控制的;而,mysql.user表是myisam引擎表;所以,我们只要将user.frm\user.MYD\user.MYI这3个相关数据表文件,在o......
  • openEuler root账户执行文件但是permission denied
    查看是否有可执行权限x,查看是否有rwx的x权限:ls-lfilename 没有就加上:chmod+xfilename ......
  • 1066 Root of AVL Tree
    题目:AnAVLtreeisaself-balancingbinarysearchtree.InanAVLtree,theheightsofthetwochildsubtreesofanynodedifferbyatmostone;ifatanytimetheydifferbymorethanone,rebalancingisdonetorestorethisproperty.Figures1-4illustr......
  • fastadmin 只允许在开发环境下执行命令
      解决1、设置文件[…\fastadmin\application\config.php]中的app_debug为true即可。//应用调试模式//'app_debug'=>Env::get('app.debug',false),//在线命令提示:只允许在开发环境下执行命令'app_debug'=>Env::get(&......
  • fastposter v2.15.0 从繁琐到简单,简洁好用的海报生成器
    fastposterv2.15.0从繁琐到简单,简洁好用的海报生成器从繁琐到简单,简洁好用的海报生成器我很高兴向大家推荐一款令人兴奋的工具——Fastposter海报生成器。作为一名开发者,我们深知在项目中创建专业级海报的重要性,但常常面临时间和设计技能的限制。现在,Fastposter海报生成器为我们......
  • fastposter v2.15.0 从繁琐到简单,简洁好用的海报生成器
    fastposterv2.15.0从繁琐到简单,简洁好用的海报生成器从繁琐到简单,简洁好用的海报生成器我很高兴向大家推荐一款令人兴奋的工具——Fastposter海报生成器。作为一名开发者,我们深知在项目中创建专业级海报的重要性,但常常面临时间和设计技能的限制。现在,Fastposter海报生成器为我......
  • MYSQL设置密码时显示Failed! Error: SET PASSWORD has no significance for user 'roo
    ​ 用这个命令进入mysqlsudomysql在sql命令行输入以下命令回车,你就可以把密码改成mynewpasswordALTERUSER'root'@'localhost'IDENTIFIEDWITHmysql_native_passwordby'mynewpassword';exit回到终端命令行,输入:sudomysql_secure_installation输入刚才的......